版本 说明 发布日期 1.0 发布文章第一版 2021-01-12
前言
这篇文章是我个人的学习笔记,可能无法做到面面俱到,也可能会有各种纰漏。如果任何疑惑的地方,欢迎一起讨论~ 如果想完整阅读这个系列的文章,欢迎关注我的专栏《Java基础系列文章》~
啥子是反射机制?
对于反射机制的理解,可能很多小伙伴们都是这样的:
同事A:晓不晓得啥子是反射机制? 同事B:大概还是晓得嘛。 同事A:那你来描述一哈喃? 同事B:呃… 同事A:就是一种可以在运行时再确定代码怎么执行的机制。 同事B:喔~~~~~~就是嘛,就这个意思! 通常情况下,我们编写的代码都是固定的,无论运行多少次,代码的执行逻辑都是一样的。 但在某些特殊场合中,我们在编写代码时并不确定要创建什么对象、调用什么方法。而是在运行时通过参数来决定。实现该需求的机制叫做反射机制,也叫动态编程技术。 目前主流的框架底层都是采用反射机制实现的,例如Spring的IOC。
Class类
基本概念
位于java.lang.Class。该类用于描述Java中的类和接口。 该类没有公共构造方法,该类的实例均由JVM和类加载器(ClassLoader)自动构造。每一个被加载到内存中的类,都会被自动构造一个对应的Class实例。
注意了哦,类加载器会把类的信息加载到方法区中。但是Class对象是存放在堆区中的哈。
获取Class对象的五种方式
数据类型.class。这种方式很常用,要脸熟他哦!
public class ClassTest {
public static void main ( String[ ] args) {
Class c = String. class ;
System. out. println ( c) ;
c = int . class ;
System. out. println ( c) ;
c = void . class ;
System. out. println ( c) ;
}
}
运行结果如下。可以发现哈,Class的toString方法比较独特。
此外,小伙伴们肯定也注意到了,基本数据类型和void也是有对应的Class实例的。为什么要这样设计呢?看了下文就会明白了,因为反射机制中,很多地方是要对基本数据类型和void返回值进行操作的! class java.lang.String
int
void
引用/对象.getClass()。这个没啥好说的。但是注意,因为基本数据类型不是对象,所以这个方式无法获取基本数据类型的Class对象。 包装类.TYPE获取对应基本数据类型的Class对象。注意和包装类.class进行区分哦。.Type获取到的是基本数据类型的Class对象,而.class获取到的是包装类自身的Class对象。 Class.forName()。这种方式也很常用,请继续脸熟~
public class ClassTest {
public static void main ( String[ ] args) throws ClassNotFoundException {
System. out. println ( Class. forName ( "java.lang.String" ) ) ;
}
}
运行结果如下。注意喽,forName的参数必须是完整类名(也就是带上包路径的类名)。同时,这种方式也不能获取基本数据类型的Class对象。 class java.lang.String
类加载器ClassLoader的loadClass方法。
public class ClassTest {
public static void main ( String[ ] args) throws ClassNotFoundException {
ClassLoader loader = ClassTest. class . getClassLoader ( ) ;
System. out. println ( loader. loadClass ( "java.lang.String" ) ) ;
}
}
执行结果还是一样。我们可以通过任何类的CLass实例来获取类加载器对象。然后用其loadClass方法,可以获取任何指定类的Class对象(也是需要提供完成类名)。除此之外,类加载器还有很多别的方法。
newInstance方法(过时)
newInstance方法用于实例化Class对象对应的类。但是目前该方法已经过时了,现在推荐使用Constructor来实例化(下文会讲)。 但这newInstance作为传统艺能,还是需要眼熟一下滴!
public class Person {
String name;
public Person ( String name) {
this . name = name;
}
public Person ( ) {
this . name = "莫得名字" ;
}
public void introduce ( ) {
System. out. println ( "俺是一个人,俺叫:" + this . name) ;
}
}
public class ClassTest {
public static void main ( String[ ] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Scanner sc = new Scanner ( System. in) ;
String className = sc. next ( ) ;
Class< ? > c = Class. forName ( className) ;
System. out. println ( "创建对象:" + c. newInstance ( ) ) ;
sc. close ( ) ;
}
}
控制台内容如下。当我们输入了Person的完整类名之后,就成功调用了Person的无参构造创建了对象。从这里,小伙伴也能够感受到什么叫“动态创建对象”吧。
com.Reflection.Person
创建对象:com.Reflection.Person@5d5eef3d
Class类的其他方法
Class类中还有很多其他的方法,这里再列举一些稍微比较常用的。但是这里也不举例子来讲了,这些东西讲起来就稍微有点多喽(偷个小懒)。
方法声明 功能 Package getPackage() 获取所在的包信息。Package为描述包的类。 Class<? super T> getSuperclass() 获取父类的信息。 Type getGenericSuperclass() 获取带泛型的父类的信息。Type为描述带泛型的类的类。 Class<?>[] getInterfaces() 获取所有接口信息。 Type[] getGenericInterfaces() 获取带泛型的接口信息。 Annotation[] getAnnotations() 获取所有注解信息。Annotation为描述注解的类。
Constructor类
位于java.lang.reflect.Constructor。用于描述构造方法的反射。 Class类有几个方法可以用于获取Constructor对象:
方法声明 功能 Constructor getConstructor(Class<?>… parameterTypes) 获取此Class对应类中,参数所对应的公共构造方法。 Constructor getDeclaredConstructor(Class<?>… parameterTypes) 获取此Class对应类中,参数所对应的构造方法。 Constructor<?>[] getConstructors() 获取此Class对应类中,所有的公共构造方法。 Constructor<?>[] getDeclaredConstructors() 获取此Class对应类中,所有的构造方法。
常用方法
方法声明 功能 T newInstance(Object… initargs) 使用对应构造方法来构造对应对象。 int getModifiers() 获取方法的访问修饰符。常见的值:无修饰:0、public:1、protected:4、private:2、public final:17。此外还有很多,就不一一列举啦。 String getName() 获取方法的名称。 Class<?>[] getParameterTypes() 获取方法所有参数的类型。
来个栗子
public class Person {
private String name;
private int age;
public Person ( String name) {
this . name = name;
}
public Person ( ) {
this . name = "莫得名字" ;
}
private Person ( String name, int age) {
this . name = name;
this . age = age;
}
@Override
public String toString ( ) {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}' ;
}
public void introduce ( ) {
System. out. println ( "俺是一个人,俺叫:" + this . name + "。今年" + this . age + "岁了" ) ;
}
private void growUp ( int period) {
age += period;
}
}
public class ClassTest {
public static void main ( String[ ] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class< Person> perClass = Person. class ;
Constructor< Person> noParaCon = perClass. getConstructor ( ) ;
Constructor< Person> oneParaCon = perClass. getConstructor ( String. class ) ;
Person per = noParaCon. newInstance ( ) ;
System. out. println ( "无参构造的结果:" + per) ;
per = oneParaCon. newInstance ( "angel" ) ;
System. out. println ( "有参构造的结果:" + per) ;
}
}
执行结果如下。
Class类的getConstructor方法中指定对应构造方法的参数,即可获取到对应的Constructor实例。注意了,getConstructor中的参数都是Class对象。所以就如上文提到的,基本数据类型的Class对象就可能在这里派上用场。 Constructor对象的newInstance方法中,需要根据对应构造方法的参数列表来传入参数。此时传入的参数就是正常的参数值了。
无参构造的结果:Person{name='莫得名字'}
有参构造的结果:Person{name='angel'}
我要全部的构造方法!
很多时候,我们并不能清楚地知道将要获取哪一个构造方法。这个时候我们可能需要先把所有构造方法都拿到,然后再来慢慢挑选。 此时我们就用getDeclaredConstructors来实现我们的小小心愿。这个例子还是用上面那个毫无创意的Person。
public class ClassTest {
public static void main ( String[ ] args) {
Class< Person> perClass = Person. class ;
Class< ? > [ ] paraTypes;
Constructor< ? > [ ] cons = perClass. getDeclaredConstructors ( ) ;
for ( Constructor< ? > con : cons) {
System. out. println ( "构造方法名称是:" + con. getName ( ) ) ;
System. out. println ( "访问权限是:" + con. getModifiers ( ) ) ;
paraTypes = con. getParameterTypes ( ) ;
System. out. print ( "参数列表是:" ) ;
for ( Class< ? > paraType : paraTypes) {
System. out. print ( paraType. getName ( ) + ", " ) ;
}
System. out. println ( "" ) ;
System. out. println ( "-------------" ) ;
}
}
}
运行结果如下。因为用的declared获取,所以获取到了所有的构造方法。
构造方法名称是:com.Reflection.Person
访问权限是:2
参数列表是:java.lang.String, int,
-------------
构造方法名称是:com.Reflection.Person
访问权限是:1
参数列表是:
-------------
构造方法名称是:com.Reflection.Person
访问权限是:1
参数列表是:java.lang.String,
-------------
Field类
位于java.lang.reflect.Field。用于描述单个成员变量的反射。 和Constructor类似,Class类中有也有对应的获取Field实例的方法。同理,带declared获取所有的,不带declared就是只获取公共的。
方法声明 功能 Field getDeclaredField(String name) 获取此Class对应类中,参数指定的单个成员变量 Field[] getDeclaredFields() 获取此Class对应类中,所有成员变量
常用方法
方法声明 功能介绍 Object get(Object obj) 从obj中获取此Field对象对应的成员变量的数值。 void set(Object obj, Object value) 将obj中此Field对象对应的成员变量的数值修改为value。 void setAccessible(boolean flag) 设置是否对访问权限进行检查。true代表不检查,false代表检查。 int getModifiers() 获取成员变量的访问修饰符。值与Constructor类的相同。 Class<?> getType() 获取成员变量的数据类型。 String getName() 获取成员变量的名称。
又来个栗子
public class FieldTest {
public static void main ( String[ ] args) throws NoSuchFieldException, IllegalAccessException {
Class< ? > c = Person. class ;
Field field = c. getDeclaredField ( "name" ) ;
Person per = new Person ( ) ;
field. setAccessible ( true ) ;
System. out. println ( field. getName ( ) + "的值为:" + field. get ( per) + "。该变量的类型是:" + field. getType ( ) . getName ( ) ) ;
field. set ( per, "大帅哥" ) ;
System. out. println ( field. getName ( ) + "的值为:" + field. get ( per) + "。该变量的类型是:" + field. getType ( ) . getName ( ) ) ;
}
}
运行结果如下。我很想习惯性地说点什么,但是这个例子太简单了,实在是不知道说啥。获取所有的Field我就不测啦,一回事儿!
name的值为:莫得名字。该变量的类型是:java.lang.String
name的值为:大帅哥。该变量的类型是:java.lang.String
Method类
位于java.lang.reflect.Method。用于描述单个成员方法的反射。 Class类中对应的获取方法如下。
方法声明 功能 Method getMethod(String name, Class<?>… parameterTypes) 获取该Class对应类中,名字为name,参数为parameterTypes的公共成员方法。 Method[] getMethods() 获取该Class对应类中,所有公共成员方法。包括从父类继承的方法。 Method[] getDeclaredMethods() 获取该Class对应类中,所有成员方法。不包括从父类继承的方法。
常用方法
方法声明 功能 Object invoke(Object obj, Object… args) 调用对象obj中对应的方法,args为实参列表。 int getModifiers() 获取方法的访问修饰符。值与Constructor类的相同。 void setAccessible(boolean flag) 设置是否对访问权限进行检查。true代表不检查,false代表检查。 Class<?> getReturnType() 获取方法的返回值类型。 String getName() 获取方法的名称。 Class<?>[] getParameterTypes() 获取方法所有参数的类型。 Class<?>[] getExceptionTypes() 获取方法的异常抛出类型。
双来个栗子
public class MethodTest {
public static void main ( String[ ] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
Class< ? > c = Person. class ;
Constructor< ? > constructor = c. getDeclaredConstructor ( String. class , int . class ) ;
constructor. setAccessible ( true ) ;
Object object = constructor. newInstance ( "小美女" , 24 ) ;
Method m1 = c. getDeclaredMethod ( "introduce" ) ;
m1. invoke ( object) ;
}
}
运行结果如下。到这里,小伙伴们都能发现了,无论是Constructor、Filed还是Method。反射机制的这几个家伙,几乎都是大同小异。所以,不会吧?不会吧?不会真有人还不能触类旁通吧?
俺是一个人,俺叫:小美女。今年24岁了
获取子类方法时,有无declared的区别
按规矩来说,这一节应该讲获取所有方法的例子,但是光讲这个就太没意思了。所以我决定整一个子类,然后获取其所有方法,看看方法数组中有哪些父类方法:
public class Woman extends Person {
}
public class MethodTest {
public static void main ( String[ ] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
MethodTest. multi ( ) ;
}
private static void multi ( ) {
Class< ? > c = Woman. class ;
Method[ ] methods = c. getMethods ( ) ;
for ( Method method : methods) {
System. out. println ( "方法名称为:" + method. getName ( ) + "。访问控制符为:" + method. getModifiers ( ) + "。返回值类型为:" + method. getReturnType ( ) . getName ( ) ) ;
System. out. println ( ) ;
}
}
}
执行结果如下。
可以看到,除了获取到了Person类的方法,还获取到了很多Object类的。 getModifiers还出现了几个新值:273:public final native、257:public native。
方法名称为:toString。访问控制符为:1。返回值类型为:java.lang.String
方法名称为:introduce。访问控制符为:17。返回值类型为:void
方法名称为:wait。访问控制符为:17。返回值类型为:void
方法名称为:wait。访问控制符为:17。返回值类型为:void
方法名称为:wait。访问控制符为:273。返回值类型为:void
方法名称为:equals。访问控制符为:1。返回值类型为:boolean
方法名称为:hashCode。访问控制符为:257。返回值类型为:int
方法名称为:getClass。访问控制符为:273。返回值类型为:java.lang.Class
方法名称为:notify。访问控制符为:273。返回值类型为:void
方法名称为:notifyAll。访问控制符为:273。返回值类型为:void
如果把Method[] methods = c.getMethods();
改为Method[] methods = c.getDeclaredMethods();
,则执行结果会变为如下。对,就是一个方法都没有哈哈哈!这一点在上面的方法表格中也说到了,因为Woman类中确实一个方法都没有声明。