Java反射机制

Java基础

浏览数:79

2019-6-7

什么是Java的反射机制?

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

简单来说,反射就是可以在程序运行的时候动态装载类,查看类的信息,生成对象,或操作生成的对象。

Java反射机制相关API

Class类介绍

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。JVM 中有 N 多的实例,每个类的实例都有 Class 对象。(包括基本数据类型)

Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM 已经帮我们创建好了。

Java 类的加载过程:

在 Java 中,类装载器把一个类装入 Java 虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步:

  • 装载:查找和导入类或接口的二进制数据;
  • 链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
  • 校验:检查导入类或接口的二进制数据的正确性;
  • 准备:给类的静态变量分配并初始化存储空间;
  • 解析:将符号引用转成直接引用;
  • 初始化:激活类的静态变量的初始化 Java 代码和静态 Java 代码块。

如果知道一个实例,那么可以通过实例的getClass()方法获得运行实例的 Class(该类型的字节码文件对象),如果你知道一个类型,那么你也可以使用.class的方法获得运行实例的 Class。

部分相关api如下所示

//返回与给定字符串名称的类或接口相关联的类对象。
public static Class <?> forName(String className)throws ClassNotFoundException

//返回类表示此所表示的实体(类,接口,基本类型或void)的超类类 。
public Class<? super T> getSuperclass()

//确定由该对象表示的类或接口实现的接口。 
public Class< ? >[] getInterfaces()

//创建此 Class 对象所表示的类的一个新实例
public T newInstance() throws InstantiationException, IllegalAccessException

//返回表示此类公共构造方法的 Constructor 对象数组
public Constructor< ? >[] getConstructors() throws SecurityException

//返回 Constructor 对象的一个数组
public Constructor<?>[] getDeclaredConstructors() throws SecurityException

//返回表示公共字段的 Field 对象的数组
public Field[] getFields()  throws SecurityException

//返回表示此类所有已声明字段的 Field 对象的数组
public Field[] getDeclaredFields() throws SecurityException

//表示此类中公共方法的 Method 对象的数组
public Method[] getMethods() throws SecurityException

//表示此类所有声明方法的 Method 对象的数组
public Method[] getDeclaredMethods()  throws SecurityException

//返回该类的类加载器。
public ClassLoader getClassLoader()

//返回:使用参数 args 在 obj 上指派该对象所表示方法的结果
public Object invoke(Object obj, Object... args)  throws IllegalAccessException, IllegalArgumentException,  InvocationTargetException

//查找具有给定名称的资源。
public InputStream getResourceAsStream(String name)

//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

具体相关方法及其使用方法可以查看API文档

Java 反射机制举例

以下例子基于Person类

package DateTest;

public class Person implements Comparable{
    public String name;
    private int age;
    public int id;
    protected String phone;
    public Person() {
        System.out.println("默认的无参构造方法执行了");
    }
    
    public Person(String name) {
        System.out.println("姓名:"+name);
    }
    
    public Person(String name,int age) {
        System.out.println("姓名:"+name+"年龄:"+age);
    }
    
    //受保护的构造方法
    protected Person(boolean b) {
        System.out.println("这是一个受保护的构造方法:b="+b);
    }
    
    //私有构造方法
    private Person(int age) {
        System.out.println("这是一个私有构造方法,年龄="+age);
    }

