Reference: https://liaoxuefeng.com
JVM每加载一种class
,就为其创建一个Class
实例,并关联起来。注意:这里的Class
类型是一个名叫Class
的class
,形如:
1 | public final class Class { |
以String类为例,当JVM加载String类的时候,首先会读取String.class
到内存,然后,为String
类创建一个Class
实例并关联起来:
1 | Class cls = new Class(String); |
JVM持有的每个Class
实例都指向一个数据类型(class
或interface
):
1 | ┌───────────────────────────┐ |
一个Class
实例包含了该class
的所有完整信息:
1 | ┌───────────────────────────┐ |
由于JVM为每个加载的class
创建了对应的Class
实例,并在实例中保存了该class
的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class
实例,我们就可以通过这个Class
实例获取到该实例对应的class
的所有信息。
这种通过Class
实例获取class
信息的方法称为反射(Reflection)。
获取一个class
的Class
实例有三种方法:
方法一:直接通过一个class
的静态变量class
获取:
1 | Class cls = String.class; |
方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()
方法获取:
1 | String s = "Hello"; |
方法三:如果知道一个class
的完整类名,可以通过静态方法Class.forName()
获取:
1 | Class cls = Class.forName("java.lang.String"); |
Class
类提供了以下几个方法来获取字段:
一个Field
对象包含了一个字段的所有信息:
getName()
:返回字段名称,例如,"name"
;getType()
:返回字段类型,也是一个Class
实例,例如,String.class
;getModifiers()
:返回字段的修饰符,它是一个int
,不同的bit表示不同的含义。还是以上面这段代码为例:
利用反射拿到字段的一个Field
实例之后,可以拿到一个实例对应的该字段的值:
如果不调用Field.setAccessible(true)
,就会抛出一个IllegalAccessException
的错误,这是因为name
字段被定义成private
。
除了使用get方法获取字段值,还可以用set方法获取字段值:
同样,修改非public
字段,需要首先调用setAccessible(true)
。
Class
类提供了以下几个方法来获取Method
:
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)Method[] getMethods()
:获取所有public
的Method
(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有Method
(不包括父类)一个Method
对象包含一个方法的所有信息:
getName()
:返回方法名称,例如:"getScore"
;getReturnType()
:返回方法返回值类型,也是一个Class实例,例如:String.class
;getParameterTypes()
:返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义。当我们获取到一个Method
对象时,就可以对它进行调用。我们以下面的代码为例:
1 | String s = "Hello world"; |
如果用反射来调用substring
方法:
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke
方法传入的第一个参数永远为null
。以Integer.parseInt(String)
为例:
和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()
获取该方法实例,但直接对其调用将得到一个IllegalAccessException
。为了调用非public方法,我们通过Method.setAccessible(true)
允许其调用:
一个Person
类定义了hello()
方法,并且它的子类Student
也覆写了hello()
方法,那么,从Person.class
获取的Method
,作用于Student
实例时,同样会遵循多态原则,实际调用的是Student
类的hello()
方法。
Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。
通过Class实例获取Constructor的方法如下:
getConstructor(Class...)
:获取某个public
的Constructor
;getDeclaredConstructor(Class...)
:获取某个Constructor
;getConstructors()
:获取所有public
的Constructor
;getDeclaredConstructors()
:获取所有Constructor
。由于一个类可能实现一个或多个接口,通过Class
我们就可以查询到实现的接口类型。例如,查询String
实现的接口:
https://www.jianshu.com/p/95970b089360
在运行期动态创建一个interface
实例的方法如下:
InvocationHandler
实例,它负责实现接口的方法调用;Proxy.newProxyInstance()
创建interface
实例,它需要3个参数:
ClassLoader
,通常就是接口类的ClassLoader
;InvocationHandler
实例。Object
强制转型为接口。使用@Target
可以定义Annotation
能够被应用于源码的哪些位置:
ElementType.TYPE
;ElementType.FIELD
;ElementType.METHOD
;ElementType.CONSTRUCTOR
;ElementType.PARAMETER
。元注解@Retention
定义了Annotation
的生命周期:
RetentionPolicy.SOURCE
;RetentionPolicy.CLASS
;RetentionPolicy.RUNTIME
。如果@Retention
不存在,则该Annotation
默认为CLASS
。
我们总结一下定义Annotation
的步骤:
第一步,用@interface
定义注解:
1 | public @interface Report { |
第二步,添加参数、默认值:
1 | public @interface Report { |
把最常用的参数定义为value()
,推荐所有参数都尽量设置默认值。
第三步,用元注解配置注解:
1 | @Target(ElementType.TYPE) |
其中,必须设置@Target
和@Retention
,@Retention
一般设置为RUNTIME
,因为我们自定义的注解通常要求在运行期读取。
Person.java
1 | public class Person { |
Range.java
1 | import java.lang.annotation.ElementType; |
Main.java
1 | import java.lang.reflect.Field; |
输出:
1 | Person {Person: name=Bob, city=Beijing, age=20} checked ok. |