一、什么是反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。Java不是动态语言。但是Java有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
二、初识Java反射机制
反射之中包含了一个“反”的概念,要解释反射就必须先从“正”开始解释,一般而言,一定是先有类再产生实例化对象。如下:
package com.wz.reflectdemo;
import java.util.Date;//先有类
public class ReflectTest {
public static void main(String[] args) {
Date date = new Date();//再产对象
System.out.println(date);
}
}
而所谓的“反”,是通过对象找到类。在Object类里面提供有一个方法,
取得class对象:
public final Class<?> getClass()
注:反射之中的所有泛型都定义为?,返回值都是Object。
实例如下:
package com.wz.reflectdemo;
import java.util.Date;//先有类
public class ReflectTest {
public static void main(String[] args) {
Date date = new Date();//再产对象
System.out.println(date.getClass());
}
}
执行结果:
class java.util.Date
我们发现,调用getClass()后,得到了类的完整名称。也就找到了对象的出处。
三、Class类对象实例化
java.lang.Class是一个类,它和一般类一样继承自Objec。这个类是反射操作的源头,即所有的反射都要从此类开始进行,这个类有三种实例化方式:
方式一:调用Object类的getClass()方法(很少用到):
package com.wz.reflectdemo;
import java.util.Date;
public class ReflectTest {
public static void main(String[] args) {
Date date = new Date();
Class<?> cls = date.getClass();
System.out.println(cls);
}
}
运行结果:
class java.util.Date
方式二:使用“类.class”取得:
package com.wz.reflectdemo;
import java.util.Date;
public class ReflectTest {
public static void main(String[] args) {
//Date date = new Date();
Class<?> cls = Date.class;
System.out.println(cls);
}
}
运行结果:
class java.util.Date
注意:先前取得Class类对象之前需要实例化,但此时并没有进行实例化。
方式三:调用Class类提供的一个方法:
public static Class<?> forName(String className) throws ClassNotFoundException
实例如下:
package com.wz.reflectdemo;
//import java.util.Date;
public class ReflectTest {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("java.util.Date");
System.out.println(cls);
}
}
运行结果:
class java.util.Date
此时,无需使用import语句导入一个明确的类,而类名称是采用字符串的形式进行描述的。
四、反射实例化对象
一般情况下,对象的实例化操作需要依靠构造方法和关键字new完成。可是有了Class类对象之后,可以利用反射来实现对象的实例化。
通过反射实例化对象:
public T newInstance() throws InstantiationException, IllegalAccessException
实例:
package com.wz.reflectdemo;
class Book{
public Book(){
System.out.println("Book类的无参构造方法");
}
@Override
public String toString() {
return "This a book !";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.wz.reflectdemo.Book");
Object obj = cls.newInstance();//相当于使用new调用无参构造实例化对象
Book book = (Book)obj;
System.out.println(book);
}
}
运行结果:
Book类的无参构造方法
This a book !
如上,有了反射之后,进行实例化的操作不再只有依靠关键字new来完成了。但这个操作要比之前使用的new复杂一些,并且并不表示用new来进行实例化被完全取代了。为什么呢?
对于程序的开发一直强调:尽量减少耦合。而减少耦合的最好做法是使用接口,但是就算使用了接口也逃不出关键字new,所以实际上new是造成耦合的关键元凶。
先看一个简单的工厂设计模式:
package com.wz.reflectdemo;
interface Fruit{
public void eat();
}
class Apple implements Fruit{
@Override
public void eat(){
System.out.println("eat apple");
}
}
class Factory{
public static Fruit getInstance(String className){
if("apple".equals(className)){
return new Apple();
}
return null;
}
}
public class FactoryDemo{
public static void main(String[] args){
Fruit f = Factory.getInstance("apple");
f.eat();
}
}
运行结果:
eat apple
以上是一个简单的工厂设计模式,但是在这个工厂设计模式之中有一个问题:如果增加了Fruit接口子类,那么就需要修改工厂类。
增加了Fruit接口子类Orange :
class Orange implements Fruit {
public void eat() {
System.out.println("eat orange");
};
}
需要修改工厂类:
class Factory{
public static Fruit getInstance(String className){
if("apple".equals(className)){
return new Apple();
}else if("orange".equals(className)){
return new Orange();
}
return null;
}
}
问题来了,每增加一个接口子类,就需要去修改工厂类,那么若随时可能增加多个子类呢?那么就要一直对工厂类进行修改!
根本原因:工厂类中的对象都是通过关键字new直接实例化的。那么如果说现在不使用关键字new了,变为了反射机制呢?
反射机制实例化对象的时候实际上只需要“包.类”就可以,于是根据此操作,修改工厂设计模式如下:
package com.wz.reflectdemo;
interface Fruit{
public void eat();
}
class Apple implements Fruit{
@Override
public void eat(){
System.out.println("eat apple");
}
}
class Orange implements Fruit{
@Override
public void eat(){
System.out.println("eat orange");
}
}
class Factory{
public static Fruit getInstance(String className){
/*if("apple".equals(className)){
return new Apple();
}else if("orange".equals(className)){
return new Orange();
}
return null;*/
Fruit f = null;
try {
f = (Fruit)Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
public class FactoryDemo{
public static void main(String[] args){
/*Fruit f = Factory.getInstance("apple");
f.eat();
Fruit f1 = Factory.getInstance("orange");
f1.eat();*/
Fruit f1= Factory.getInstance("com.wz.reflectdemo.Apple");
f1.eat();
Fruit f2 = Factory.getInstance("com.wz.reflectdemo.Orange");
f2.eat();
}
}
运行结果:
eat apple
eat orange
这个时候即使增加了接口的子类,工厂类照样可以完成对象的实例化操作,这个才是真正的工厂类,可以应对于所有的变化。这就完成了解耦合的目的,而且扩展性非常强。
五、反射调用构造方法
之前我们通过反射实例化对象都是这么写的:
Class<?> cls = Class.forName(“*****className*****”);
Object obj = cls.newInstance();
这只能调用默认的无参构造方法,那么,问题来了:若类中不提供无参构造方法呢?怎么解决?
看一个范例:
先写一个Book类:
package com.wz.reflectdemo;
public class Book {
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
}
@Override
public String toString() {
return "图书名称:"+this.title + " ,价格:"+this.price;
}
}
然后实例化对象:
package com.wz.reflectdemo;
public class ReflectTest {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.wz.reflectdemo.Book");
Object obj = cls.newInstance();//相当于使用new调用无参构造实例化对象
Book book = (Book)obj;
System.out.println(book);
}
}
执行结果:
Exception in thread "main" java.lang.InstantiationException: com.wz.reflectdemo.Book
at java.lang.Class.newInstance(Unknown Source)
at com.wz.reflectdemo.ReflectTest.main(ReflectTest.java:8)
Caused by: java.lang.NoSuchMethodException: com.wz.reflectdemo.Book.<init>()
at java.lang.Class.getConstructor0(Unknown Source)
... 2 more
由此可见,由于此时Book类没有提供无参构造方法,而cls.newInstance()的时候又调用了无参构造方法,所以无法进行对象实例化。那么,怎么解决?只能明确的调用有参构造方法。
在Class类里面,提供了方法来取得构造:
(1)取得全部构造:
public Constructor<?>[] getConstructors() throws SecurityException
(2)取得一个指定参数顺序的构造:
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
以上两个方法返回的都是”java.lang.reflect.Constructor”类的对象。在这个类中提供有一个明确传递有参构造内容的实例化对象方法:
public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
改写上面范例的实例化对象方法,明确调用有参构造方法:
package com.wz.reflectdemo;
import java.lang.reflect.Constructor;
public class ReflectTest {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.wz.reflectdemo.Book");
/*Object obj = cls.newInstance();//相当于使用new调用无参构造实例化对象
Book book = (Book)obj;
System.out.println(book);*/
Constructor<?> con = cls.getConstructor(String.class,double.class);
Object obj = con.newInstance("Java开发",79.8);//实例化对象
System.out.println(obj);
}
}
执行结果:
图书名称:Java开发 ,价格:79.8
很明显,调用无参构造方法实例化对象要比调用有参构造的更加简单、方便。so,简单的Java开发中不过提供有多少个构造方法,请至少保留有无参构造。
六、反射调用普通方法
我们都知道:类中的普通方法只有在一个类产生实例化对象之后才可以调用。
先看一个例子。我们先定义一个类:
package com.wz.reflectdemo;
public class Book {
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
这个类有无参构造方法,所有实例化对象的时候可以直接利用Class类提供的newInstance()方法。
在Class类里面提供以下取得类在Method的操作:
(1)取得一个类中的全部方法:
public Method[] getMethods() throws SecurityException
(2)取得指定方法:
public Method getMethod(String name, Class<?>... parameterTypes) throws
NoSuchMethodException, SecurityException
以上的方法返回的都是”java.lang.reflect.Method”类的对象,在这个类中有一个调用方法:
public Object invoke(Object obj, Object... args) throws IllegalAccessException,
IllegalArgumentException, InvocationTargetException
我们接着上面的例子来看反射调用方法:
package com.wz.reflectdemo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectTest {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.wz.reflectdemo.Book");
Object obj = cls.newInstance();
Method setMet = cls.getMethod("setTitle", String.class);
setMet.invoke(obj, "Java开发");//等价于Book类的setTitle("Java开发")
Method getMet = cls.getMethod("getTitle");
System.out.println(getMet.invoke(obj));//等价于Book类的getTitle()
}
}
执行结果:
Java开发
此时,我们完全看不见具体的操作类型,也就是说,利用反射可以实现任意类的指定方法的调用。
七、反射调用成员
我们都知道,类中的属性一定要在本类实例化对象之后才可以分配内存空间。在Class类里面提供有取得成员的方法:
(1)取得全部成员:
public Field[] getDeclaredFields() throws SecurityException
(2)取得指定成员:
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException
这两个方法的返回值类型是”java.lang.reflect.Field”类的对象。在这个类里面有两个重要的方法:
(1)取得属性内容(类似于:对象.属性):
public Object get(Object obj)
throws IllegalArgumentException, IllegalAccessException
(2)设置属性内容(类似于:对象.属性=内容):
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException
接着看一个例子:
先定义一个类:
package com.wz.reflectdemo;
public class Book {
private String title;
}
这个类只定义了一个私有属性,按照之前的做法,它一定无法被外部所使用。但是,可以反射调用:
package com.wz.reflectdemo;
import java.lang.reflect.Field;
public class ReflectTest {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.wz.reflectdemo.Book");
Object obj = cls.newInstance();
Field titleField = cls.getDeclaredField("title");
titleField.setAccessible(true);//解除封装
titleField.set(obj, "Java开发");//相当于Book类对象.title = "Java开发"
System.out.println(titleField.get(obj));//相当于Book类对象的.title
}
}
执行结果:
Java开发
注:构造方法和普通方法一样可以解除封装,只是很少这么去做。而对于属性的访问还是建议使用setter和getter方法完成。