反射


反射简介

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


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 著

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

推荐阅读更多精彩内容