反射与Class类
(所有源代码已经上传到码云上:https://gitee.com/c3h2/knowledge-study.git 该章节所属reflect包中,具体代码开编号,每个类都有编号)
1.1 反射简介
在Java之中,反射就是其中最重要的特性之一,也正是因为Java支持反射,才使得Java的设计更加灵活,这么多年也一直经久不衰的原因之一(抗高并发也是一个原因)。如果你不掌握反射,那么在Java里面很难走的通,也写不出高复用的代码,因为几乎所有的框架都是基于反射来实现的。
既然Java反射如此的重要,那么什么是Java反射呢,Java反射中的“反”又是什么意思呢?观察如下代码(源码reflect.cn.tansanqinger.basics.Test1.java):
public class Test1 {
public static void main(String[] args) {
Class<?> clazz = new java.util.Date().getClass();
System.out.println(clazz);//直接输出,详细描述所有信息
System.out.println(clazz.getName());//获取类完整的包名信息
System.out.println(clazz.getSimpleName());//获取类名
}
}
上述代码运行结果为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bqi8pmfK-1600683885467)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20200921113201484.png)]
上述代码获取了Date类的相关信息,包括类路径和类名,相当于是通过一个名称,将它的底完全挖掘出来了。能够获取到类的包名,那么我们就可以实现不导包而将类进行实例化操作,这样就降低了耦合。
1.2 获取Class类对象实例
在Java反射中用于获取Class类对象实例的方式有三,他们分别是:
第一种:利用Object类提供的getClass()方法,通过实例化对象调用获得
第二种:利用“类.class”的原生方式来获得
第三种:通过Class类中提供的方法进行实例化
上述获取实例的new java.util.Date().getClass()
也可以修改为java.util.Date.class
,前者利用Object中的getClass()方法来获取Class类的实例,实际上需要对通过指定类的实例化对象来完成,但是后者可以在没有指定类实例化对象的时候直接获取Class实例,相比之前节约了一个实例化对象。
除此之外,还有Class内部提供的一个forName方法,利用该方法也可以实现Class的实例化,该方法的定义为:
public static Class<?> forName(String className) throws ClassNotFoundException
使用此方法需要注意的是,要书写正确的类路径和类名,否则将会抛出ClassNotFoundException
这个异常。
三种方式都很重要,因此都需要掌握
getClass():需要通过明确的获得使用类的实例化对象;
类.class:需要明确的进行操作类的导入处理;
Class.forName():可以通过字符串描述要使用类的名称;
三种不同方式在开发中的使用如下(源码reflect.cn.tansanqinger.basics.Test2.java):
public class Test2 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clsA = java.util.Date.class;
Class<?> clsB = Class.forName("java.util.Date");
Class<?> clsC = new java.util.Date().getClass();
System.out.println(clsA);
System.out.println(clsB);
System.out.println(clsC);
System.out.println(clsA.getName());
System.out.println(clsB.getName());
System.out.println(clsC.getName());
System.out.println(clsA.getSimpleName());
System.out.println(clsB.getSimpleName());
System.out.println(clsC.getSimpleName());
}
}
运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b41DzvtN-1600683885474)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20200921132410798.png)]
1.3 反射与类操作
上述说了Java反射的三种使用方式,这一节来分析一下反射与类的操作,首先看一组方法
序号 | 方法名称 | 类型 | 说明 |
---|---|---|---|
1 | public Package getPackage() | 方法 | 获取程序所在的包名称 |
2 | public Class<? super T> getSuperclass() | 方法 | 获取所继承的父类 |
3 | public Class<?>[] getInterfaces() | 方法 | 获取所有的父接口 |
4 | public Constructor **getConstructor(Class<?>… parameterTypes) ** throws NoSuchMethodException, SecurityException | 方法 | 根据指定的参数类型获取指定的构造方法(public) |
5 | public Class<?>[] getConstructors() throws SecurityException | 方法 | 获取类中的所有的构造方法(public) |
6 | public Constructor getDeclaredConstructor(Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException | 方法 | 获取一个类中指定的构造方法(public、protected、default) |
7 | public Constructor<?>[] **getDeclaredConstructors() **throws SecurityException | 方法 | 获取一个类中的全部构造方法(public、protected、default) |
8 | public Method[] getDeclaredMethods() throws SecurityException | 方法 | 获取本类中的所有方法 |
9 | public Method getDeclaredMethod(String name, Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException | 方法 | 获取本类一个指定类型的Method实例 |
10 | public Method getMethod(String name, Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException | 方法 | 获取一个公共的方法,包括父类的方法 |
11 | public Field[] getFields() throws SecurityException | 方法 | 获取所有继承来的public成员 |
12 | public Field getField(String name) throws NoSuchFieldException, SecurityException | 方法 | 获取一个指定类型的成员 |
13 | public Field[] getDeclaredFields() throws SecurityException | 方法 | 获取本类定义的全部的成员 |
14 | public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException | 方法 | 获取一个本类定义成员 |
15 | public Class<?> getType() | 获取属性类型 | |
16 | public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException | 获取属性内容 | |
17 | public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException | 设置属性内容 | |
18 | public void setAccessible(boolean flag) | 取消封装 |
上述的所有的方法就是Java反射中重要的方法,当然还有很多的方法,这里就不一一列举了,如果你有兴趣,可以直接去查看源代码JDK12文档了解更多。
同时需要注意的一件事:使用反射进行类实例化在jdk1.9之后发生了变化。我们可以通过官网的源代码来了解这一变化。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gLJt8oyo-1600683885477)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20200921172320288.png)]
下面演示一下这些方法的使用(源代码reflect.cn.tansanqinger.basics.Test3.java):
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
interface IMessage{}
interface IChannel{}
abstract class AbstraceChannalMessage implements IMessage,IChannel{
public void AbstractChannelMessage() {}
public void AbstractChannelMessage(String username, String password) {}
}
//该类为定义方法,但是可以获取继承来的方法
class Person{}
class CloudMessage extends AbstraceChannalMessage implements IMessage,IChannel{
public CloudMessage() {}
public CloudMessage(String url, String userName, String password) {}
}
public class Test3 {
public static void main(String[] args) {
Class<?> clazzA = CloudMessage.class;
System.out.println("【获取包的名称】"+clazzA.getPackage().getName());
System.out.println("【获取继承父类】"+clazzA.getSuperclass().getName());
System.out.println("【获取实现接口】"+ Arrays.toString(clazzA.getInterfaces()));
//---------------获取构造方法----------------
Class<?> clazzB = CloudMessage.class;
Constructor<?> constructor[] = clazzB.getDeclaredConstructors();
for(Constructor<?> cont : constructor) {
System.out.println(cont);
}
//-------------获取方法--------------
Class<?> clazz = Person.class;
Method methods[] = clazz.getMethods();
for(int i = 0 ; i < methods.length ; i ++) {
System.out.print(Modifier.toString(methods[i].getModifiers())+" ");
System.out.print(methods[i].getReturnType().getName()+" ");
System.out.print(methods[i].getName()+"(");
Class<?> params[] = methods[i].getParameterTypes();//获取所有参数
for(int x = 0 ; x < params.length ; x++) {
System.out.print(params[x].getSimpleName()+" arg-"+x);
if(x<params.length -1) {
System.out.print(",");
}
}
System.out.print(") ");
Class<?> exps[] = methods[i].getExceptionTypes();
if(exps.length > 0) {
System.out.print("throws ");
for(int x = 0 ; x < exps.length ; x++) {
System.out.print(exps[x].getSimpleName());
if(x <params.length -1) {
System.out.print(",");
}
}
}
System.out.println();
}
}
}
剩下几个类似的使用方法,这里就不细说了。
1.4Unsafe工具类
为了进一步对反射进行扩展,Java提供了一个Unsafe类,该类位于sun.misc
包之中,该类主要的功能就是通过反射来获取对象,打破JVM的固定对象使用模式,该类直接通过C++对数据进行读取,这个类可以在没有实例化对象的时候进行类中方法的调用。
构造方法:private Unsafe() {}
常量:private static final Unsafe theUnsafe = new Unsafe();
由于构造方法被私有化了,因此该类需要通过内部定义的“theUnsafe”常量对象来进行Unsafe类的调用。使用方法如下(源代码reflect.cn.tansanqinger.basics.Test4.java):
class Singleton{
private Singleton(){
System.out.println("构造方法被私有化");
}
public void msg(){
System.out.println("www.tansanqinger.cn");
}
}
public class Test4 {
public static void main(String[] args) {
try{
Field unsafe = Unsafe.class.getDeclaredField("theUnsafe");
unsafe.setAccessible(true);
Unsafe unsafe1 = (Unsafe) unsafe.get(null);
Singleton singleton = (Singleton) unsafe1.allocateInstance(Singleton.class);
singleton.msg();
} catch (Exception e){}
}
}
现在可以发现利用Unsafe类可以打破已有的JVM中关于对象的使用的模型,可以在没有实例化对象的时候直接调用类中所提供的普通方法,但是这样的操作表示的处理全部由程序自己完成,而所有的代码不受到JVM的控制,也就意味着所有的垃圾回收机制就将失效。
提出意义:Unsafe只是提供了一种底层的直接进行对象操作的支持,与实际的开发意义不大。如果在以后笔试的过程里面要求你编写单例设计模式的时候,请你注意如下几点:要使用懒汉式单例设计、要针对于数据的同步做出处理、补充上利用Unsafe可以绕过对象的实例化机制,直接调用类中的方法。