    @Override
    public int compareTo(Object arg0) {
        // TODO Auto-generated method stub
        return 0;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

通过一个对象获取某个类的完整包名和类名

通过实例的 getClass() 方法获得运行实例的字节码文件对象,然后通过 getName() 方法获得类的完整包名和类名。

public void test1() {
        Person person = new Person();
        System.out.println(person.getClass().getName());
}
//输出结果:
默认的无参构造方法执行了
DateTest.Person

获取 Class 对象

  • 方式1:通过 Class 类的静态方法获取 Class 类对象
  • 方式2:因为所有类都继承 Object 类。因而可通过调用 Object 类中的 getClass 方法来获取
  • 方式3:任何数据类型(包括基本数据类型)都有一个“静态”的 class 属性
public void test2() throws ClassNotFoundException {
        Class<?> class1 = null;
        Class<?> class2 = null;
        Class<?> class3 = null;
        class1 = Class.forName("DateTest.Person");
        class2 = new Person().getClass();
        class3 = Person.class;
        System.out.println("类名称1:"+class1.getName());
        System.out.println("类名称2:"+class2.getName());
        System.out.println("类名称3:"+class3.getName());
}
//输出结果
默认的无参构造方法执行了
类名称1:DateTest.Person
类名称2:DateTest.Person
类名称3:DateTest.Person

获取一个对象的父类和实现的接口

为了测试getInterfaces()方法,此处将Person类继承Comparable接口

public void test3() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("DateTest.Person");
        //获取父类
        Class<?> parentClass = clazz.getSuperclass();
        System.out.println("父类为:"+parentClass.getName());
        //获取所有接口
        Class<?> interf[] = clazz.getInterfaces();
        System.out.println("实现的接口有:");
        for(int i = 0; i < interf.length; i++) {
            System.out.println((i+1)+":"+interf[i].getName());
        }
}
//输出结果
父类为:java.lang.Object
实现的接口有:
1:java.lang.Comparable

获取某个类的全部构造函数并调用私有构造方法

获取 Student 类的全部构造函数,并使用 Class 对象的 newInstance() 方法来创建 Class 对象对应类 Person 的实例来调用私有构造方法。

public void test4() throws Exception {
        //1.加载Class对象
        Class clazz = Class.forName("DateTest.Person");
        //2.获取所有公有构造方法
        System.out.println("所有公有构造方法");
        Constructor[] conArray = clazz.getConstructors();
        for(Constructor c : conArray) {
            System.out.println(c);
        }
        
        System.out.println("----------------------------");
        System.out.println("所有构造方法");
        conArray = clazz.getDeclaredConstructors();
        for(Constructor c : conArray) {
            System.out.println(c);
        }
        
        System.out.println("----------------------------");
        System.out.println("公有、无参的构造方法");
        Constructor con = clazz.getConstructor(null);
        System.out.println("con = " + con);
        //调用构造方法  
        Object obj = con.newInstance();
        System.out.println("obj = " + obj);
        
        System.out.println("----------------------------");
        System.out.println("获取私有的构造方法,并调用");
        con = clazz.getDeclaredConstructor(int.class);
        System.out.println(con);
        //调用构造方法  
        con.setAccessible(true);
        obj = con.newInstance(20);
    }
//输出结果
所有公有构造方法
public DateTest.Person(java.lang.String,int)
public DateTest.Person(java.lang.String)
public DateTest.Person()
----------------------------
所有构造方法
private DateTest.Person(int)
protected DateTest.Person(boolean)
public DateTest.Person(java.lang.String,int)
public DateTest.Person(java.lang.String)
public DateTest.Person()
----------------------------
公有、无参的构造方法
con = public DateTest.Person()
默认的无参构造方法执行了
obj = DateTest.Person@7a4f0f29
----------------------------
获取私有的构造方法,并调用
private DateTest.Person(int)
这是一个私有构造方法,年龄=20

获取某个类的全部属性

获取 Person类的全部属性并调用:

public void test5() throws Exception {
        //1.获取Class对象
        Class pclass = getClass().forName("DateTest.Person");
        //2.获取字段
        System.out.println("获取所有公有字段");
        Field[] fieldArray = pclass.getFields();
        for(Field f : fieldArray) {
            System.out.println(f);
        }
        System.out.println("----------------------------");
        System.out.println("获取所有字段");
        fieldArray = pclass.getDeclaredFields();
        for(Field f : fieldArray) {
            System.out.println(f);
        }
        System.out.println("----------------------------");
        Field f = pclass.getField("name");
        System.out.println(f);
        //获取一个对象
        Object obj = pclass.getConstructor().newInstance();
        f.set(obj,"张三");
        //验证
        Person p = (Person)obj;
        System.out.println("验证的姓名:"+p.getName());
        System.out.println("----------------------------");
        f = pclass.getDeclaredField("age");
        System.out.println(f);
        f.setAccessible(true);
        f.set(obj, 21);
        System.out.println("验证年龄:"+p);
    }
//输出结果
获取所有公有字段
public java.lang.String DateTest.Person.name
public int DateTest.Person.id
----------------------------
获取所有字段
public java.lang.String DateTest.Person.name
private int DateTest.Person.age
public int DateTest.Person.id
protected java.lang.String DateTest.Person.phone
----------------------------
public java.lang.String DateTest.Person.name
默认的无参构造方法执行了
验证的姓名:张三
----------------------------
private int DateTest.Person.age
验证年龄:Person [name=张三, age=21]

获取某个类的全部方法

public void test6() throws Exception {
        //1.获取Class对象
        Class pClass = Class.forName("DateTest.Person");
        //2.获取所有公有方法
        System.out.println("获取所有的公有方法*");
        pClass.getMethods();
        Method[] methodArray = pClass.getMethods();
        for(Method m : methodArray){
            System.out.println(m);
        }
        System.out.println("----------------------------");
        System.out.println("获取所有的方法");
        methodArray = pClass.getDeclaredMethods();
        for(Method m : methodArray){
            System.out.println(m);
        }
        System.out.println("获取公有的test1()方法");
        Method m = pClass.getMethod("test1", String.class);
        System.out.println(m);
        //实例化一个Student对象
        Object obj = pClass.getConstructor().newInstance();
        m.invoke(obj, "lisi");

        System.out.println("获取私有的test4()方法*");
        m = pClass.getDeclaredMethod("test4", int.class);
        System.out.println(m);
        m.setAccessible(true);//解除私有限定
        Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
        System.out.println("返回值:" + result);
}
//输出结果
获取所有的公有方法*
public java.lang.String DateTest.Person.toString()
public int DateTest.Person.compareTo(java.lang.Object)
public java.lang.String DateTest.Person.getName()
public int DateTest.Person.getId()
public void DateTest.Person.setName(java.lang.String)
public void DateTest.Person.test1(java.lang.String)
public int DateTest.Person.getAge()
public java.lang.String DateTest.Person.getPhone()
public void DateTest.Person.setAge(int)
public void DateTest.Person.setPhone(java.lang.String)
public void DateTest.Person.setId(int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
----------------------------
获取所有的方法
public java.lang.String DateTest.Person.toString()
public int DateTest.Person.compareTo(java.lang.Object)
public java.lang.String DateTest.Person.getName()
public int DateTest.Person.getId()
public void DateTest.Person.setName(java.lang.String)
public void DateTest.Person.test1(java.lang.String)
protected void DateTest.Person.test2()
private java.lang.String DateTest.Person.test4(int)
void DateTest.Person.test3()
public int DateTest.Person.getAge()
public java.lang.String DateTest.Person.getPhone()
public void DateTest.Person.setAge(int)
public void DateTest.Person.setPhone(java.lang.String)
public void DateTest.Person.setId(int)
获取公有的test1()方法
public void DateTest.Person.test1(java.lang.String)
默认的无参构造方法执行了
调用了:公有的,String参数的test1(): name = lisi
获取私有的test4()方法*
private java.lang.String DateTest.Person.test4(int)
调用了,私有的,并且有返回值的,int参数的test4(): age = 20
返回值:abcd

调用某个类的方法

Java 反射获取 Class 对象并使用invoke()方法调用方法 reflect1() 和 reflect2():

public void test7() throws Exception{
        Class<?> clazz = Class.forName("DateTest.ReflectTest");
        Method method = clazz.getMethod("show1");
        method.invoke(clazz.newInstance());
        method = clazz.getMethod("show2", int.class, String.class);
        method.invoke(clazz.newInstance(), 20, "张三");
    }

    public void show1() {
        System.out.println("调用show1()");
    }
    public void show2(int age, String name) {
        System.out.println("调用show2()");
        System.out.println("age: " + age + "name: " + name);
    }
//输出结果
调用show1()
调用show2()
age: 20name: 张三

反射机制的动态代理

首先需要定义一个 InvocationHandler 接口的子类,完成代理的具体操作

package DateTest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyInvocationHandler implements InvocationHandler{
     private Object obj = null;

     public Object bind(Object obj) {
         this.obj = obj;
         return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
     }

     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         Object temp = method.invoke(this.obj, args);
         return temp;
     }
}
package DateTest;

public interface Animal {
    public void eat(String name);
}
package DateTest;

public class Dog implements Animal{
    public void eat(String name) {
        System.out.println(name+"eat!");
    }
}

新建类实现接口Animal,使用 MyInvocationHandler.bind() 方法获取即可实现调用。

public void test8(){
        MyInvocationHandler invo = new MyInvocationHandler();
        Animal sub = (Animal) invo.bind(new Dog());
        sub.eat("小狗");
    }
//输出结果
小狗eat!

通过反射越过泛型检查

泛型作用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的。

