美文网首页Java资料整理
java基础(一),类型信息(反射)

java基础(一),类型信息(反射)

作者: Alent | 来源:发表于2016-09-14 23:19 被阅读188次

    1、RTTI(Run-Time Type Information)

    面向对象编程的基本目的是:让代码只操纵对基类的引用(多态),以此来增加代码的复用性。但是这种泛化引用不能知道一个类的确切类型,从而无法调用这个类的一些专有方法。RTTI即在运行时识别一个对象的确切类型。RTTI的一个基本使用:运行时的类型转换。java使用Class对象来执行其RTTI。

    java中的所有类都是在对其第一次使用时,由类加载器动态加载到JVM中的。JVM预定义的三种类型类加载器:

    • 启动(Bootstrap)类加载器:用C++编写,JVM自带的类加载器,用来加载核心类库。它负责将<Java_Runtime_Home>/lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,所以该加载器无法直接获取。

    • 扩展(Extension)类加载器:它负责将jre/lib/ext下的jar包或者-D java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

    • 系统(System)类加载器:它负责将系统类路径java -classpath-Djava.class.path变量所指的目录下的类库加载到内存中。是最常用的加载器。

    类的加载过程:

    • 加载:将类的class文件读入内存,并为之创建java.lang.Class对象。由类加载器完成。

    • 链接:为静态域分配存储空间,解析类的引用

    • 初始化:执行静态初始化器和静态初始化块

    2、Class对象

    无论何时,只要你想在运行时使用类型信息,就必须首先获得恰当的Class对象的引用。 Class 没有公共的构造方法,一个类的Class对象在内存中只会存在一个。
    构造Class对象的方法有四种:

        
        Person p1 = new Person();
        //下面的这三种方式都可以得到Class
        //方式1, 不会立即初始化
        CLass pClazz = Person.class();
        //方式2
        p1.getClass(); 
        //方式3,若存在这个类的Class对象则加载,否则新建。立即初始化
        Class.forName("com.alent.Person");
        //方式4,通过类的加载器, 不会立即初始化
        Class clazz = Thread.currentThread().getContextClassLoader().loadClass("com.alent.Person");
    

    三种方法的比较:

    • 类字面常量(方式1),更简单高效,更安全,编译时进行类型检查(建议使用.class形式,保持与普通类一致)

    • 方式2,需要持有该类型的引用,限制较大

    • 方式3,不需要为了获得Class引用而持有该类型的对象,且可以运行时指定字符串来加载,比较灵活。一般用于加载运行时才能确定的class对象,如:动态代理。

    类的Class对象被加载到内存时会初始化(执行static成员的引用及static代码块),但当用方式1或方式4来创建Class对象的引用时,不会自动的初始化该Class对象。如:

    
        public class TestClassInitialization {
            public static Random rand = new Random(47);
            
            public static void main(String[] args) throws Exception {
                Class person1Clazz = Person1.class;//不会初始化
                System.out.println("After creating person1");
                System.out.println(Person1.staticFinal);
                System.out.println("staticFinal2: " + Person1.staticFinal2);
                //非编译期常量,先初始化,再读取
                System.out.println(Person2.staticNonFinal);
                
                Class person3Clazz = Class.forName("com.alent.Person3");//立即初始化
                System.out.println("After creating person3");
                System.out.println(Person3.staticNonFinal);
                
            }
        }
        
        class Person1 {
            static final int staticFinal = 47; //编译器常量
            static final int staticFinal2 = TestClassInitialization.rand.nextInt(100);
            
            static {
                System.out.println("Initializing person1");
            }
        }
        
        class Person2 {
            static int staticNonFinal = 147; //不是编译器常量
            
            static {
                System.out.println("Initializing person2");
            }
        }
        
        class Person3 {
            static int staticNonFinal = 247;
            
            static {
                System.out.println("Initializing person3");
            }
        }
    

    执行结果为:

        After creating person1
        47
        Initializing person1
        staticFinal2: 58
        Initializing person2
        147
        Initializing person3
        After creating person3
        247
    

    结论:

    • 使用.class方法获得Class的引用时不会初始化,Class.forName()立即初始化

    • 编译期常量(static final)不需要初始化就能被读取,读取非编译器常量时必须先进行初始化

    基本类型的包装类中的TYPE指向对应基本类型的Class对象:

    • int.class 不等 Integer.class

    • int.class 等 Integer.TYPE

    注意:Class<Integer> clazz = int.class是合法的。

    泛化的Class

    通过使用泛型,使编译器强制执行类型检查。

    注意:数组是协变的,泛型不是协变得。例如:

    若Son extends Father则有 Son[] extends Father[],但是没有List<Son> extends List<Father>
    

    Class<?> 优于 Class,即使它们是等价的,Class<?>表示你并非是由于疏忽而使用了一个非具体的类引用。

        Class<? super Son> up = Son.class;
        Object obj = up.newInstance; //返回值是Object类型
    

    3、类型转换前先检查

    进行向下转型前,先要做类型转换检查。

    使用关键字instanceof

        if(x instanceof Dog) //判断x是否为Dog的实例
            ((Dog)x).bark();
    

    使用instanceof时只可将其与命名类型进行比较,有时可能需要写大量的判断语句,如:

    
        //判断一个pet对象,是哪种宠物
        if(pet instanceof Dog)
            ...//执行操作
        if(pet instanceof Pug)
            ...
        if(pet instanceof Rat)
            ...
        if(pet instanceof Cat)
            ...
    

    这时可使用Class的isInstance方法,动态的测试对象

        List<Class<? extends Animal>> clazzs = new ArrayList<>();
        clazzs.add(Dog.class);
        ...
        for(Class clazz : clazzs)
            if(clazz.isInstance(pet))
                ...//执行操作
    

    instanceofisInstance与直接比较Class对象的区别:

    • instanceof或isInstance指的是“你是这个类吗?或你是这个类的派生类吗?”

    • 用==比较Class对象,没有考虑继承,指“你是这个类吗?”

    4、反射

    反射就是把Java的各种成分映射成相应的Java类。反射是Java被视为动态语言的关键,它允许程序运行时(不是编译时)进行自我检查并对内部的成员进行操作。

    • java.lang.Class是反射的源头

    • 每个运行时类只加载一次

    有了Class的实例后就可以进行如下操作:

    • 创建对应运行时类的对象

    • 获取对应运行时类的完整结构(方法,属性,构造器,内部类,父类,子类,所在包,异常,注解...)

    • 调用对应运行时类的指定的结构(属性,方法,构造器)

    • 动态代理

    1)、创建对象

    使用newInstance()创建对应的运行时类的对象(调用类的空参构造器)
    要想创建成功,要求:

    • 对应的运行时类有空参构造器

    • 构造器的权限修饰符的权限足够

    2)、获取对应运行时类的完整结构

    • getFields():只能获取运行时类中及其父类中声明的public的属性

    • getDeclaredFields():获取运行时类本身声明的所有属性

    代码如下:

    
        Class clazz = Person.class;
        Field[] field = clazz.getDeclaredFields();
        for(Field f : field) {
            //获取每个属性的权限修饰符
            int i = f.getModifiers();
            String str1 = Modifier.toString(i);
            //获取属性的类型
            Class type = f.getType();
            //获取属性名
            String str2 = f.getName();
        }
    
        Method[] methods = clazz.getDeclaredMethods();
        for(Method m : methods) {
            //注解
            Annotation[] ann = m.getAnnotations();
            for(Annotation a : ann) {
                System.out.println(a);
            }
            //获取权限修饰符
            int i = m.getModifiers();
            String str1 = Modifier.toString(i);
            //获取返回值的类型
            Class type = m.getReturnType();
            //获取方法名
            String str2 = f.getName();
            //形参列表
            System.out.print("(");
            Class[] params = m.getParameterTypes();
            for(int i=0; i<params.length;i++) {
                System.out.print(params[i].getName() + " args-"+i + " ");
                System.out.print(")");
            }
        }
    

    3)、调用指定的结构

    
        //调用指定的属性
        Class clazz = Person.class;
        Person p = (Person)clazz.newInstance();
        Field age = clazz.getDeclaredField("id");
        //由于属性权限修饰符的限制,为了保证可以给属性赋值,需要在操作前使此属性可操作
        age.setAccessible(true);
        age.set(p,10);
        
        //调用指定的方法
        //调用invoke(Object obj, Object ...obj),返回值为Object
        Mothod m = clazz.getDeclaredMothod("dislay", String.class, Integer.class);
        m.setAccessible(true);
        Object value = m.invoke(p,"CHN",10);
        
        //调用指定的构造器
        Constructor cons = clazz.getDeclaredConstructor(String.class, int.class);
        cons.setAccessible(true);
        Person p = (Person)cons.newInstance("alent",20);
    

    参考

    • java编程思想

    相关文章

      网友评论

        本文标题:java基础(一),类型信息(反射)

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