一、反射
通过Class实例获取 class 信息的方法称为反射(Reflection)。
(一) Class 类
Java 除基本类型外其他都是class类型(包括 interface,例如:String、Object、Runnable、Exception),class 的本质就是一种数据类型;
1、Class 的简介
(1)class/interface 的数据类型是 Class(如下代码),每加载一个 class,JVM 就为其创建了一个 Class 类型的实例,并关联起来(如下图:“创建class.png”)
(2)JVM 持有的每个 Class 实例都指向一个数据类型(class 或 interface,如下图:”Class实例.png“)。
(3)一个 Class 实例包含了该 class 的完整信息,例如下图是指向 String 类(class)的 Class 实例:它包含了 String 这个类的 name、package、super、interface、field、method 等信息。
(4)JVM 为每个加载的 class 创建对应的 Class 实例,并在实例中保存该 class 的所有信息。
2、反射:通过Class实例获取class信息的方法称为反射。如果获取了某个 Class 实例,则可以获取到该实例对应的 class 的所有信息。
(2)对实例变量调用 getClass 方法:
(3)使用 Class 提供的 静态方法 forName():
4、Class 实例在 JVM 中是唯一的,可以使用 == 比较两个 Class 实例
5、Class 实例比较和 instanceof 的差别:
(1)instanceof 可以判断是否相等,判断子类关系。
(2)Class 实例比较符 "==",只能判断是否相等,而不能判断是否是子类关系。
6、从 Class 实例获取 class 信息:
(1)getName()
(2)getSimpleName()
(3)getPackage()
7、从 Class 实例本身判断 class 的类型:
(1)isInterface() 判断是不是 interface 类型
(2)isEnum() 判断是不是 枚举 类型
(3)isArray() 判断是不是 Array 类型
(4)isPrimitive() 判断是不是 Primitive 类型
8、使用 Class 创建 class 实例
9、利用 JVM 动态加载 class 的特性,可以在运行期根据条件加载不同的实现类(例如 Commons Logging 的使用):
10、Class 类使用实际案例
(二) 访问字段
1、通过Class实例获取字段field信息:
(1)getField(name):获取某个 public 的field(包括父类)
(2)getDeclaredField(name):获取当前类的某个field(不包括父类)
(3)getFields():获取所有 public 的field(包括父类)
(4)getDeclaredFields():获取当前类的所有field(不包括父类)
2、Field对象包含一个field的所有信息:
(1)getName()
(2)getType()
(3)getModifiers()
(2)set(Object, Object) 设置 一个实例的该字段 field 的值
4、通过设置 setAccessible(true) 来访问非 public 字段
注意:setAccessible(true) 可能会失败,如果 JVM 中定义了 SecurityManager ,SecurityManager会有一个规则,如果这个规则阻止对 Field 设置 accessible(例如:规则应用于所有 java 和 javax 开头的 package 的类,则就不能访问 private 字段)。通常情况下我们自己编写的类 和 第三方类没有这个限制。
(三) 调用方法
1、通过Class实例获取方法Method信息:
(1)getMethod(name, Class...):获取某个public的method(包括父类)
(2)getDeclaredMethod(name, Class...):获取当前类的某个method(不包括父类)
(3)getMethods():获取所有public的method(包括父类)
(4)getDeclaredMethods():获取当前类的所有method(不包括父类)
2、Method对象包含一个method的所有信息:
(1)getName()
(2)getReturnType()
(3)getParameterTypes()
(4)getModifiers()
(2)调用带参数的 Method 方法:Object invoke(Object obj, Object...args),即第一个参数是实例变量,第二个参数是方法参数
4、通过设置setAccessible(true)来访问非public方法。
(四) 调用构造方法
1、调用 public 无参数构造方法:
(1)使用 Class.newInstance() 调用
(2)newInstance() 只能调用无参数的构造方法
2、调用带参数的 构造方法
(1)调用带参数的构造方法,不能直接使用 Class 的 newInstance 方法。需要获取 Constructor 对象,Constructor 对象包含一个构造方法的所有信息,通过 Constructor 对象的 newInstance() 方法来调用。(即Constructor实例使用 newInstance 可以创建一个实例对象)
(2)实例:
(3)通过Class实例获取 构造方法 的信息:
a、getConstructor(Class...):获取某个public的Constructor
b、getDeclaredConstructor(Class...):获取某个Constructor
c、getConstructors():获取所有 public 的 Constructor
d、getDeclaredConstructors():获取所有Constructor
(4)通过设置setAccessible(true)来访问非public构造方法。
3、注意区分 Class.newinstance 和 Constructor.newinstance 的区别
(五) 获取继承关系
1、获取父类的Class:
(1)使用 Class getSuperclass() 获取父类 Class
(2)Object 的父类是null
(3)interface 的父类是null
2、获取当前类直接实现的 interface(不包括间接实现的interface) :
(1)使用 Class[] getInterfaces() 来获取实现的 interface
(2)没有interface的class返回空数组
(3)interface 返回继承的 interface
3、判断一个向上转型是否成立:bool isAssignableFrom(Class)
4、总结
(1)getSuperclass() // 获取类的父类
(2)getInterfaces() // 获取类直接实现的接口
(3)isAssignableFrom() // 判断向上转型是否正确
二、注解
(一) 使用注解
1、注解的定义:注解(Annotation)是放在Java源码的类、方法、字段、参数前的一种标签,是 java 语言用于工具处理的标注。
2、注解的作用
(1)注解本身对代码逻辑没有任何影响
(2)如何使用注解由工具(例如:编译器)决定
3、编辑器可以使用的注解:
(1)@Override:让编辑器检查该方法是否正确地实现了覆写
(2)@Deprecated:告诉编译器该方法已经被标记为"作废",在其他地方引用将会出现编译警告
(3)@SuppressWarnings:告诉编译器忽略某种警告
(4)写注解编译器会帮助检查问题,不写注解编译器就不会检查。
4、注解可以定义配置参数:
(1)配置参数由注解类型定义
(2)配置参数可以包括:
(a)所有基本类型
(b)String
(c)枚举类型
(d)数组
(3)配置参数必须是常量
5、缺少某个配置参数将使用默认值
(1)如果只写常量,相当于省略了 value 参数
(2)如果只写注解,相当于全部使用默认值,默认值的定义是在注解定义时定义的
(二) 定义注解
1、在 Java 中使用 @interface 来定义注解(Annotation)
(1)注解的参数类似无参数方法
(2)可以给注解设定一个 默认值 (推荐)
(3)把最常用的参数命名为 value能(推荐)
2、元注解:
(1)有些注解可以修饰其他注解,称为元注解。
(2)使用 @Target 定义Annotation 可以被应用于源码的哪些位置
(a)类或接口:ElementType.TYPE
(b)字段:ElementType.FIELD
(c)方法:ElementType.METHOD
(d)构造方法:ElementType.CONSTRUCTOR
(e)方法参数:ElementType.PARAMETER
(3)使用 @Retention 定义 Annotation 的生命周期:
(a)仅编译期:RetentionPolicy.SOURCE 编译器在编译时直接丢弃,即编译器编译后该注解就不存在了
(b)仅 class 文件:RetentionPolicy.CLASS 仅存储在 class 文件中
(c)运行期:RetentionPolicy.RUNTIME 在运行期可以读取该Annotation注解
(d)如果 @Retention 不存在,则该 Annotation 默认为 CLASS,通常自定义的注解 Annotation 都是 RUNTIME。
(4)使用 @Repeatable 定义 注解Annotation 是否可重复 (JDK >= 1.8)
(5)使用 @Inherited 定义子类是否可以继承父类定义的 注解
(a)仅针对 @Target 为 TYPE 类型的 注解
(b)仅针对 class 的继承
(c)对 interface 的继承无效
3、总结定义注解步骤:
(1)用 @interface 定义注解
(2)用 元注解(meta annotation)配置注解
(a)Target:必须设置
(b)Retention:一般设置为 RUNTIME
(c)通常不必写 @Inherited、@Repeatable 等等
(3)定义注解参数和默认值
4、总结:
(1)使用 @interface 定义注解
(2)可以定义多个参数和默认值,核心参数使用 value 名称
(3)必须设置 @Target 来指定 Annotation 可以应用的范围
(4)应当设置 @Retention 为 RUNTIME 便于运行期读取 该 Annotation
(三) 处理注解
1、使用反射API读取 RUNTIME类型 的注解 Annotation:
(1)Class.isAnnotationPresent(Class) :判断 注解 是否存在
(2)Field.isAnnotationPresent(Class)
(3)Method.isAnnotationPresent(Class)
(4)Constructor.isAnnotationPresent(Class)
(5)Class.getAnnotation(Class) :获取 注解
(6)Field.getAnnotation(Class)
(7)Method.getAnnotation(Class)
(8)Constructor.getAnnotation(Class)
(9)getParameterAnnotations()
2、使用反射 API 读取 class Annotation 常用的两种方法:
3、使用反射 API 读取方法参数的注解 PARAMETER Annotation 常用的方法:
4、可以通过工具处理注解来实现相应的功能:
(1)对JavaBean的属性值按规则进行检查
(2)JUnit会自动运行@Test标记的测试方法
(四) 注解的应用:
1、编写注解 @NotNull:检查该属性为非null
2、编写注解 @Range:检查整型介于min~max,或者检查字符串长度介于min~max
3、编写注解 @ZipCode:检查字符串是否全部由数字构成,且长度恰好为value
三、泛型
(一) 泛型的定义
1、泛型就是定义一种模版,例如 ArrayList<T>
(1)在代码中为用到的类创建对应的 ArrayList<类型>:
ArrayList<String> strList = new ArrayList<String>();
(2)编译器针对类型作检查操作:
strList.add("hello");
strList.add(new Integer(123)); // complie error!
(2)注意泛型的继承关系:
a. 可以把 ArrayLIst<Integer> 向上转型为 List<Integer> (T 不能变);
b. 不能把 ArrayLIst<Integer> 向上转型为 ArrayLIst<Number> 或 List<Number>,因为 ArrayList<Number>、 List<Number> 和 ArrayList<Integer> 没有继承关系;
(二) 泛型的使用
1、使用泛型时,可以省略编译器能自动推断出的类型
2、Arrays.sort() 可以对 Object[] 数组进行排序:
3、泛型的好处是在编译的时候检查类型安全,并且所有的 强制转换 都是自动和隐式的,提高代码的重用率。
4、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
(三) 泛型的编写(不常用)
1、如何编写一个泛型类:
(1)按照某种类型(例如 String)编写类
(2)标记所有的特定类型(例如 String)
(3)把特定类型替换为 T, 并申明 <T>
2、静态方法不能引用泛型类型<T>,必须定义其他类型<K>来实现“泛型”,即:编译器无法在 静态字段 或者 静态方法 中使用泛型类型<T>.
java基础篇反射
3、编写泛型时可以定义多种类型
(四) 擦拭法
1、Java 的 泛型是采用擦拭法实现的,虚拟机其实对泛型一无所知,所有的泛型都是编译器在操作。
编译器把类型 <T> 视为 Object
编译器根据 <T> 实现安全的强制转型
2、擦拭法的局限
(1)<T>不能是基本类型,例如:int
(2)Object字段无法持有基本类型
(3)无法取得带泛型的Class,例如:Pair<String>.class, 即无论 T 是什么类型,Class 都是 Pair.class
(6)泛型方法要防止重复定义方法,例如:public boolean equals(T obj)
3、子类可以获取父类的泛型类型 <T>
4、JDK 定义了 Type接口, 他的实现类包括:Class、ParameterizedType、GenericArrayType、WildcardType。
(五) extends 通配符
1、使用 extends 通配符,可以接收本身类型及子类,例如:
案例一:使用 Pair<? extends Number> 可以使方法接收所有泛型类型为 Number 或 Number 子类的 Pair 类。
案例二:使用 Pair<? extends String> 可以使方法接收所有泛型类型为 String 或 String 子类的 Pair 类。
2、 使用类似<? super Integer> 通配符作为 方法参数 是表示(代码如上):
(1)方法内部可以调用获取 Integer 引用的方法(get方法),例如:Number n = obj.getXxx()
(2)方法内部无法调用传入 Integer 引用的方法(set方法 null 除外),例如:obj.setXxx(Number n) // 报错
3、extends 通配符作为 方法参数 实例
4、extends 通配符的另一个用法 -- 定义泛型类:<T extends Number>
(1)定义泛型时可以通过extends限定T 必须 是Number或Number的子类
(2)方法内部可以调用获取(get) Number 引用的方法,Number n = obj,getXxx()。
(3)方法内部无法调用传入(set、add、remove) Number 引用的方法(null 除外),obj.setXxx(Number n)。
5、使用类似<T extends Number> 定义泛型类型时表示:泛型类型限定为 Number 或 Number 的子类
(六) super 通配符
1、使用 super 通配符,可以接收本身类型及超类,例如:
案例一:使用 Pair<? super Integer> 可使方法接受所有泛型类型为 Integer 或 Integer 超类的 Pair 类
案例二:使用 Pair<? super String> 可使方法接受所有泛型类型为 String 或 String 超类的 Pair 类
2、使用类似 <? super Integer> 通配符作为 方法参数 时表示(代码如上):
(1)方法内部可以调用传入 Integer 引用的方法:obj.setXxx(Integer n)
(2)方法内部无法调用获取 Integer 引用的方法(Object 除外):Object o = p.getFirst() // 获取 object 的引用
3、super 通配符作为 方法参数 实例
以上实例:
(1)允许传入List<Integer>,List<Number>,List<Object>
(2)允许调用方法传入Integer类型
(3)不允许调用方法获取Integer类型(Object除外)
4、supper 通配符的另一个用法 -- 定义泛型类:<T super Integer>
(1)使用 <T super Integer> 定义泛型类 时表示:泛型类限定为 Integer 或者 Integer 超类。
(2)实例:
5、extends 和 super 通配符的区别:
(1)<? extends T> 允许调用方法获取 T 的引用
(2)<? super T> 允许调用方法传入 T 的引用
6、无限定通配符 <?> 很少使用,只能获取 Object 引用,只能传入 null,可以用 <T> 替换。
(七) 泛型和反射
(2)Constructor<T> 是泛型
(2)必须通过强制转型实现带泛型的数组:
(3)使用泛型数组时要注意安全地使用,因为数组在运行过程中是没有泛型的。
3、带泛型的数组实际上是编译器的类型擦除,所以对带泛型的数组调用 getClass() 方法,返回的是 定义的类型.class
(2)方法二:利用可变参数(T...)创建 T[] 数组:
(八) 泛型使用实例
1、案例一:
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/3838.html