 public void test9() throws Exception{
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(111);
        list.add(222);
        Method method = list.getClass().getMethod("add", Object.class);
        method.invoke(list, "越过泛型检查");
        //遍历集合  
        for(Object obj : list){  
            System.out.println(obj);  
        }
    }
//输出结果
111
222
越过泛型检查

通过反射机制获得数组信息并修改数组的大小和值

通过反射机制分别修改int和String类型的数组的大小并修改int数组的第一个值。

 public static void test10() throws Exception{
         int[] temp = { 12,45,65,5,1,32,4,56,12};
         int[] newTemp = (int[]) arrayInc(temp, 15);
         print(newTemp);
         Array.set(newTemp, 0, 100);
         System.out.println("修改之后数组第一个元素为: " + Array.get(newTemp, 0));
         print(newTemp);
         String[] atr = { "a", "b", "c" };
         String[] str1 = (String[]) arrayInc(atr, 8);
         print(str1);
    }
    // 修改数组大小
    public static Object arrayInc(Object obj, int len) {
        Class<?> arr = obj.getClass().getComponentType();
        Object newArr = Array.newInstance(arr, len);
        int co = Array.getLength(obj);
        System.arraycopy(obj, 0, newArr, 0, co);
        return newArr;
    }
    // 打印
    public static void print(Object obj) {
        Class<?> c = obj.getClass();
        if (!c.isArray()) {
            return;
        }
        Class<?> arr = obj.getClass().getComponentType();
        System.out.println("数组类型: " + arr.getName());
        System.out.println("数组长度为: " + Array.getLength(obj));
        for (int i = 0; i < Array.getLength(obj); i++) {
            System.out.print(Array.get(obj, i) + " ");
        }
        System.out.println();
    }
//输出结果
数组类型: int
数组长度为: 15
12 45 65 5 1 32 4 56 12 0 0 0 0 0 0 
修改之后数组第一个元素为: 100
数组类型: int
数组长度为: 15
100 45 65 5 1 32 4 56 12 0 0 0 0 0 0 
数组类型: java.lang.String
数组长度为: 8
a b c null null null null null 

将反射机制应用于工厂模式

对于普通的工厂模式当我们在添加一个子类的时候,就需要对应的修改工厂类。 当我们添加很多的子类的时候,会很麻烦。

package DateTest;

public interface Animal {
    public abstract void sleep();
}
package DateTest;

public class Dog implements Animal{
    @Override
    public void sleep() {
        System.out.println("dog");
    }
}
package DateTest;

public class Cat implements Animal{
    @Override
    public void sleep() {
        System.out.println("cat");
    }
}
package DateTest;

public class Factory {
    public static Animal getInstance(String ClassName) {
        Animal a = null;
        try {
            a = (Animal) Class.forName(ClassName).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return a;
    }
}
public void test11()throws Exception{
        Animal a = Factory.getInstance("DateTest.Dog");
        if (a != null) {
            a.sleep();
        }
    }
//输出结果
dog

加载配置文件

//配置文件内容为
//className = DateTest.ReflectTest
public void test13() throws IOException {
         Properties pro = new Properties();//获取配置文件的对象  
         InputStream in=new Person().getClass().getResourceAsStream("/pro.txt");
         pro.load(in);//将流加载到配置文件对象中  
         in.close();  
         System.out.println(pro.getProperty("className"));
    }
//输出结果
默认的无参构造方法执行了
DateTest.ReflectTest

获取 ClassLoader 类加载器

public void test12() throws ClassNotFoundException {  
        //1、获取一个系统的类加载器  
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();  
        System.out.println(classLoader);  

        //2、获取系统类加载器的父类加载器  
        classLoader = classLoader.getParent();  
        System.out.println(classLoader);  

        //3、获取扩展类加载器的父类加载器  
        //输出为Null,引导类加载器无法被Java程序直接引用  
        classLoader = classLoader.getParent();  
        System.out.println(classLoader);  

        //4、测试当前类由哪个类加载器进行加载 ,结果是系统的类加载器  
        classLoader = Class.forName("DateTest.ReflectTest").getClassLoader();  
        System.out.println(classLoader);  

        //5、测试JDK提供的Object类由哪个类加载器负责加载的  
        classLoader = Class.forName("java.lang.Object").getClassLoader();  
        System.out.println(classLoader);  
    }
//输出结果
sun.misc.Launcher$AppClassLoader@6bc7c054
sun.misc.Launcher$ExtClassLoader@2ef1e4fa
null
sun.misc.Launcher$AppClassLoader@6bc7c054
null

总结

反射机制的应用虽然有很多优点,但是反射会额外消耗一定的系统资源,因此,反射操作的效率要比那些非反射操作低得多。另外,反射允许代码执行一般情况下不被允许的操作,所以一般能用其它方式实现的就尽量不去用反射。

参考文章

https://blog.csdn.net/sinat_3…
https://www.cnblogs.com/tech-…

作者:1s_Kiwen