Java反射机制 详解

一、什么是反射机制

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方法完成。

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值