前言
这篇文章鸽了好久,拖稿了好几天,趁着有精力的时候多写写文章。Java语言一直被我认为是常见语言中最难的语言,主要是概念繁多,然后就是JVM的知识点复杂,导致尽管有些人会写代码,但是问到某个知识点却不知道为何是这样。
今天谈谈Java中的反射机制。什么是反射呢?反射就是在Java编译期以外的时间可以动态的加载一个类。还可以实例化对象,获取变量,方法等。
实践
入门
上面的概念刚开始我也不是很懂,为什么Java会有反射这种机制呢?假设有以下代码:
JVM编译时会把代码编译成class文件加载到jvm的内存中,以上代码会使Cat.java文件编译成Cat.class然后加载到JVM的内存当中,然后执行new申请内存空间,然后返回实例。这种方法只适合已经知道需要什么对象,而如果在运行的时候才确定了对象,此时已经错过了编译期,所以需要动态地加载一个类,这样的操作我们称作反射。
那么反射有什么优点与缺点呢?优点就是十分灵活,类的加载无需重新编译,这种思想广泛用在了java生态当中,常见的框架都采用了反射的思想,如spring等。
而缺点就是影响性能,不建议使用在普通代码中,并且会使代码逻辑变得模糊,不利于维护。还有一点就是由于反射的功能强大,可以执行一些不被允许的操作(例如获取到类中的私有属性与方法),从而会引发某些问题。
Class类
在继续理解前,我们需要了解一下Java中的Class类。Class类是反射的基础。在Java中用来表示运行时类型信息的对应类就是Class类,存在于JDK的java.lang包中。手动编写一个类后,会产生一个Class对象,保存在同名的.class文件中。而这个类无论有多少个实例,内存中只有一个与之对应的Class对象。查看Class类的源码:
1 2 3
| private Class(ClassLoader loader) { classLoader = loader; }
|
我们发现构造器为私有的,只有JVM能够创建Class对象,那么如何获得这个类的Class对象呢?有三种方法:
1 2 3 4 5 6
| Class c1 = Cat.class;
Class c2 = cat.getClass();
Class c3 = Class.forName("Cat");
|
而通过Class对象获得类的信息的方法我们就称作反射(Reflection)。
反射的相关操作
获取成员方法
说了这么多,反射具体能做些啥呢?其实具体就以下几种:
这里我统一使用Class.forName()获取Class对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class Cat { public int id; private String name; public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
public int getId() { return id; }
public String getName() { return name; }
private void hello(){ System.out.println(this.name); } }
public class Main { public static void main(String[] args) throws Exception{ Class cls = Class.forName("model.Cat"); Object o = cls.newInstance(); Method method = cls.getMethod("setName", String.class); method.invoke(o,"波斯猫"); System.out.println(cls.getMethod("getName").invoke(o));
} }
|
首先使用Class.forName 获得Class对象,接着使用newInstance生成Cat的实例对象(之后会讲构造器的newInstance和直接的newInstance的区别),
使用getMethod可以获取某个方法,接着通过invoke方法传入对象与参数。
如果使用getMethods则可以获取所有方法。但是getMethod和getMethods获取的是public方法,如果需要获得私有方法,则使用getDeclaredMethod方法。
1 2 3
| Method hello = cls.getDeclaredMethod("hello"); hello.setAccessible(true); hello.invoke(o);
|
获取成员属性
这里我们只写main方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void main(String[] args) throws Exception{
Class cls = Class.forName("model.Cat"); Object o = cls.newInstance(); Method method1 = cls.getMethod("setId", int.class); method1.invoke(o,1); Method method2 = cls.getMethod("setName", String.class); method2.invoke(o,"哈士奇");
Field field = cls.getField("id"); System.out.println("id: " + field.get(o));
Field field1 = cls.getDeclaredField("name"); field1.setAccessible(true); System.out.println("name: " + field1.get(o)); }
|
获取构造函数
反射还可以获取类的构造函数。在这之前,有一个之前说的问题,Class的newInstance和构造器的newInstance有什么差。其实他们的区别在于Class的newInstance方法只能创建无参数构造函数的类的实例,而构造器的newInstance方法则可以创建带有参数的类的实例。
我们另写两个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public class Dog { private int id; private String name;
public Dog(int id){ this.id = id; }
public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
public int getId() { return id; }
public String getName() { return name; } } public class Bird { private int id; private String name;
private Bird(int id){ this.id = id; }
public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
public int getId() { return id; }
public String getName() { return name; } }
|
这两个类区别在于构造器分别是public和private。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Main {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("model.Dog"); Constructor constructor = cls.getConstructor(int.class); Object o = constructor.newInstance(100); Method method = cls.getMethod("getId"); System.out.println(method.invoke(o));
Class cls1 = Class.forName("model.Bird"); Constructor constructor1 = cls1.getDeclaredConstructor(int.class); constructor1.setAccessible(true); Object o1 = constructor1.newInstance(101); System.out.println(cls1.getMethod("getId").invoke(o1)); } }
|
获取构造器分别使用getConstructor与getDeclaredConstructor来获取公有和私有的构造器,而构造器的newInstance方法则是用来创建带参数的类的实例。
总结
这篇文章差不多讲了Java反射的入门知识,学习了Class类与反射的基本知识。不同于new,反射可以在需要的时候动态加载类。尽管在开发中反射并不常见,但了解反射的思想有助于对框架源码的解读。
参考
http://tengj.top/2016/04/28/javareflect/
https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html