反射

概念

Java反射机制是在运行状态中

  • 对于任意一个,都能够知道这个类的所有属性方法
  • 对于任意一个对象,都能够调用这个对象的所有属性方法

这种动态获取信息以及动态调用对象的功能称为Java语言的反射机制。即,通过反射,该类对我们来说是完全透明的,可以获取任何有关它的信息。

使用反射的意义

  • 增加程序的灵活性,避免将程序写死到代码里。
    • 定义了一个接口,实现这个接口的类有20个,程序里用到了这个实现类的地方有很多地方。如果不使用配置文件,而是手写的话,但需要修改代码时,代码的改动量很大,因为每个地方都要改而且不容易定位。
    • 但是,如果在编写之前先将接口与实现类写在配置文件里,下次只需改配置文件,利用反射(Java API已经封装好了,直接用就可以用Class.newInstance())就可完成。
  • 代码简洁,提高代码的复用率,外部调用方便。
  • 对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法,获取其属性。

实例:

package reflection.testdemo;
 
interface Fruit { // 水果接口
  public void eat() ; // 吃水果
}
 
class Apple implements Fruit{ // 定义苹果
  public void eat() {
    System.out.println("吃苹果。"); 
  } 
}
 
class Orange implements Fruit{
  public void eat() {
    System.out.println("吃橘子。"); 
  }
}
 
class Factory{
  public static Fruit getInstance(String className){
    Fruit fruit = null ;
    try{
      fruit = (Fruit) Class.forName(className).newInstance() ;
    }catch(Exception e ){
      e.printStackTrace() ;
    }
    return fruit ;
  }
}
 
public class FactoryDemo{
  public static void main(String args[]){
  // 通过工厂类取得接口实例,传入完整的包.类名称
    Fruit fruit = Factory.getInstance("reflection.testdemo.Apple") ;
    if(fruit != null){ // 判断是否取得接口实例
      fruit.eat() ;
    }
  }
}

如果不用反射,那么如果再加一个西瓜类,就得在Factory里判断,每添加一个类都要修改一次Factory。但用了反射,只用在调用的时候传入完整的类名就可完成
结果:用反射,修改一处代码;不用反射,修改两处代码。

动态获取类的信息

Java有3种方法可以获取Class信息:

  • 类名.class 获取类信息(静态)
    • 直接通过一个的静态变量class获取:Class cls = String.class;
  • Class.forName("类名") 获取类信息 (动态)
    • 如果知道一个Class类的完整类名,可以通过静态方法Class.forName()获取:Class cls = Class.forName("类名");
  • obj.getClass() 获取类信息。运行期间,通过当前对象获取类信息
    • 如果有一个实例变量,可以通过该实例变量提供的getClass()方法获取:String s = "Hello"; Class cls = s.getClass();

获得Class后,可以调用具体的方法:

  • 通过Method[] all = cls.getDeclaredMethods()动态获取类的方法信息;
  • 通过Field[] all = cls.getDeclaredFields()动态获取类声明的属性信息;
  • 通过Constructor[] constructors = cls.getDeclaredConstructors()动态获取类的构造器信息;

动态获取类的方法信息并调用类的方法

动态获取类的方法信息

获取方法:

// 动态加载类
Class cls = Class.forName(className);
// 动态获取类的方法信息
Method[] all = cls.getDeclaredMethods();

实例:

package class_information;

import java.lang.reflect.Method;

/**
 * 动态获取类的方法信息
 * getDeclaredMethods
 *
 * 类的全名:class_information.Foo
 * 编译后:class_information.Foo.class
 * @author chenzufeng
 */
public class Foo {
    public int test() {
        return 5;
    }

    public double test1() {
        return 5d;
    }

    public static void main(String[] args) {
        getClassMethod();
    }

