Java反射机制 详解

来源:互联网 发布:蒙古舞演出服淘宝 编辑:程序博客网 时间:2024/06/02 02:18

一、什么是反射机制

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 appleeat 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) throwsNoSuchMethodException, 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方法完成。

1 0
原创粉丝点击