java反射
反射概述
原作者总结的非常到位,这里只划上重点。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
个人理解:
众所周知,一个类编译后会生成且只会生成一个与该类相对应的字节码文件。我们也知道,当我们定义了一个java类,可以用这个java类实例化任意多个对象,实例化得到对象之后,我们可以给对象属性赋值,并且操作对象的方法。上述步骤如下:定义的Class —> new Instance()实例化对象 —> instance.set() instance.get()调用方法。这些操作都是在编译前,编译之后会生成该类的字节码文件,而反射恰恰相反。
首先反射的核心在于Class类,而Class类依赖于字节码文件,字节码文件都有了肯定是编译后,也就是说,反射是编译后来操作类的,也就是运行时的。我们通过Class类的API可以得到已经定义的任何一个类的所有信息,这些信息包括该类的构造方法,成员变量,成员方法等等,我们可以调用这些方法,修改属性,完成一系列操作。上述步骤如下:编译后 —> 字节码文件 —> Class类的API获取需要的类 —> 通过这个类实例化对象,操作方法。
深入理解的话,这里偷一张图:
类加载
类加载时机
- 当程序使用到某个类,如果这个类还没被加载到内存中,则加载该类。
- 系统会通过加载、连接、初始化来对类进行初始化
- 加载:将class文件读入内存,并为之创建一个class对象,任何类被使用时系统都会建立一个class对象
- 连接:验证是否正确的内部连接,并和其他类协调一致,准备负责为类的静态成员分配内存,并设置默认初始化值
- 初始化成员变量等等
- 加载时机
- 创建类的实例
- 访问类中的静态变量,或者为静态变量赋值
- 调用静态方法
- 初始化某个类的子类
- 使用反射方式强制创建某个类对应或接口对应的java.lang.class对象
类加载器
什么是类加载器(class loader)
- 负责将.class文件加载到内存中,并生成对应的Class对象
- 虽然我们不需要关心类怎么加载,但是了解这个机制后有利于我们理解程序的运行
类加载器分类
根类加载器、扩展类加载器、系统类加载器
反射
创建一个对象的三个阶段
源文件阶段.java文件,字节码阶段.class,创建对象阶段,new类名
内省
在运行时能够获取Javabean当中的属性名和getset方法
反射
java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。这种动态的获取类的信息以及调用类的方法的机制成为java反射机制。想要使用反射,必须得有字节码文件
反射API
假设已经有一个标准的Student bean类,得到该类的Class对象的三种方式:
首先写一个Student类,如下:
public class TestAPI {
public static void main(String[] args) {
// 获取class对象的三种方式:
// 第一种方式
Student stu1 = new Student("lzj", 21);
Class stu1class1 = stu1.getClass();
System.out.println(stu1class1.getName());
// 第二种方式
Class stu1class2 = Student.class;
System.out.println("判断Student.class和stu1.getClass()获得的class是否是同一对象");
System.out.println(stu1class1 == stu1class2);
// 第三种方式
try {
Class stu1class3 = Class.forName("test_reflection.Student");// 全路径,加包名
System.out.println("判断Student.class和Class.forName()获得的class是否是同一对象");
System.out.println(stu1class2 == stu1class3);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
打印结果:
test_reflection.Student
判断Student.class和stu1.getClass()获得的class是否是同一对象
true
判断Student.class和Class.forName()获得的class是否是同一对象
true
由结果来看,这三种方式得到的Class对象都是同一个对象,也证明了:在运行期间,一个类只有一个Class对象,对于这三种方式常用第三种,第一种对象都有了还要反射干什么。第二种需要导入类的包,依赖太强,不导包就抛编译错误。一般都第三种,一个字符串可以传入也可写在配置文件中等多种方法。 此外第三种更安全性能更好。
使用反射
得到这个Class对象后,我们可以解析其中的属性和方法。
使用构造方法,API比较简单,这里直接贴代码了
package test_reflection; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; class Person { // ---------------构造方法------------------- // (默认的构造方法) Person(String str) { System.out.println("(默认)的构造方法 s = " + str); } // 无参构造方法 public Person() { System.out.println("调用了公有、无参构造方法执行了。。。"); } // 有一个参数的构造方法 public Person(char name) { System.out.println("姓名:" + name); } // 有多个参数的构造方法 public Person(String name, int age) { System.out.println("姓名:" + name + "年龄:" + age);// 这的执行效率有问题,以后解决。 } // 受保护的构造方法 protected Person(boolean n) { System.out.println("受保护的构造方法 n = " + n); } // 私有构造方法 private Person(int age) { System.out.println("私有的构造方法 年龄:" + age); } } public class ReflectConstructors { public static void main(String[] args) { try { /** * 获取Person的Class类 */ Class personClass = Class.forName("test_reflection.Person"); /** * 获取所有公有构造方法 */ System.out.println("***********获取所有公有构造方法***********"); Constructor[] publicConstructors = personClass.getConstructors(); for (int i = 0; i < publicConstructors.length; i++) { System.out.println(publicConstructors[i]); } System.out.println("***********获取所有公有构造方法***********" + "\n"); /** * 获取特定公有构造方法 */ System.out.println("***********获取特定公有构造方法***********"); // 根据参数类型来获取 Constructor publicConstructor1 = personClass.getConstructor(null); System.out.println(publicConstructor1); Constructor publicConstructor2 = personClass .getConstructor(char.class); System.out.println(publicConstructor2); Constructor publicConstructor3 = personClass.getConstructor( String.class, int.class); System.out.println(publicConstructor3); System.out.println("***********获取特定公有构造方法***********\n"); /** * 获取所有构造方法,包括公有、私有、受保护、默认 */ System.out.println("***********获取所有构造方法,包括公有、私有、受保护、默认***********"); Constructor[] allConstructors = personClass .getDeclaredConstructors(); for (int i = 0; i < allConstructors.length; i++) { System.out.println(allConstructors[i]); } System.out .println("***********获取所有构造方法,包括公有、私有、受保护、默认***********\n"); /** * 获取特定的私有构造方法,并调用实例化一个对象 */ System.out.println("***********获取特定的私有构造方法,并调用***********"); Constructor protectedConstructor = personClass .getDeclaredConstructor(boolean.class); System.out.println(protectedConstructor); Constructor privateConstructor = personClass .getDeclaredConstructor(int.class); System.out.println(privateConstructor); // 暴力访问(忽略掉访问修饰符) protectedConstructor.setAccessible(true); privateConstructor.setAccessible(true); Object protectedObject = protectedConstructor.newInstance(true); Object privateObject = privateConstructor.newInstance(13); System.out.println("***********获取特定的私有构造方法,并调用***********\n"); catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
输出:
*获取所有公有构造方法*
public test_reflection.Person()
public test_reflection.Person(char)
public test_reflection.Person(java.lang.String,int)
*获取所有公有构造方法**获取特定公有构造方法*
public test_reflection.Person()
public test_reflection.Person(char)
public test_reflection.Person(java.lang.String,int)
*获取特定公有构造方法**获取所有构造方法,包括公有、私有、受保护、默认*
test_reflection.Person(java.lang.String)
public test_reflection.Person()
public test_reflection.Person(char)
public test_reflection.Person(java.lang.String,int)
protected test_reflection.Person(boolean)
private test_reflection.Person(int)
*获取所有构造方法,包括公有、私有、受保护、默认**获取特定的私有构造方法,并调用*
protected test_reflection.Person(boolean)
private test_reflection.Person(int)
受保护的构造方法 n = true
私有的构造方法 年龄:13
*获取特定的私有构造方法,并调用*总结:
- getConstruct(parameterTypes)系列API获得公开的构造方法,可以通过参数类型选定需要的构造方法。无参则传null,返回全部方法后面加s表示多个。
- getDeclaredConstructor(parameterTypes)系列API获取所有构造方法,一般用于获得私有构造方法,规则同上,暴力访问加上:setAccessible(true)。
使用成员变量
package test_reflection; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; class Student1 { public Student1() { } // **********字段*************// public String name; protected int age; char sex; private String phoneNum; public static int staticFiled; @Override public String toString() { return "Student [name=" + name + ", age=" + age + ", sex=" + sex + ", phoneNum=" + phoneNum + "]"; } } public class ReflectFields { public static void main(String[] args) { try { /** * 获取Class对象 */ Class student1class = Class.forName("test_reflection.Student1"); /** * 获取所有公有字段,获取特定字段等同构造方法 */ Field[] publicFields = student1class.getFields(); for (int i = 0; i < publicFields.length; i++) { System.out.println(publicFields[i]); } /** * 这里再举一个获取私有字段并给它赋值的例子 * 可以看到,获取特定字段的标志是字段名:phoneNum */ Field privateField = student1class.getDeclaredField("phoneNum"); System.out.println(privateField); //暴力访问,无视修饰符 privateField.setAccessible(true); //在给这个字段赋值之前,需要创建一个对象,否则不知道是哪个对象的字段 //这里也用反射实例化一个对象 Object studentObj = student1class.getConstructor(null).newInstance(null); //这里的set方法就是给此字段赋值,如果字段是私有的,可以暴力访问 privateField.set(studentObj, "1845111...."); System.out.println(studentObj); /** * 获取静态字段 */ Field staticField = student1class.getDeclaredField("staticFiled"); System.out.println(staticField); staticField.setAccessible(true); staticField.set(null, 12); System.out.println(Student1.staticFiled); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
打印结果:
public java.lang.String test_reflection.Student1.name
public static int test_reflection.Student1.staticFiled
private java.lang.String test_reflection.Student1.phoneNum
Student [name=null, age=0, sex= , phoneNum=1845111….]
public static int test_reflection.Student1.staticFiled
12API比较简单,这里总结下:
获取字段:getDeclaredField(name)获取字段,传入的参数是字段名。
使用字段:给字段赋值:privateField.set(studentObj, “1845111….”);第一个参数是该字段所属对象,第二个参数是赋值内容。
思考,如果一个字段不仅仅属于一个对象,而是属于该类,也就是静态成员变量,那该如何获取和使用?
猜测:获取还是照常获取,使用的话第一个参数不需要传字段所在对象,直接传null,果然不出所料,静态成员变量是不需要依赖某一实例化对象的。
/** * 获取静态字段 */ Field staticField = student1class.getDeclaredField("staticFiled"); System.out.println(staticField); staticField.setAccessible(true); staticField.set(null, 12); System.out.println(Student1.staticFiled);
调用成员方法
package test_reflection; import java.lang.reflect.Method; class Student2 { // **************成员方法***************// public void show1(String s) { System.out.println("调用了:公有的,String参数的show1(): s = " + s); } protected void show2() { System.out.println("调用了:受保护的,无参的show2()"); } void show3() { System.out.println("调用了:默认的,无参的show3()"); } private String show4(int age) { System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age); return "abcd"; } private static int display(String print){ System.out.println(print); return 1; } } public class ReflectMethods { public static void main(String[] args) { /** * 和获取构造方法并调用一样,只不过newInstance()方法换成了invoke() * 调用方法也需要知道调用哪个对象的方法,所以第一个参数是对象,后面是参数列表 */ try { //得到Class对象 Class studentClass = Class.forName("test_reflection.Student2"); //获得一个特定方法,第一个参数是方法名,后面是参数列表的类型 Method privateMethod = studentClass.getDeclaredMethod("show4", int.class); //调用对象的方法,首先得有个对象,除非是静态方法,所以先用反射实例化一个对象 Object obj = studentClass.getDeclaredConstructor(null).newInstance(null); privateMethod.setAccessible(true); //调用方法并获得返回值 String result = (String) privateMethod.invoke(obj, 13); System.out.println(result); //调用静态方法 Method staticMethod = studentClass.getDeclaredMethod("display", String.class); staticMethod.setAccessible(true); int ret = (Integer) staticMethod.invoke(null, "123"); System.out.println(ret); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
打印:
调用了,私有的,并且有返回值的,int参数的show4(): age = 13
abcd
123
1可以看到,如果调用静态方法的话,像使用静态成员变量一样不需要传入对象,可以直接调用。
反射的应用
应用一:通过配置文件配置反射类的信息
配置文件:pro.txt
className = test_reflection.Animal methodName = print
源文件:
package test_reflection; import java.io.FileReader; import java.io.IOException; import java.util.Properties; class Animal{ public void print(){ System.out.println("print"); } } public class UseReflectionByConfigFile { public static void main(String[] args) { try { Class clazz = Class.forName(getValue("className")); Object obj = clazz.getDeclaredConstructor(null).newInstance(null); clazz.getDeclaredMethod(getValue("methodName"), null).invoke(obj, null); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } //此方法接收一个key,在配置文件中获取相应的value public static String getValue(String key) throws IOException{ Properties pro = new Properties();//获取配置文件的对象 FileReader in = new FileReader("src/test_reflection/pro.txt");//获取输入流 pro.load(in);//将流加载到配置文件对象中 in.close(); return pro.getProperty(key);//返回根据key获取的value值 } }
将类的信息都保存在配置文件中,包括类名,成员方法名,字段名,等等。现在需要修改方法名,只需要在配置文件中修改完后,其他地方可以继续引用配置文件里的信息,通过引入配置文件,降低了耦合度,程序可扩展性增强。现在有需求,修改方法名为print1。
源文件:
class Animal{ public void print1(){ System.out.println("print1"); } }
如果没有引入配置文件,那么反射所有用到print方法的地方全部都要修改成print1,但是有配置文件之后就只用修改配置文件中的信息:
className = test_reflection.Animal methodName = print1
所有引用配置信息的代码完全不用改动。
应用二:通过反射越过泛型检查
泛型:参数化类型。
参数化类型究竟是什么意思,个人理解是通过类型参数来在编译阶段限定你所想要的类型。
对泛型有一定了解的朋友应该都知道泛型擦除,泛型在编译阶段会有语法检查,而在编译过后的字节码文件中,泛型就不存在了,是什么类还是什么类,跟泛型参数没任何关系。所以可以这么理解,泛型其实就是一种语法检查机制,为了统一集合类型而存在的安全机制。
在前面我们提到了,反射作用于java运行时,也就是生成字节码文件之后。因为泛型在编译生成字节码文件后会被擦除,所以可以利用这一点使用反射越过泛型的语法检查。
public class ReflectionAndGeneric { public static void main(String[] args) { List<Integer> strings = new ArrayList<Integer>(); strings.add(123); strings.add(456); System.out.println(strings); Class listClass = strings.getClass(); try { Method listAddMethod = listClass.getDeclaredMethod("add", Object.class); listAddMethod.invoke(strings, "你好~"); System.out.println(strings); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
打印结果:
[123, 456]
[123, 456, 你好~]两者相互证明:泛型在编译后擦除,反射是java运行时的。
参考资料:https://blog.youkuaiyun.com/sinat_38259539/article/details/71799078