    /**
     * 动态的加载类信息到方法区,并且返回对应的Class对象!
     * Class对象可以访问类的全部信息!
     *
     * 将className对应的类文件,从磁盘中加载到内存方法区,返回这个类的信息
     */
    public static void getClassMethod() {
        String className = "class_information.Foo";
        try {
            // 动态加载类
            Class cls = Class.forName(className);
            // 动态获取类的方法信息
            Method[] all = cls.getDeclaredMethods();
            for (Method method : all) {
                System.out.println(method.getName());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

main
test
getClassMethod
test1

动态调用类的方法

实例:

package class_method;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 动态调用类的方法
 * @author chenzufeng
 */
public class InvokeClassMethod {
    public static void main(String[] args) throws
            NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        List<String> list = new ArrayList<>();
        list.add("Tom");
        list.add("Jack");

        // 1. 动态获取类信息
        Class cls = list.getClass();

        // 2. 通过[方法名]和[参数类型]找到对应的方法:remove
        Method method = cls.getDeclaredMethod("remove", new Class[] {int.class});

        // 3. 调用方法,传递对象和具体参数
        Object value = method.invoke(list, new Object[] {0});

        System.out.println(value);  // Tom
    }
}

动态获取类的属性信息和属性值

动态获取类的属性信息

获取方法:

// 动态加载类
Class cls = Class.forName(className);
// 动态获取类声明的属性信息
Field[] all = cls.getDeclaredFields();

实例:

package class_information;

import java.lang.reflect.Field;

/**
 * 动态获取类的属性信息
 *
 * @author chenzufeng
 */
public class Eoo {
    int id;
    String name;
    double salary;

    public Eoo() {}

    public Eoo(int id, String name, double salary) {
        super();
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

    /**
     * 动态获取一个类的全部属性信息
     * 1. 动态加载一个类到方法区
     * 2. 动态获取类的属性信息
     */
    public static void getClassFields() throws ClassNotFoundException {
        String className = "class_information.Eoo";
        // 动态加载类
        Class cls = Class.forName(className);
        // 动态获取类声明的属性信息
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            // getName获取属性的名字
            System.out.println(field.getName());
        }
    }

    public static void main(String[] args) throws ClassNotFoundException {
        getClassFields();
    }
}

输出结果:

id
name
salary

动态获取类的属性值

实现过程(利用反射API实现动态属性访问):

  • 找到对象的类型信息(方法区)
  • 在信息中找属性信息(Field)
  • 在对象上获取属性的值!

实例:

package attribute_value;

import java.lang.reflect.Field;

/**
 * 动态获取类的属性值
 * @author chenzufeng
 */
public class Goo {
    public int id;
    public String name;

    public Goo() {}

    public Goo(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }

    /**
     * 获取obj对象的 fieldName对应属性的值
     * @param object 对象
     * @param filedName 属性
     * @return 属性值
     */
    public Object getAttributeValue(Object object, String filedName) {
        try {
            /*
             * 1. 获取类信息:找到对象的类型信息
             * Java有3种方法可以获取Class信息
             *   a) 类名.class获取类信息(静态)
             *   b) Class.forName("类名") 获取类信息 (动态)
             *   c) obj.getClass()获取类信息。运行期间,通过当前对象获取类信息
             */
            Class cls = object.getClass();

            /*
             * 2. 找到属性:在信息中找属性信息
             *   getDeclaredField按照属性名在cls中查找类信息。当属性没有找到时候,抛出异常!
             */
            Field field = cls.getDeclaredField(filedName);

            /*
             * 3. 在对象上获取属性的值
             *   get方法:在一个对象上获取属性的值,对象上没有对应的属性,抛出异常
             */
            Object value = field.get(object);

            return value;

        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

        return null;
    }

    public static void testGetAttributeValue() {
        Goo goo = new Goo(1, "Tom");
        Object id = goo.getAttributeValue(goo, "id");
        Object name = goo.getAttributeValue(goo, "name");
        System.out.println(id + " " + name);
    }

    public static void main(String[] args) {
        testGetAttributeValue(); // 1 Tom
    }
}

动态获取类的构造器信息

获取方法:

// 动态加载类
Class cls = Class.forName(className);
// 动态获取类的构造器信息
Constructor[] constructors = cls.getDeclaredConstructors();

实例:

/**
 * 动态获取类的构造器信息
 */
public static void getAllConstructors() throws ClassNotFoundException {
    String className = "class_information.Eoo";
    // 动态加载类
    Class cls = Class.forName(className);
	 // 动态获取类的构造器信息
    Constructor[] constructors = cls.getDeclaredConstructors();
    for (Constructor constructor : constructors) {
        System.out.print(constructor.getName());
        /*
         * 获取构造器的参数类型列表
         * Parameter参数;Type类型
         * Class[] 代表所有参数的类型列表
         */
        Class[] types = constructor.getParameterTypes();
        System.out.println(Arrays.toString(types));
    }
}

输出结果:

class_information.Eoo[]
class_information.Eoo[int, class java.lang.String, double]

动态创建对象

调用无参构造器创建对象

// 动态加载类会遇见必检异常:java.lang.ClassNotFoundException
Class cls = Class.forName(className);
// cls.newInstance()调用无参数构造器创建对象
Object obj = cls.newInstance();
  • 如果没有显式地提供无参数构造器,将发生异常!
  • Class提供了方法newInstance()。

实例:

public class Date {

    public Date() {
        this(System.currentTimeMillis());
    }

    public Date(long date) {
        fastTime = date;
    }
}
package create_object;

import java.util.Date;

/**
 * 调用无参构造器创建对象
 * @author chenzufeng
 */
public class UseConstructorWithoutParameter {
    public static void main(String[] args) throws Exception {
        createObject();
    }

    /**
     * 动态调用无参数构造器创建对象
     * 1.动态加载类
     * 2.利用class的方法newInstance创建对象
     *
     * 注意:对象所属类必须有无参数构造器,否则出异常
     */
    public static void createObject() throws
            ClassNotFoundException, IllegalAccessException, InstantiationException {

        String className = "java.util.Date";
        Class cls = Class.forName(className);

        // cls.newInstance()调用无参数构造器创建对象
        Object object = cls.newInstance();

        System.out.println("动态的创建对象:");
        System.out.println(object);

        // 静态的创建对象!编译已经就固定了!
        Date date = new Date();
        System.out.println("静态的创建对象:");
        System.out.println(date);
    }
}

输出结果:

动态的创建对象:
Sun Feb 07 21:18:50 CST 2021
静态的创建对象:
Sun Feb 07 21:18:50 CST 2021

调用有参构造器创建对象

// 动态加载类
Class cls = Class.forName(className);
// 动态获取指定参数类型的构造器
Constructor constructor = cls.getDeclaredConstructor(paramTypes);
// 执行构造器constructor.newInstance()方法,创建对象
Object obj = constructor.newInstance(params);

实例:

package create_object;


import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 调用有参构造器创建对象
 * @author chenzufeng
 */
public class UseConstructorWithParameter {

    public static void main(String[] args) {
        testCreateObject();
    }

    /**
     * 调用有参构造器创建对象:className + paramTypes共同决定调用哪个构造器!
     * @param className 调用className对应类的有参数构造器
     * @param paramTypes 代表对应构造器的参数列表
     * @param params 执行构造器还需要具体的参数params,为构造器参数列表赋值
     * @return 创建的对象
     */
    public static Object createObject(String className, Class[] paramTypes, Object[] params) {
        try {
            // 1.动态加载类
            Class cls = Class.forName(className);
            // 2.动态获取指定参数类型的构造器
            Constructor constructor = cls.getDeclaredConstructor(paramTypes);
            // 3.执行构造器newInstance()方法,创建对象
            Object object = constructor.newInstance(params);

            return object;

        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 调用有参构造器创建对象
     * new Date(long date)
     * new String("Hello")
     * new String(byte[],"utf-8");
     */
    public static void testCreateObject() {
        /*
         * 动态调用 new Date(long date);
         */
        String className = "java.util.Date";
        // paramTypes类型列表:Class类型的数组
        Class[] paramTypes = {long.class};
        // 实际参数列表:params
        Object[] params = {1000L*60*60*24*365};

        Object object = createObject(className, paramTypes, params);
        System.out.println(object);

        /*
         * 动态调用 new String("Hello");
         * {} 只能用在声明变量时候直接初始化,不能用于赋值语句!
         * 赋值语句可以使用 new Object[]{"Hello"}
         */
        className = "java.lang.String";
        paramTypes = new Class[] {String.class};
        params = new Object[] {"Hello"};

        object = createObject(className, paramTypes, params);
        System.out.println(object);

        /*
         * 动态调用 new String(byte[],"utf-8");
         */
        object = createObject("java.lang.String",
                new Class[] {byte[].class, String.class},
                new Object[] {new byte[] {65, 66, 67, 68}, "UTF-8"});
        System.out.println(object);
    }
}

输出结果:

Fri Jan 01 08:00:00 CST 1971
Hello
ABCD

参考资料

1.Java反射机制
2.Java——反射的意义及优缺点

上一篇:工厂模式,单例模式


下一篇:python 单例模式