Java的反射机制

本人翻译并整理自Oracle官方文档

Java的反射机制允许在程序运行时去获取一个类(class)的成员变量和方法。

一、Classes

每个对象不是引用类型就是基本类型。引用类型全部继承自java.lang.Object。类、枚举类型、数组和接口都是引用类型。还有一套固定的基本类型:boolean, byte, short, int, long, char, floatdouble

对于每种类型的对象,Java虚拟机会实例化出一个不可变的java.lang.Class对象的实例,它提供了一些方法去检查这个对象的运行时属性包括它的成员和类型信息。Class类同时也提供了创造新的类和对象的能力。最重要的是它是所有反射API(Reflection APIs)的出发点。

1.1 获得Class对象-反射操作的入口

所有反射操作的出发点是java.lang.Class。因为有java.lang.reflect.ReflectPermission这个异常, java.lang.reflect下的类都没有公共的构造函数。为了获得这些类必须调用Class里的合适方法。这里有若干种方法来获得一个Class对象,取决于代码是否能访问某个对象,是否知道这个类的名称,类型(TYPE),或者一个已有的Class对象。

1.1.1 Object.getClass()

如果一个对象的实例是可以获得的,那么最简单的获得它的Class是调用Object.getClass()。当然它只在这个实例是继承自Object才可行。下面是一些例子:

Class c = "foo".getClass();

byte[] bytes = new byte[1024];
Class c = bytes.getClass();

1.1.2 .class 语法

如果类型是可以获得的但是没有对象的实例,那么可以通过在type后追加".class"来获得它的Class。这也是最简单的方法来获得一个基本类型的Class。

boolean b;
Class c = b.getClass();   // 编译错误

Class c = boolean.class;  // 正确

Class c = int[][][].class;

1.1.3 Class.forName()

如果一个类的 完全限定名(fully-qualified) 可以获得,可以通过静态方法Class.forName()来获得相应的Class。不能被用于基本类型。使用这种方法获得某个数组类的语法是Class.getName()。这个语法适用于引用和原始类型。

Class c = Class.forName("com.duke.MyLocaleServiceProvider");

Class cDoubleArray = Class.forName("[D");  // 与double[].class作用相同

1.1.4 基本类型包装类的TYPE成员变量

除了.class语法外还有一种方式可以获得基本类型的Class。每种基本类型和void都有一个包装类在java.lang被用来基本类型装箱成引用类型。每个包装类包含一个成员变量TYPE相当于Class对基本类型的包装。

Class c = Double.TYPE;

Class c = Void.TYPE;

1.1.5 方法返回的Class

这里有几个Reflection APIs返回Class对象但是需要某个Class已经被直接或间接的获得。

Class.getSuperclass() // 返回某个Class的父类Class

Class.getClasses() // 返回某个类的所有成员的Class对象
Class<?>[] c = Character.class.getClasses(); // Character包含2个成员对象Character.Subset 和Character.UnicodeBlock

1.2 访问Class的声明信息

某个类可能在被声明时使用一个或多个影响运行行为的修饰符。

  • 访问修饰符:public, protectedprivate
  • 要求重写的修饰符:abstract
  • 限制单实例的修饰符:static
  • 禁止修改值的修饰符:final
  • 强制严格浮点行为的修饰符:strictfp
  • 注解

不是所有的修饰符可以用在所有的类上,例如一个接口不能被声明为final,一个枚举类不能是abstract

java.lang.reflect.Modifier包含了所有可能的修饰符的声明。Class.getModifiers()方法返回了某个类的修饰符的集合。

下面这个例子展示了如何获得一个类的声明组件,包括修饰符,泛型类型参数,实现的接口和集成路径。如果Class实现了java.lang.reflect.AnnotatedElement接口,那么也能查询到运行时注解。

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import static java.lang.System.out;

public class ClassDeclarationSpy {

    public static void main(String... args) {
        try {
            Class<?> c = Class.forName(args[0]); // 根据输入的类名通过反射获得其Class

            out.format("Class:%n  %s%n%n", c.getCanonicalName()); // 获得完全限定名

            out.format("Modifiers:%n  %s%n%n",
                   Modifier.toString(c.getModifiers())); // 获得修饰符

            out.format("Type Parameters:%n");
            TypeVariable[] tv = c.getTypeParameters();
            if (tv.length != 0) {
                out.format("  ");

                for (TypeVariable t : tv)
                    out.format("%s ", t.getName()); // 输出类型参数

                out.format("%n%n");
            } else {
                  out.format("  -- No Type Parameters --%n%n");
            }

            out.format("Implemented Interfaces:%n");
            Type[] intfs = c.getGenericInterfaces();

            if (intfs.length != 0) {
                for (Type intf : intfs)
                    out.format("  %s%n", intf.toString()); // 输出实现的接口
                out.format("%n");
            } else {
                  out.format("  -- No Implemented Interfaces --%n%n");
            }

            out.format("Inheritance Path:%n");
            List<Class> l = new ArrayList<Class>();
            printAncestor(c, l);
            if (l.size() != 0) {
                for (Class<?> cl : l)
                    out.format("  %s%n", cl.getCanonicalName());

                out.format("%n");
            } else {
                  out.format("  -- No Super Classes --%n%n");
            }

            out.format("Annotations:%n");
            Annotation[] ann = c.getAnnotations();
            if (ann.length != 0) {
                for (Annotation a : ann)
                    out.format("  %s%n", a.toString()); // 输出注解

                out.format("%n");
            } else {
                  out.format("  -- No Annotations --%n%n");
            }

              // 生产环境的代码应该更优雅的处理这个异常
        } catch (ClassNotFoundException x) {
            x.printStackTrace();
        }
    }

    private static void printAncestor(Class<?> c, List<Class> l) {
        Class<?> ancestor = c.getSuperclass(); // 获得其父类
        if (ancestor != null) {
            l.add(ancestor);
            printAncestor(ancestor, l);
        }
    }
}

下面看看使用上面程序的实例:

$ java ClassDeclarationSpy java.util.concurrent.ConcurrentNavigableMap
Class:
  java.util.concurrent.ConcurrentNavigableMap

Modifiers:
  public abstract interface

Type Parameters:
  K V

Implemented Interfaces:
  java.util.concurrent.ConcurrentMap<K, V>
  java.util.NavigableMap<K, V>

Inheritance Path:
  -- No Super Classes --

Annotations:
  -- No Annotations --

这是一个真实的类java.util.concurrent.ConcurrentNavigableMap,它的源代码是:

public interface ConcurrentNavigableMap<K,V>
    extends ConcurrentMap<K,V>, NavigableMap<K,V>

注意,既然这是一个接口,那么它隐式的为abstract,编译器会为每个接口添加这个修饰符。

1.3 探索Class的成员

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

推荐阅读更多精彩内容