java中除了基本类型以外都是class,interface也是广义上的class(例如:String,Object,Runnable,Exception),class的本质就是数据类型,我们把一个对象的实例赋值给一种 数据类型变量时,我们必须严格按照数据类型来赋值。没有继承关系的数据类型是无法赋值的。所以Java定义了一种强类型的数据关系,并且编译器会检查数据类型,不符合数据类型要求的就会报错
class和interface的数据类型是Class,JVM在加载class时,每加载一个class,JVM就为其创建一个Class类型的实例并关联起来。JVM持有的每一个Class实例都指向一个数据类型,例如:
而一个Class实例会包含该class的完整信息,例如类名,包等:
简而言之,Class就是JVM为每个class创建对应的Class实例,并在实例中保存该class的所有信息。那么,如果我们获取到了某个Class实例,我们就可以获取到该实例对应的class的所有信息,这种通过Class实例获取class信息的方法称为反射。
如何获取一个class的Class实例呢,有三种方法,
方法一:类名.class
方法二:对实例变量使用.getClass()方法
方法三:使用Class的静态方法forName()
在JVM中,一个Class实例对应唯一的class,因此我们可以用==来比较两个Class实例,那么这和instanceof的差别是什么呢?
instanceof不仅匹配当前类型,还会匹配当前类型的子类:
而使用==时,会精确地判断当前类型
由此可见,通常我们应该使用instanceof来判断数据类型,因为面向抽象编程时我们并不关心具体的子类型,只有在需要精确判断class时,才需要用==。
那么反射机制的意义是什么呢?
1.当获得某个Object实例时,我们可以获取该Object的class信息。
那么我们在Class实例中可以获取哪些class信息呢?从Class实例获取class信息的方法有: getName() getSimpleName() getPackage()等
此外,从Class实例还可以判断class的类型:
isInterface() 是否为interface
isEnum() 是否为枚举
isArray() 是否为数组
isPrimitive()是不是基本类型
前面我们说了基本类型变量本身并不是class,但是JVM的内部会为其创建int.class这几种基本类型对应的Class实例。
我们还可以通过Class的newInstance()来创建class实例:
这里的局限性在于我们只能使用不带参数的public默认构造方法,带参数的构造方法是不能这样调用的。
我们知道,JVM在加载class时是动态加载的,利用这个特性,可以在运行期根据条件不同加载不同的实现类。这样,就算某个包不存在,我们也可以通过条件判断来控制加载。例如:
2.访问字段:
我们先来看一下java中的属性与字段的区别
java中的Java中的属性(property),通常可以理解为get和set方法,而字段(field),通常叫做“类成员”,或 “类成员变量”,有时也叫“域”,理解为“数据成员”,用来承载数据的。
例如:
这里的属性a由get和set组成
这里的b没有get和set,是字段
这里的c由字段c和get/set组成
区别开属性与字段是为了更好的实现数据安全,比如当我们想给一个类的属性赋值或者其他类用到了,就需要将这个字段设置为public,然而这样可以对字段进行任意的读写操作,非常不利于数据安全。于是就加上了属性,简单说属性实现了字段的封装,属性有get、set 方法来控制字段,该字段的属性只有set方法没有get方法,就只可以对该方法进行赋值操作,没有读操作,反之亦然。就是对字段的操作通过属性来控制。
而通过Class实例,我们可以获取字段field信息:
getField(name):获取某个public的field(包括父类)
getDeclaredField(name):获取当前类的某个field(不包括父类)
getFields():获取所有public的field(包括父类)
getDeclaredFields():获取当前类的所有field(不包括父类)
在Field对象中包含一个field的所有信息,可以用以下方法获取:
getName() 名称
getType() 字段定义类型
getModifiers() 修饰符类型,返回的是1,2,....
通过field对象还可以获得和设置field的值:
get(Object obj)
set(Object, Object)
对于静态字段,我们在使用get和set时不需要传入实例,传入null即可
当我们需要访问private字段时,可以使用以下方法:
但是此方法有失败的可能,如果JVM定义了SecurityManager,那么会有一个规则,阻止我们对该Field设置accessible,抛出异常,例如,把规则应用于所有java和javax开头的package的类,通常我们编写的类以及第三方的类是没有这个限制的。
3.调用方法
Method对象中封装了方法的所有信息
通过Class实例可以获取到method
getMethod(name, Class...):获取某个public的method(包括父类)
getDeclaredMethod(name, Class...):获取当前类的某个method(不包括父类)
getMethods():获取所有public的method(包括父类)
getDeclaredMethods():获取当前类的所有method(不包括父类)
这里的Class参数是方法参数,是可变参数
与Field对象类似,Method对象包含了一个method的所有信息
getName()
getReturnType() 返回类型
getParameterTypes() 参数类型
getModifiers()
我们获取到method对象时,可以直接在某个实例上调用这个方法
调用无参数Method: Object invoke(Object obj)
调用含参数Method: Object invoke(Object obj, Object... args)
同样的,对于private方法,我们同样可以使用setAccessible(true),但是也同样受到SecurityManager的制约。
另外,对于从父类获取到的方法,当我们传入一个子类的实例时,实际调用的是子类覆写的方法,这样保证了多态的正确性。
4.构造方法的调用
我们前面说到,使用Class.newInstance()可以调用public无参数的默认构造方法,但是对于没有定义无参数构造方法的Class,不能直接使用newInstance。对于带参数的构造方法,我们要使用Constructor对象,Constructor对象包含了一个构造方法的所有信息,可以用于创建实例。
getConstructor(Class...):获取某个public的Constructor
getDeclaredConstructor(Class...):获取某个Constructor
getConstructors():获取所有public的Constructor
getDeclaredConstructors():获取所有Constructor
对于非public构造方法,我们可以同样可以通过设置setAccessible(true)来访问。
5.获取继承关系:
获取父类的Class:Class getSuperclass()
tip:Object的父类是null ,interface的父类是null
获取当前类直接实现的interface:Class[] getInterfaces()
返回的数组中不包括间接实现的interface ,没有interface的class返回空数组, 而对于interface,返回的是继承的interface
我们还可以使用bool isAssignableFrom(Class)判断一个向上转型是否成立:
本文深入解析Java反射机制,介绍Class实例的获取方式、反射机制的应用场景,如获取类信息、访问字段、调用方法和构造方法,以及获取继承关系。

被折叠的 条评论
为什么被折叠?



