Java基础温习之反射相关大集合

什么是反射

反射是在运行时而非编译时动态获取类型的信息(譬如接口信息、成员信息、方法信息、构造方法信息等)然后依据这些动态获取到的信息创建对象、访问修改成员、调用方法等。

通过调用 Class.forName(clzss) 方法可以访问返回一个以指定字符串 clzss 为类名的类对象,因为 java 里面任何 class 都要装载在虚拟机上才能运行,所以那个方法的作用就是装载类用的

也可以直接通过类名.Class获取 Class 类对象

还可以通过实例.getClass() 方法获取 Class 类对象

Class 类提供了许多方法,譬如可以获取与类名称有关的信息,可以获取类中定义的字段 Field(静态和实例变量都被称为字段,可获取 public 与非 public 的字段)

Field 也提供了许多获取字段或者设置字段具体信息的操作,可以获取类中定义的方法 Method(静态方法和非静态方法都是方法,可获取 public 与非 public 的方法)

Method 也提供了许多获取方法信息、修饰符、参数、返回值、注解等、调用对象方法的操作

Class.newInstance()方法可以创建对象实例(只是用默认无参构造),不过也提供了一些其他方法获取所有构造方法

Class 还提供了类型检查、类型判断、修饰符、父类、接口、注解、内部类等操作的方法

从 JVM 的角度使用关键字 new 创建一个类的时候这个类可以没有被加载,但是使用 newInstance() 方法的时候就必须保证这个类已经加载且这个类已经连接了,而完成上面两个步骤是 Class 的静态方法 forName() 所完成的,这个静态方法调用了启动类加载器(即加载 java API 的那个加载器),所以 newInstance() 实际上是把 new 这个方式分解为两步,即首先调用 Class 加载方法加载某个类然后实例化。

扫描二维码关注公众号,回复: 941021 查看本文章

反射的作用

反射可以在运行时判断任意一个对象所属的类;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;在运行时构造任意一个类的对象;生成动态代理操作

**如何提高反射的效率?反射优缺点有哪些?

提高反射效率要考虑的问题如下,首先保证反射 API 最小化,譬如尽量使用 getMethod 直接获取而不是 getMethods 遍历查找获取;其次需要多次动态创建一个类的实例时尽可能的使用缓存。

反射虽然很灵活,有些时候能够使得写的代码变的大幅精简,但是在用的时候一定要注意具体的应用场景,因为反射的优点是能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性,与 Java 动态编译相结合可以实现无比强大的功能;而其缺点就是性能相对较低,此外使用反射相对来说不安全,破坏了类的封装性(可以通过反射获取这个类的私有方法和属性),有些情况下反射受版本兼容问题而不稳定(譬如 Android 不同 ROM API 的修改)而难以提前发现问题。

三种方式获取类的 Class 对象

Class c1 = Class.forName("cn.test.Demo");
Class c2 = Demo.class;
Class c3 = new Demo().getClass();

c1 方式会让 ClassLoader 进行类加载然后进行类的初始化。 c2 方式会让 ClassLoader 进行类加载但是不初始化。 c3 方式获取的是已经被加载且初始化过后实例化运行中类的 Class 实例。

获取 Field 的方法

//返回一个 Field 对象,只能获取当前类或者其父类指定的 public 成员。
public Field getField(String name)
//返回一个 Field 对象数组,只能获取当前类或者其父类所有 public 成员。
public Field[] getFields()
//返回一个 Field 对象,只能返回当前类指定的所有类型成员(public、procted、private)。
public Field getDeclaredField(String name)
//返回一个 Field 对象数组,只能返回当前类指所有类型成员(public、procted、private)。
public Field[] getDeclaredFields()

getField(s) 返回的是申明为 public 的属性,包括指定类和其父类中定义的

getDeclaredField(s) 返回的是指定类中定义的所有属性,不包括父类的,如果想获取父类的需要首先通过指定类的 getSuperclass() 获取其父类然后再调用 getDeclaredField(s) 获取

获取 Method 的方法

原理策略完全类似上面 Field 的类似

获取类 Constructor 的方法

反射的构造方法是无法获取父类的任何构造方法的,一定要明确这一点

//返回一个 Constructor 对象,代表指定类的指定 public 构造方法。 public Constructor<T> getConstructor(Class<?>... parameterTypes) //返回一个 Constructor 对象数组,代表指定类的所有 public 构造方法。 public Constructor<?>[] getConstructors() //返回一个 Constructor 对象,代表指定类的指定构造方法(不限制修饰符)。 public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //返回一个 Constructor 对象数组,代表指定类的所有构造方法(不限制修饰符)。 public Constructor<?>[] getDeclaredConstructors()

Class.newInstance() 与 Constructor.newInstance() 方法

调用 Class 对象的 newInstance 方法会调用对象的默认构造方法,如果没有默认构造方法就会异常调用失败。

调用 Constructor 对象的 newInstance 方法可以选择要不要传递参数,没有参数时就类似 Class 的 newInstance,有参数时就调用对应有参构造方法。

