Skip to content

Latest commit

 

History

History
337 lines (269 loc) · 13.2 KB

05反射.md

File metadata and controls

337 lines (269 loc) · 13.2 KB

六、反射

反射简介

每个类都有一个 Class 对象,包含了与类有关的信息。 当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。

类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 Class.forName("com.mysql.jdbc.Driver") 这种方式来控制类的加载,该方法会返回一个 Class 对象。

反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。

Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:

  • Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
  • Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
  • Constructor :可以用 Constructor 创建新的对象。

反射的优点:

  • 可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
  • 类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
  • 调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。

反射的缺点:

尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。

  • 性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。

  • 安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。

  • 内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

反射的使用

1. 获取Class对象

获得Class对象方法有三种:

(1)使用Class类的forName静态方法

public static Class<?> forName(String className)

(2)直接获取某一个对象的class,比如:

Class klass = int.class; Class classInt = Integer.TYPE;

(3)调用某个对象的getClass()方法,比如:

StringBuilder str = new StringBuilder("123"); Class<?> klass = str.getClass();

public class GetClazzObject {
    public static void main(String[] args) throws ClassNotFoundException {
        Class clazz=Class.forName("code_05_reflection.Obj");
        System.out.println(clazz);//class code_05_reflection.Obj

        Class clazz2=Obj.class;
        System.out.println(clazz2);//class code_05_reflection.Obj

        Obj obj=new Obj();
        Class clazz3=obj.getClass();
        System.out.println(clazz3);//class code_05_reflection.Obj
    }
}

2.判断是否是某个类实例

判断是否是某个类实例,使用Class类中的 isInstance()方法

public native boolean isInstance(Object obj);
public class IsInstanceOfClass {
    public static void main(String[] args) {
        Class clazz=Obj.class;

        Obj obj=new Obj();

        //判断是否是某个类的实例
        System.out.println(clazz.isInstance(obj));//true
    }
}

3.通过反射创建实例

通过反射来生成对象主要有两种方式:

  • 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
  • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例
public class CreateInstance {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。
        Class clazz=String.class;
        String str=(String)clazz.newInstance();

        //(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。
        Constructor strConstructor=clazz.getConstructor(String.class);////获取String构造函数 String(String str)
        String str2=(String)strConstructor.newInstance("sss");
        System.out.println(str2);//sss
    }
}

4.获取成员方法

获取某个Class对象的方法集合,主要有以下几个方法: (1)public Method[] getDeclaredMethods() throws SecurityException

getDeclaredMethods()方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

(2)public Method[] getMethods() throws SecurityException

getMethods()方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。

(3)public Method getMethod(String name, Class<?>... parameterTypes)

getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象

public class GetMethods {
    public static void main(String[] args) throws NoSuchMethodException {
        Class clazz=Obj.class;

        //方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
        Method[] methods=clazz.getDeclaredMethods();
        for(Method method:methods){
            System.out.println(method.getName());
        }
        System.out.println("=============");
        /**
         * 输出结果:
         add
         sub
         */

        //返回某个类的所有公用(public)方法,包括其继承类的公用方法。会有许多的Object的方法
        Method[] methods2=clazz.getMethods();
        for(Method method:methods2){
            System.out.println(method.getName());
        }
        System.out.println("=============");
        /**
         * 输出结果
         sub
         wait
         wait
         wait
         equals
         toString
         hashCode
         getClass
         notify
         notifyAll
         */

        //返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象
        Method methods3=clazz.getMethod("sub",int.class,int.class);
        System.out.println(methods3.getName());
        System.out.println(methods3);
        /**
         * 输出结果:
         sub
         public int code_05_reflection.Obj.sub(int,int)
         */
    }
}

5.获取构造器信息

获取类构造器的用法与上述获取方法的用法类似。

主要是通过Class类的getConstructor方法得到Constructor类的一个实例, 而Constructor类有一个newInstance方法可以创建一个对象实例。

