目录
1,什么是动态语言?(在程序运行的时候,我们仍然可以改变它的内容,就是动态语言)
Java动态性之:反射(Reflection)
反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。
1,什么是动态语言?(在程序运行的时候,我们仍然可以改变它的内容,就是动态语言)
程序运行时,可以改变程序结构或变量类型的。
在当我们执行一段代码的时候,这个时候我们可以动态的插入一些内容,改变程序的结构。
其实Java不是动态语言,但是Java具有很多动态语言的功能,所以我们把它称为“准动态语言”。这个时候就可以用到了反射功能。
2,反射机制
反射机制指的是可以在运行时加载、探知、使用编译期间完全未知的类。
比如:
Class c = Class.forName("com.xxx.test.User");
我们这边传的是一个User类,这个时候我们传什么类都可以,这就是一个动态的体现。
我们动态的加载完这个类后,我们可以去创建这个类的对象,甚至可以调用类的方法和属性(或相关对象的方法和属性)。
3,Class类的介绍
它位于java.lang.Class中,这个类十分特殊,用来表示java中的类型。(它是单独出来的一个类)
那么所有的类型都可以被定义为一个Class对象。比如原始Java类型(boolean、byte、char、short、int、long、float和double)和关键字void也表示为Class对象。(对象就是用来表示或封装一些数据,一个类被加载后,jvm会创建一个对应这个类的Class对象,类的整个结构信息,会被放到Class对象中)
现在Class对象就像一面镜子一样,我们通过这面镜子可以看到对应类的全部信息。这就类似反射机制。(反射机制==对象)。这时候我们可以通过这个对象看到所有的信息。
而Class类被加载后,它就会形成一个对象,第一个对象产生后就不会再产生第二个对象了(第二个就是重复第一个对象的)。
3.1 java.lang.Class对象的获取方式
String path = "TestClass02.User";// User是一个类名
Class<?> clazz = Class.forName(path);// 通过path对象名来获取这个类的名称
// 通过类名来获取对象
System.out.println(clazz.hashCode());
int[] a = new int[10];
System.out.println(a.getClass().hashCode());
同样的类型特征通过Class加载出来的都是同一个对象。
同类型不同特征通过Class加载出来的都不是同一个对象。
不同的类型特征通过Class加载出来的就不是同一个对象了。
列举
int[] arr01 = new int[30];
int[][] arr02 = new int[30][1];
double[] arr03 = new double[30];
System.out.println(arr01.getClass().hashCode());// 同样的类型特征(这是一维数组)通过Class加载出来的都是同一个对象。
System.out.println(arr02.getClass().hashCode());// 不同的类型特征(这是二维数组)加载出来的不是同一个对象。
System.out.println(arr03.getClass().hashCode());// 不同的类型加载出来的也不是同一个对象。
4,反射机制常见的作用
4.1 通过反射API,来获取类的信息(比如:类名、类的属性、类的方法、类的构造器)
我们通过Class对象来获取相关类的用法。
在创建好一个User类后,我们可以通过Class类来获取User类的所有信息(比如:类名、类的属性、类的方法、类的构造器)
获取包名+类名:
举例:
String path = "TestClass02.User";// 创建字符串,输出包名(TestClass)+ 类名(User)。
try {
Class<?> clazz = Class.forName(path);// 创建Class类
System.out.println(clazz.getName());// 获得该对象的包名+类名
System.out.println(clazz.getSimpleName());// 获得该对象的类名
获取类的属性:
举例:
通过对象名clazz . getFields();方法来获取公开的属性。
通过对象名clazz . getDeclaredFields();方法可以获取所有的属性(包括私有属性private)。
通过对象名clazz . getDeclaredField();方法指定获取的属性。
Field[] fields = clazz.getFields();// 只能获得公开的属性
System.out.println(fields.length);// 打印获取公开的属性的长度
Field[] dfs = clazz.getDeclaredFields();// 可以获取所有属性
System.out.println(dfs.length);// 打印获取所有的属性的长度
Field dfs2 = clazz.getDeclaredField("uname");// 指定获取的属性
System.out.println(dfs2);// 打印指定获取的属性
这时候我们可以使用增强for循环来打印出User类里的所有属性(包括私有属性private)。
for (方法 循环名 : 方法名){
输出所有属性,并循打印出来。
}
for (Field temp : dfs) { // 使用增强for循环来便利这个属性
System.out.println("属性:" + temp);
}
获得类的相关方法:
举例:
通过对象名clazz . getDeclaredMethods();方法可以获取所有相关的方法。
通过对象名clazz . getDeclaredMethod();方法指定获取的方法。
// 获得相关的方法
Method[] methods = clazz.getDeclaredMethods();
Method m1 = clazz.getDeclaredMethod("getUname", null);// 加上null意思就是我这时候调用的是空方法
System.out.println(m1);
Method m2 = clazz.getDeclaredMethod("setUname", String.class);// 加上这个String.class意思是当我调用这个方法时,采用的时(String class)的方法
System.out.println(m2);
for (Method temp2 : methods) { // 使用增强for循环来遍历获取method方法
System.out.println("方法:" + temp2);
}
获得相关的构造器:
通过对象名clazz.getDeclaredConstructors(); 方法来获取所有的构造器。
通过对象名clazz.getDeclaredConstructor();方法来获取指定的构造器(也可以传递不同参数类型来获取该构造器)
举例:
// 获得相关的构造器
Constructor<?>[] cs = clazz.getDeclaredConstructors();// 获取所有构造器
Constructor cs2 = clazz.getDeclaredConstructor( int.class,int.class,String.class);// 传递不同参数类型来获取构造器
System.out.println(cs2);
for (Constructor temp3 : cs) { // 使用增强for循环来遍历获取constructor构造器
System.out.println("构造器:" + temp3);
}
4.2 通过反射API动态的操作:构造器、方法、属性。
首先说使用反射的好处:它可以实现动态的调用。(动态的调用是指,在程序运行时,我们可以动态的插入一些内容改变程序的结构)
通过反射API动态调用构造器。
举例:
// 怎么通过反射API动态去调用构造器
String path = "TestClass02.User";// 创建字符串,输出包名(TestClass02)+类名(User)
try {
Class<User> clazz = (Class<User>) Class.forName(path);
User u = clazz.newInstance();// 这里的newInstance方法调用User的无参构造方法
System.out.println(u);
// 首先我们通过参数类型指定相对应的构造器方法。(指定调用的构造器)
Constructor<User> c = clazz.getDeclaredConstructor(int.class,int.class,String.class);
// 然后设置指定调用构造器的值,通过newInstance来传递实际的参数来进行调用
User u2 = c.newInstance(1001, 15, "张三");
// 最后将u2.getUname();方法把Uname"张三"的值打印出来了,现在调用的就是有参构造器了。
System.out.println(u2.getUname());// 打印出u2的getUname方法
通过反射API来调用方法:
这样做的好处是:通过反射得到的都是都是变量(比如下面代码的方法名和参数都成了变量)
举例
// 通过反射API来调用普通方法
User u3 =clazz.newInstance();// 这是通过u3.setUname来调用方法
Method method = clazz.getDeclaredMethod("setUname",String.class);
// 怎么通过反射API来调用方法呢? 首先我要通过反射获得方法再去调用。
method.invoke(u3,"李四");// 给u3设值,这段意思是通过setUname方法,给u3设置Uname名字。
System.out.println(u3.getUname());// 输出给u3设置的值.
通过反射API操作属性
setAccessible(true);方法,表示了这个属性不用再做安全检查了,可以直接访问。(因为我们User类的属性一般都设置为private私有的)
举例
User u4 = clazz.newInstance();// 调用属性
Field f = clazz.getDeclaredField("uname");
f.setAccessible(true);// 表示这个属性不用做安全检查了,可以直接访问。(这时候就可以不用考虑到私有属性了)
f.set(u4,"赵五");// 通过反射直接把值写给属性。
System.out.println(u4.getUname());// 这串代码写的时候会报错,因为类的属性是私有的,我们访问不到。(这时候我们要加上上面的setAccessible方法才能使用)
System.out.println(f.get(u4));// 通过反射API来调用的
5,反射的性能问题
优点:在使用反射的时候,程序运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够给我们带来了活的编程,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。但是相同的,反射也给我们带来了弊端,那就是让程序变慢了。
缺点:在进行反射操作的时候会消耗一定的系统资源。(然而,我们如果不需要动态的创建一个对象的时候,就不需要用反射。)
当然我们也有setAccessible();方法,这个方法意思是当我们有执行程序的时候,我们把这个setAccessible方法设为true,这样就可以跳过安全检查,节约时间的访问到所有的信息了。提升了程序运行速度。(但是这样忽略权限的检查,可能会破坏封装性而导致一些安全问题。)