Class.forName 和 ClassLoader.loadClass 方法

一个 Java 类加载到 JVM 中会经过三个步骤: 装载(查找和导入类或接口的二进制数据) 链接(校验:检查导入类或接口的二进制数据的正确性;准备:给类的静态变量分配并初始化存储空间;解析:将符号引用转成直接引用;) 初始化(激活类的静态变量的初始化 Java 代码和静态 Java 代码块)

//Class.forName(className) 方法内部实际调用的方法是 Class.forName(className, true, classloader);
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)

/**
三个参数的含义分别为:
name:要加载 Class 的名字
initialize:是否要初始化
loader:指定的 classLoader
*/
//ClassLoader.loadClass(className) 方法内部实际调用的方法是 ClassLoader.loadClass(className, false);
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException

/**
两个参数的含义分别为:
name:class 的名字
resolve:是否要进行链接
*/

Class.forName 方法执行之后已经对被加载类的静态变量分配完了存储空间

ClassLoader.loadClass 方法并没有一定执行完链接这一步;当想动态加载一个类且这个类又存在静态代码块或者静态变量而你在加载时就想同时初始化这些静态代码块则应偏向于使用 Class.forName 方法。

获取外部类实例情况下如何反射实例化内部类及调用内部类方法

class OuterClass {
    public void func() {
        System.out.println("Outer class.");
    }
    class InnerClass {
        private String mName;
        public InnerClass(String name) {
            mName = name;
        }
        void getName() {
            System.out.println("name is:"+mName);
        }
    }
}
public class Demo {
    public static void main(String[] args) throws Exception {
        Class outerClass = Class.forName("OuterClass");
        //通过外部类的方法名获取外部类的方法
        Method method = outerClass.getDeclaredMethod("func");
        //调用外部类实例的func方法,invoke第一参数为外部类实例
        method.invoke(outerClass.newInstance());
        //内部类反射名字需要使用$分隔,编译后生成的规则
        Class innerClass = Class.forName("OuterClass$InnerClass");
        //通过内部类的方法名获取内部类的方法
        Method method2 = innerClass.getDeclaredMethod("getName");
        //特别注意!!!!内部类newInstance的第一个参数必须是外部类实例的引用
        method2.invoke(innerClass.getDeclaredConstructors()[0].newInstance(outerClass.newInstance(),"yan"));
    }
}

即便上面 InnerClass 的修饰符是 private,上面的代码依旧可以成功反射,只是不能直接通过 outClass.new InnerClass("yan"); 的方式去实例化内部类。

同理当上面 InnerClass 的修饰符如果是 static,即内部类是静态内部类时上面的代码就不能用了,需要将答案的最后一行代码改成 method2.invoke(innerClass.getDeclaredConstructors()[0].newInstance("yan")); 才行,因为静态内部类没有持有外部类的任何引用,所以不用传递外部类实例对象的引用。

反射如下类的匿名内部成员类

class OuterClass {
    public Runnable runnable = new Runnable() {
        public void run() {
            System.out.println("runnable.");
        }
    };
}
public class Demo {
    public static void main(String[] args) throws Exception {
        Class outerClass = Class.forName("OuterClass");
        Runnable runnable = (Runnable) (outerClass.getField("runnable").get(outerClass.newInstance()));
        runnable.run();
    }
}

看段代码:

public class Test {
    private static final Integer KEY_EXIT = 1024;
    private static void invok1() throws NoSuchFieldException, IllegalAccessException {
        System.out.println("invok1->"+Test.KEY_EXIT);
        Field field = Test.class.getDeclaredField("KEY_EXIT");
        field.setAccessible(true);
        field.set(null, 1000);
        System.out.println("invok1-<"+Test.KEY_EXIT);
    }
    private static void invok2() throws NoSuchFieldException, IllegalAccessException {
        System.out.println("invok2->"+Test.KEY_EXIT);
        Field field = Test.class.getField("KEY_EXIT");
        field.set(null, 512);
        System.out.println("invok2-<"+Test.KEY_EXIT);
    }
    private static void invok3() throws NoSuchFieldException, IllegalAccessException {
        System.out.println("invok3->"+Test.KEY_EXIT);
        Field field = Test.class.getDeclaredField("KEY_EXIT");
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, 256);
        System.out.println("invok3-<"+Test.KEY_EXIT);
    }
    public static void main(String[] args) throws Exception {
        invok1();
        invok2();
        invok3();
    }
}
//invok1运行结果
invok1->1024
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.Integer field Test.KEY_EXIT to java.lang.Integer
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
    at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
    at java.lang.reflect.Field.set(Field.java:764)
    at Test.invok1(Test.java:14)
    at Test.main(Test.java:37)
//invok2运行结果
invok2->1024
Exception in thread "main" java.lang.NoSuchFieldException: KEY_EXIT
    at java.lang.Class.getField(Class.java:1703)
    at Test.invok2(Test.java:20)
    at Test.main(Test.java:38)