public T newInstance(Object ... initargs)
public class GetConstructorInfo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz=Obj.class;
        Constructor constructor=clazz.getConstructor();
        //Constructor类有一个newInstance方法可以创建一个对象实例
        Obj obj=(Obj) constructor.newInstance();
        System.out.println(obj.sub(1,2));
    }
}

6.获取类的成员变量信息

主要是这几个方法: (1)getFiled: 访问公有的成员变量

(2)getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量

(3)getFileds和getDeclaredFields用法同上(参照Method)

7.利用反射创建数组

Array类为java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象。

public static Object newInstance(Class<?> componentType, int length)
        throws NegativeArraySizeException {
            return newArray(componentType, length); //newArray()方法是一个Native方法
        }
public class CreateArrays {
    public static void main(String[] args) {
        Class clazz=String.class;

        Object[] arr= (Object[]) Array.newInstance(clazz,25);
        Array.set(arr,0,"aaa");
        Array.set(arr,1,"bbb");
        Array.set(arr,2,"ccc");
        Array.set(arr,3,"ddd");
        Array.set(arr,4,"eee");
        Array.set(arr,5,"fff");
        System.out.println(arr.length); //25
        System.out.println(Array.get(arr,2));//ccc

    }
}

8.调用方法

当我们从类中获取了一个方法后,我们就可以用invoke()方法来调用这个方法。

public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
    InvocationTargetException
   //obj就是表示这个实例对象
   //args表示该方法的参数
public class InvokeMethod {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Class clazz=Class.forName("code_05_reflection.Obj");

        //先获取该类的实例对象
        Obj obj=(Obj)clazz.newInstance();

        Method method=clazz.getMethod("sub",int.class,int.class);

        //利用反射调用该方法
        Integer num=(Integer)method.invoke(obj,1,2);
        System.out.println(num);
    }
}

浅析invoke方法

invoke()源码如下:

@CallerSensitive
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException
{
    if (!override) {
    //TODO:(1)权限校验
    //invoke方法会首先检查AccessibleObject的override属性的值。
    //AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。
    //它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。
    //override的值默认是false,表示需要权限调用规则,调用方法时需要检查权限;
    //TODO:可以用setAccessible方法设置为true,
    //若override的值为true,表示忽略权限规则,调用方法时无需检查权限
    // (也就是说可以调用任意的private方法,违反了封装)。
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
        //Reflection.quickCheckMemberAccess(clazz, modifiers)方法检查方法是否为public,如果是的话,就跳出本步
            Class<?> caller = Reflection.getCallerClass(); //Reflection.getCallerClass()方法获取调用这个方法的Class对象,这是一个native方法
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    MethodAccessor ma = methodAccessor;  // read volatile
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args); //TODO:(2)调用MethodAccessor的invoke方法
    //TODO:Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。
}

Method的invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。

MethodAccessor接口

public interface MethodAccessor {
    /** Matches specification in {@link java.lang.reflect.Method} */
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException;
}

Method对象的基本构成,每个Java方法有且只有一个Method对象作为root, 它相当于根对象,对用户不可见。 当我们创建Method对象时,我们代码中获得的Method对象都相当于它的副本(或引用)。

root对象持有一个MethodAccessor对象,所以所有获取到的Method对象都共享这一个MethodAccessor对象, 因此必须保证它在内存中的可见性。

//volatile表示该对象是可见的
private volatile MethodAccessor methodAccessor;
private Method  root;

acquireMethodAccessor()

private MethodAccessor acquireMethodAccessor() {
    // First check to see if one has been created yet, and take it
    // if so
    MethodAccessor tmp = null;
    if (root != null) tmp = root.getMethodAccessor();
    if (tmp != null) {
        methodAccessor = tmp;
    } else {
        // Otherwise fabricate one and propagate it up to the root
        tmp = reflectionFactory.newMethodAccessor(this);
        setMethodAccessor(tmp);
    }
    return tmp;
}

第一次调用一个Java方法对应的Method对象的invoke()方法之前, 实现调用逻辑的MethodAccessor对象还没有创建; 等第一次调用时才新创建MethodAccessor并更新给root, 然后调用MethodAccessor.invoke()完成反射调用。