反射

作者: JacobY | 来源:发表于2018-01-06 03:52 被阅读0次

    反射简介

    反射允许我们在程序运行时获取和使用类的信息。


    Class 对象

    Java程序运行时,用Class对象表示类型信息,它包含了与类相关的信息。类是程序的一部分,每个类都有一个Class对象,也就是说,当一个类被编译之后,就会产生一个Class对象,并保存在.class文件中。当程序创建第一个对类的静态成员的引用时,JVM就会用类加载器加载Class对象,当该类的Class对象被加载入内存后,就可以使用它来创建实例对象。

    获取Class对象引用的方式:

    • 通过Class.forName() 方法来加载类并获取Class对象的引用并初始化该类,该方法的参数必须为类的全限定名(包含包名)
    try {
        Class.forName("Person");
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    
    • 如果已经获得该类的实例,可通过该实例调用getClass()方法获取Class对象的引用
    Person person = new Person();
    Class clazz = person.getClass();
    
    • 使用类字面常量,该方式仅仅只是拿到Class对象的引用,并没有进行初始化
    Class clazz = Person.class;
    

    对于基本数据类型,同样存在类字面常量,而且,在对应的包装类中有一个字段TYPE作为基本数据类型的类字面常量的引用,比如,对于 int 来说,其字面常量为int.class,它与Integer.TYPE等价,但要注意的是,它们与Integer.class是两回事。
    为了使用类而做的准备工作有三个步骤:
    1.加载。由类加载器执行,查找字节码,并通过字节码创建一个Class对象。
    2.链接。在这个阶段将验证类中的字节码,为静态域分配存储空间,如果必须的话,还要解析这个类创建的对其他类的引用
    3.初始化。如果该类有超类,则对其初始化,执行静态初始化器和静态初始化块。初始化被延迟到了对静态方法(在这种情况下,构造器可以视为静态的方法)或者非常数静态域进行首次引用的时候才执行。例子如下:

    import java.util.Random;
    
    class Initable {
        static final int staticFinal = 47;
        static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
    
        static {
            System.out.println("Initializing Initable");
        }
    }
    
    class Initable2 {
        static int staticNonFinal = 147;
        static {
            System.out.println("Initializing Initable2");
        }
    }
    
    class Initable3 {
        static int staticNonFinal = 74;
        static {
            System.out.println("Initializing Initable3");
        }
    }
    
    public class ClassInitialization {
    
        public static Random rand = new Random(47);
    
        public static void main(String[] args) throws Exception {
    
            //这里不会触发初始化
            Class initable = Initable.class;
            System.out.println("After creating Initable ref");
    
            //这里也不会触发初始化,因为只是引用编译期常量
            System.out.println(Initable.staticFinal);
    
            //这里触发初始化,staticFinal2并不是一个编译期常量
            System.out.println(Initable.staticFinal2);
    
            //这里同样会触发初始化, 因为如果static域不是final的情况下,对它进行访问时必须要先进行链接(分配存储空间)和初始化(初始化存储空间)
            System.out.println(Initable2.staticNonFinal);
    
            //Class.forName()方法在加载类后会进行初始化
            Class.forName("Initable3");
            System.out.println("After creating Initable3 ref");
            System.out.println(Initable3.staticNonFinal);
    
        }
    
    }
    

    输出结果如下:

    After creating Initable ref
    47
    Initializing Initable
    258
    Initializing Initable2
    147
    Initializing Initable3
    After creating Initable3 ref
    74

    Class对象一些常用方法:

    • newInstance()
      创建新实例对象,使用该方法来创建的类,必须带有默认的构造器,否则会抛出异常InstantiationException
    • getName(), getSimpleName(), getCanonicalName()
      getName() 获取全限定的类名,getSimpleName() 获取不含包名的类名, getCanonicalName() 也是获取全限定的类名, 对于普通的类来说,两者结果基本是一样的,但是对于一些特殊的类,比如数组和内部类等,两者的结果有所差异。
    public class Main {
    
        public static void main(String[] args) {
    
            Integer[] array = new Integer[5];
            InnerClass innerClass = new InnerClass();
    
            System.out.println(array.getClass().getName());
            System.out.println(array.getClass().getCanonicalName());
    
            System.out.println(innerClass.getClass().getName());
            System.out.println(innerClass.getClass().getCanonicalName());
    
        }
    
        private static class InnerClass {
    
        }
    
    }
    

    结果如下:

    [Ljava.lang.Integer;
    java.lang.Integer[]
    Main$InnerClass
    Main.InnerClass

    • isInstance()
      instanceof 关键字作用一样,但是更灵活, instanceof 需要显示地指定类型,而isInstance() 则不需要,只要拿到Class对象的引用即可。
      对于两个Class对象的比较,可以使用==也可以使用equals(),但是它们与isIntanceof以及isInstance()有所不同,==equals()不考虑继承
    class Base {}
    
    class Derived extends Base {}
    
    public class ClassCompareTest {
    
    
        static void test(Object x) {
            Class clazz = x.getClass();
            String className = clazz.getSimpleName();
            System.out.println("测试" + clazz);
            System.out.println("x instanceof Base [" + (x instanceof Base) + "]");
            System.out.println("x instanceof Derived [" + (x instanceof Derived) + "]");
            System.out.println("Base.class.isInstance(x) [" + (Base.class.isInstance(x)) + "]");
            System.out.println("Derived.class.isInstance(x) [" + (Derived.class.isInstance(x)) + "]");
            System.out.println(className + ".class == Base.class [" + (clazz == Base.class) + "]");
            System.out.println(className + ".class == Derived.class [" + (clazz == Derived.class) + "]");
            System.out.println(className + ".class.equals(Base.class) [" + (clazz.equals(Base.class)) + "]");
            System.out.println(className + ".class.equals(Derived.class) [" + (clazz.equals(Derived.class)) + "]");
        }
    
        public static void main(String[] args) {
            test(new Base());
            test(new Derived());
        }
    }
    

    输出结果为:

    测试class Base
    x instanceof Base [true]
    x instanceof Derived [false]
    Base.class.isInstance(x) [true]
    Derived.class.isInstance(x) [false]
    Base.class == Base.class [true]
    Base.class == Derived.class [false]
    Base.class.equals(Base.class) [true]
    Base.class.equals(Derived.class) [false]
    测试class Derived
    x instanceof Base [true]
    x instanceof Derived [true]
    Base.class.isInstance(x) [true]
    Derived.class.isInstance(x) [true]
    Derived.class == Base.class [false]
    Derived.class == Derived.class [true]
    Derived.class.equals(Base.class) [false]
    Derived.class.equals(Derived.class) [true]


    类成员

    通过Class对象可以获取类运行时的类成员信息,类成员用Member接口表示,该接口的实现类有Field、Method、 Constractor。

    Field类

    • Field对象的获取
      Field表示类的成员变量,可以使用Class对象的getFields()getDeclaredFields(),两者的区别在于前者只能拿到所有从父类继承来的public成员变量以及自己的public成员变量,而后者只能拿在本类内声明的所有成员变量,不管修饰符是什么。另外还有getField()getDeclaredField()用来返回特定的成员变量,区别和前面一样。对于Method类以及Constractor类也同理。
    • 获取成员变量的类型
      可以使用getType()getGenericType()获取成员变量的类型,两者的区别:
      • 前者返回的是变量类型的Class对象引用,不包含泛型信息
      • 当变量类型没有泛型时,后者结果与前者一致。当变量类型有泛型时,返回结果是一个Type接口的类型,包含泛型信息。
        假设有这样一个类:
      public class Student {
        private String name;
        private List<String> courses;
      }
      
      运行下面例子:
            Class clazz = Student.class;
      
            Field[]  fields = clazz.getDeclaredFields();
      
            for (Field field: fields) {
                System.out.println("getType(): " + field.getType());
                System.out.println("getGenericType(): " + field.getGenericType());
            }
      

    结果如下:

    getType(): class java.lang.String
    getGenericType(): class java.lang.String
    getType(): interface java.util.List
    getGenericType(): java.util.List<java.lang.String>

    参考:《Java编程思想(第4版)》Bruce Eckel 著

    相关文章

      网友评论

          本文标题:反射

          本文链接:https://www.haomeiwen.com/subject/htzdnxtx.html