//invok3运行结果
invok3->1024
invok3-<256

对于 invok1 方法来说成功获取了 KEY_EXIT 静态常量,但是由于是 final 常量的,不允许直接修改,所以直接调用 set 时发生修改异常。

对于 invok2 方法来说 getFields() 方法只能获得某个类及其父类中的所有的 public 字段,而 getDeclaredFields() 方法却能获得某个类(不包括父类)的所有字段(包括 public、private、proteced 等。同样类似的还有 getConstructors() 和 getDeclaredConstructors()、getMethods() 和 getDeclaredMethods() 方法对,所以 invok2 会提示找不到字段而崩溃。 对于 invok3 来说就是标准的反射修改静态常量的姿势了,没啥好说的。

public class Test {
    private static final int KEY_EXIT = 1024;
    private static int KEY_BACK = 1025;
    private static final String KEY_STR = "1001";
    private static void invok1() throws NoSuchFieldException, IllegalAccessException {
        System.out.println("invok1->"+Test.KEY_EXIT);
        Field field = Test.class.getDeclaredField("KEY_EXIT");
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, 256);
        System.out.println("invok1-<"+Test.KEY_EXIT);
    }
    private static void invok2() throws NoSuchFieldException, IllegalAccessException {
        System.out.println("invok2->"+Test.KEY_BACK);
        Field field = Test.class.getDeclaredField("KEY_BACK");
        field.setAccessible(true);
        field.set(null, 1000);
        System.out.println("invok2-<"+Test.KEY_BACK);
    }
    private static void invok3() throws NoSuchFieldException, IllegalAccessException {
        System.out.println("invok3->"+Test.KEY_STR);
        Field field = Test.class.getDeclaredField("KEY_STR");
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, "512");
        System.out.println("invok3-<"+Test.KEY_STR);
    }
    public static void main(String[] args) throws Exception {
        invok1();
        invok2();
        invok3();
    }
}

//invok1运行结果
invok1->1024
invok1-<1024
//invok2运行结果
invok2->1025
invok2-<1000
//invok3运行结果
invok3->1001
invok3-<1001

上面三个方法的运行结果中你可能会去 debug 断点调试 invok1 和 invok3,你会发现相关成员属性都得到了正确的反射修改值,但是为啥输出却不是反射修改后的值。其实原因很简单,当我们定义基本类型的 final 常量或者 String 类型的 final 常量时(只要为 final,不限制有无 static),如果在编译时能确定其确切值则编译器会将其用到的地方用其实际值进行替换,譬如 static final int A = 23; println(A); if(i > A){} 这样的语句会被编译成 static final int A = 23; println(23); if(i > 23){} 的形式,所以即便运行时反射成功也没有任何意义,因为相关值已经在编译时被替换为了常量,而对于包装类型则没事。 所以在反射 final 修饰的变量时一定要留意其是否会在编译时被替换的问题,否则不但不会报错还会给自己带来不必要的错觉麻烦。当然除过基本类型的 final 外 String 类型的 final 为啥会有类似特性的问题可以参看

public class Test {
    public void age(int age) {
        System.out.println("int age="+age);
    }
    public void age(Integer age) {
        System.out.println("Integer age="+age);
    }
    public static void main(String[] args) throws Exception {
        Test obj = new Test();
        Method m1 = obj.getClass().getMethod("age", int.class);
        m1.invoke(obj, new Integer(27));  //1
        m1.invoke(obj, 28); //2
        Method m2 = obj.getClass().getMethod("age", Integer.class);
        m2.invoke(obj, new Integer(27));  //3
        m2.invoke(obj, 28); //4
    }
}
int age=27
int age=28
Integer age=27
Integer age=28

getMethod("age", Integer.TYPE).invoke(obj, 27);

而基本数据类型没有类的全限定名且没有 getClass 方法可供使用,所以如果想用 Class 类来表示基本数据类型的 Class 实例就得使用 Java 提供的内置实例,即: Class clzss = byte.class; Class clzss = short.class; Class clzss = int.class; Class clzss = long.class; Class clzss = char.class; Class clzss = float.class; Class clzss = double.class; Class clzss = boolean.class; Class clzss = void.class; 此外 Java 的基本数据类型的包装类中都有一个名为 TYPE 的常量来表示对应包装类型的基本数据类型的 Class 实例,即: Byte.TYPE = byte.class; Short.TYPE = short.class; Integer.TYPE = int.class; Long.TYPE = long.class; Char.TYPE = char.class; Float.TYPE = float.class; Double.TYPE = double.class; Boolean.TYPE = boolean.class; Void.TYPE = void.class; 同理对于数组类型的 Class 类型实例也是类似规则,譬如: Class clzss = int[].class; //int[] 数组 Class clzss = String[].class; //String[] 数组 Class clzss = String[][].class; //String[][] 数组的数组

猜你喜欢

转载自my.oschina.net/hensemlee/blog/1815426