1.对于任意一个object实例,只要我们获取了它的Class,就可以获取它的一切信息。
2.利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。
3.如果使用反射可以获取private字段的值,那么类的封装还有什么意义?
正常情况下,我们总是通过p.name来访问Person的name字段,编译器会根据public、protected和private决定是否允许访问字段,这样就达到了数据封装的目的。而反射始终非常规的用法,使用反射,首先代码十分繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。此外,setAccessible(true)可能会失败。如果JVM运行期间存在SercurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。
4.通过Filed实例既然可以获取到指定实例的字段值,自然也可以设置字段的值。设置字段值是通过Filed.set(Object, Object)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。
(1)java的反射API提供的Method对象封装了方法的所有信息。
(2)通过Class实例的方法可以获取Method实例:getMethod(),getMethods(), getDeclaredMethod(), getDeclaredMethods();
(3)通过Method实例可以获取方法信息:getName(), getReturnType(), getParamterTypes(), getModifiers();
(4)通过反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法。
(1)我们通常使用new操作符创建新的实例,如果使用反射来创建新的实例,可以调用Class提供的newInstance()方法。调用Class.newInstance()的局限是,它只能调用该类的public无参构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstacn()来调用。
(2)为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且调用结果总是返回实例。
(1)class和interface的区别:
一、可以实例化class(非abstract)
二、不能实例化interface。
所有interface类型的变量总是通过某个实例向上转型并赋值给接口类型变量的。为了实现不编写实现类,直接在运行期创建某个interface的实例,java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以再运行期间动态创建某个interface的实例。这种没有实现类但是在运行期间创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,叫作动态代理。
(2)在运行期动态创建一个interface实例的方法如下:
1.定义一个InvocationHandler实例,它负责实现接口的方法调用。
2.通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
使用的ClassLoader,通常就是接口类的ClassLoader;
需要实现的接口数组,至少需要传一个接口进去;
用来处理接口调用方法的InvocationHandler
3.将返回的Object强制转型为接口
(3)动态代理实际上是JVM在运行期动态创建class字节码并加载的过程。
(1)注解(Annotation)是放在java源码的类、方法、字段、参数前的一种特殊注释。
(2)注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。
(3)从JVM的角度看,注解本身对代码逻辑没有任何影响,如果使用注解完全由工具决定。
(4)java的注解可以分为三类:第一类是由编译器使用的注解,例如:@Override:让编译器检查该方法是否正确地实现了覆写。@SupperessWarning:告诉编译器忽略此处代码产生的警告。这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。
第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存于内存中。这类注解只被一些底层库使用,一般我们不用自己处理。
(5)定义一个注解,还可以定义配置参数。配置参数可以包括:所有基本类型;String;枚举类型;基本类型、String、Class以及枚举的数组。因为配置参数必须是常量,所以上述限制保证了注解在定义时就已经确定了每个参数的值。注解的配置参数可以有默认值,缺少某个配置参数时将使用默认值。此外,大部分注解会有一个名为value的配置参数,对此参数赋值,可以只写常量,相当于省略了value参数。如果只写注解,相当于全部使用默认值。
(1)Java使用@interface语法来定义注解(Annotation)它的格式如下:
public @interface Report {
int type() default 0;
String level() default “info”:
String value() default “”;
}
(2)元注解:有些注解可以修饰其他注解,这些注解就被称为元注解(meta annotation)。
@Taget
最常用的元注解就是@Target。使用@Target可以定义Anootation能够被应用于源码的哪些位置:
类或接口:ElementType.TYPE;
字段:ElementType.FIELD;
方法:ElementType.METHOD;
构造方法:ElementType.CONSTRUCTOR;
方法参数:ElementType.PARAMETER
@Retention
另一个重要的元注解@Retention定义了Annotataion的声明周期:
仅编译器:RetentionPolicy.SOURCE;
仅class文件:RetentionPolicy.CLASS
运行期:RetentionPolicy.RUNTIME
如果@Retention不存在,则该Annotation默认为CLASS。因为通常我们自定的Annotataion都是RUNTIME,所以,务必加上@Retention(RetentionPolicy.RUNTIME)这个元注解。
@Inherited
使用@Inherited定义子类是否可以继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效。
JAVA的注解本身对于代码逻辑没有任何影响。根据@Retention的配置:
SOURCE类型的注解在编译器就被丢掉了;
CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM
RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取
(1)因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。Java提供的使用反射API读取Annotation的方法包括:判断某个注解是否存在于Class、Field、Method、或者Contructor:
Class.isAnnotationPresent(Class)
Field.isAnnotationPresent(Class)
Method.isAnnotationPresent(Class)
Constructor.isAnnotationPresent(Class)
(2)使用反射API读取Annotation有两种方法。方法一是判断Annotation是否存在,如果存在,则直接读取:
Class cls = Person.class;
if (cls.isAnnotationPresent(Report.class)) {
Report report = cls.getAnnotation(Report.class);}
第二种方法是直接读取Annotation,如果Annotation不存在,则返回null。
Class cls = Person.class;
Report report= cls.getAnnotation(Report.class);
if (report != null) {
…}
读取方法、字段和构造方法的Annotation和Class类似,单数读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。要获取方法参数的注解,我们先用反射获取Method实例,然后读取方法参数的所有注解。