首先,从动态语言讲起。像Python、Ruby这种语言,只要修改了代码,修改的效果立即生效,因为这种语言是无需编译,直接执行代码的,我们称这类语言是“动态语言”。而C++、Java这种,在运行之前需要先编译,如果中途修改了代码不重新编译去执行的话就没有变化。但是,Java有一个非常突出的动态相关机制,即反射:我们可以于运行时(区别于编译时)加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class(在这之前修改这个类即时不编译都有效),获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
再通俗地说一下什么是反射?
普通的Java对象是通过new关键字把对应类的字节码文件加载到内存,然后创建该对象的。
反射是通过一个名为Class的特殊类,用Class.forName(“类名”);得到类的字节码对象,然后用newInstance()方法在虚拟机内部构造这个对象(针对无参构造函数)。
也就是说反射机制让我们可以在程序运行时动态地拿到Java类对应的字节码对象(而不是在编译的时候),然后动态的进行任何可能的操作。反射的功能主要包括:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法(这样就可以修改这个对象的属性)
- 生成动态代理
使用反射的主要作用是方便程序的扩展,由于其运行时动态加载的特性。
Class类
Java中只有2种东西不是面向对象的,一个基本类型,一个静态成员(方法、变量、常量)。我们提供的每一个类也是对象,一个类的类类型是java.lang.Class类的实例对象。
1 | // 创建类Foo的实例对象 |
Class类的构造器是私有的,只能JVM能创建Class类的实例对象。
可以通过类类型 (上面的c1 c2 c3)创建Foo类的实例对象:
1 | Foo foo = (Foo)c1.newInstance(); |
动态加载类
什么是动态加载?什么是静态加载?
静态加载的类在编译的时候就要提供,而动态加载的类在源程序编译时可以缺席。区分编译时和运行时。
Class.forName(“类名”) 这种方式,不仅表示了类的类类型,还代表了动态加载类。
用new这种方式静态加载方式,编译的时候,如果new的对象的那个类不存在的话,编译不通过;但是用Class.forName这种动态加载方式,没有这个类编译的时候不会报任何错,但是运行的时候会因为找不到这个类而报错。动态加载有什么好处呢?配合接口编程,可以实现一个接口对多种实现,从而可以动态地去选择完成不同的功能。因此,类动态加载对扩展功能很有用。
其实,动态类加载主要就是通过反射机制将类对象注入进去。
静态加载:
1 | public class Office_Static { |
动态加载:
1 | public interface OfficeAble { |
1 | public class Word implements OfficeAble { |
1 | public class Excel implements OfficeAble { |
1 | public class OfficeBetter { |
通过反射动态获取对象的方法属性构造器信息
1 | import java.lang.reflect.Method; |
通过反射运行时动态调用对象的方法
用方法对象进行反射操作,运行时动态调用一个对象的方法:
1 | import java.lang.reflect.Method; |
通过反射了解Java泛型的本质
反射的操作都是编译之后的操作,反射动态加载的类是在程序运行时编译并加载的。来看看下面这个例子。
1 | import java.lang.reflect.Method; |