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

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编程思想
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,631评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,656评论 18 139
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,104评论 0 62
  • 风风光光更好更更更更更好何境辉广泛的 Q心理
    蜻蜓爬树阅读 310评论 3 2
  • 今天是坚持晨跑的第四天。由于晨跑,看到了清晨路灯的清美,欣赏了月亮西下时瞬间的精彩,体味了清洁工阿姨的辛苦,也感受...
    贺彩莲阅读 163评论 0 0