Java 基础 - 反射

Class 类

  • 在 Java 中大部分元素都是对象(但也有例外,如静态成员、基本数据类型);
  • 类是 java.lang.Class 类的实例对象,这个对象称为该类的类类型;
  • Class 类是私有类,不能直接用于创建对象,只有 JVM 可以访问;
  • 通过 Class 可以访问系统为所有对象维护运行时的类型标识、从而可以通过类的类类型创建类的实例对象。

对于一个普通的类,可以使用 new 创建其对象

class Foo {
    void print() {
        System.out.println("foo");
    }
}
Foo foo1 = new Foo();

Class 类不能直接访问;任何一个类都是 Class 类的实例对象,这个实例对象有三种表达方式

// 第一种表达方式,表示任何一个类都有一个隐含的静态成员变量 class
Class c1 = Foo.class;

// 第二种表达方式,已经知道该类的对象通过 getClass 方法
Class c2 = foo1.getClass();

// c1、c2 表示了 Foo 类的类类型(class type)
System.out.println(c1 == c2);

// 第三种表达方式
Class c3 = null;
c3 = Class.forName("com.ywh.reflect.Foo");
System.out.println(c2 == c3);

“可以通过类的类类型创建类的实例对象”,指的是在这里可以通过 c1c2c3 创建 Foo 的实例

try {
    Foo foo = (Foo) c1.newInstance();    // 需要在类中定义无参数的构造方法,否则会抛出异常
    foo.print();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

动态加载与静态加载

Class.forName("com.ywh.reflect.Foo")

  • 表示类的类类型,同时代表动态加载类;
  • 编译时加载类(使用 new 创建对象,类不存在时编译无法通过)是静态加载类,运行时加载类是动态加载类。

静态加载类

要求所有工具类都写在 Office 类中,耦合度高(Excel 不存在,Word 也不可用,因为无法通过编译)。

class Office {
    public static void main(String[] args) {
        if ("Word".equals(args[0])) {
            Word w = new Word();
            w.start();
        }
        else if ("Excel".equals(args[0])) {
            Excel e = new Excel();
            e.start();
        }
    }
}

动态加载类

  • Word 和 Excel 继承标准接口,在动态加载时代替必须指定其中的某个类型的强制类型转换;
  • 当需要扩展工具时,只需要添加符合标准(继承接口)的工具类,不需要再修改 Office 类;
// Word 和 Excel 的共同标准接口
interface OfficeAble {
    public void start();
}

class Office {
    public static void main(String[] args) {
        try {
            Class c = Class.forName(args[0]);
            OfficeAble oa = (OfficeAble) c.newInstance();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

编译、运行

javac Office.java
java Office Word

获取类的信息

其他类型(基本数据类型、关键字等)都存在类类型

Class c1 = int.class;    // int 的类类型
Class c2 = String.class;    // String 类的类类型,相当于 String 类字节码
Class c3 = double.class;
Class c4 = Double.class;
Class c5 = void.class;    // package 没有,因为不是在类中声明的

System.out.println(c1.getName());
System.out.println(c2.getName());
System.out.println(c2.getSimpleName());    // 不包含包名的类的名称
System.out.println(c5.getName());

对于一个类,Method 存放类的方法对象,Field 存放类的成员对象。

获取类方法的信息

public static void printClassMethodMessage(Object obj) {
    // 首先要获取类的类类型
    Class c = obj.getClass();    // 传递的是哪个子类的对象,c 就是该子类的类类型
    System.out.println("类的名称是:" + c.getName());    // 获取类的名称

    Method[] ms = c.getMethods();    // 获取所有有 public 方法,包括父类继承而来的
    // c.getDeclaredMethods() 获取的是所有该类自己声明的方法
    for (int i = 0; i < ms.length; i++) {
        Class returnType = ms[i].getReturnType();    // 得到方法的返回值类型的类类型
        System.out.print(returnType.getName() + " ");
        System.out.print(ms[i].getName() + "(");    // 得到方法的名称
        Class[] paramTypes = ms[i].getParameterTypes();    // 获取参数类型,即参数列表的类型的类类型
        for (Class class1 : paramTypes) {
            System.out.print(class1.getName() + ",");
        }
        System.out.println(")");
    }
}

获取类成员的信息

  • 成员变量也是对象(java.lang.reflect.Field 的对象);
  • 其中 Field 类封装了关于成员变量的操作。
public static void printFieldMessage(Object obj) {
    Class c = obj.getClass();
    // Field[] fs = c.getFields();    // 获取所有的public的成员变量的信息
    Field[] fs = c.getDeclaredFields();    // 获取该类自己声明的成员变量的信息
    for (Field field : fs) {
        Class fieldType = field.getType();    // 得到成员变量的类型的类类型
        String typeName = fieldType.getName();
        String fieldName = field.getName();    // 得到成员变量的名称
        System.out.println(typeName + " " + fieldName);
    }
}

获取类构造方法的信息

  • 构造方法也是对象(java.lang.Constructor 的对象)
public static void printConMessage(Object obj) {
    Class c = obj.getClass();
    // Constructor[] cs = c.getConstructors();    // 获取所有的public的构造函数
    Constructor[] cs = c.getDeclaredConstructors();    // 得到所有的构造函数
    for (Constructor constructor : cs) {
        System.out.print(constructor.getName() + "(");
        Class[] paramTypes = constructor.getParameterTypes();    // 获取构造函数的参数列表(参数列表的类类型)
        for (Class class1 : paramTypes) {
            System.out.print(class1.getName() + ",");
        }
        System.out.println(")");
    }
}

反射的基本操作

  • 方法由名称、参数列表决定;
  • 使用方法对象的 invoke(对象, 参数列表) 方法来实现反射操作。

对于一个普通类

class A {
    public void print() {
        System.out.println("helloworld");
    }

    public void print(int a, int b) {
        System.out.println(a + b);
    }

    public void print(String a, String b) {
        System.out.println(a.toUpperCase() + "," + b.toLowerCase());
    }
}

执行反射操作

  • 先获取类的类类型;
  • 由名称、参数列表决定获取的方法;
  • 使用方法对象来调用方法。
try {
    A a1 = new A();
    Class c = a1.getClass();
    
    // 获取 a1 中,名称为 “print”、第一个参数类型为 “int”、第二个参数类型为 “int” 的方法并调用
    Method m0 = c.getMethod("print", int.class, int.class);
    Object o = m0.invoke(a1, 10, 20);    // 等价于a1.print(10, 20)
    
    Method m1 = c.getMethod("print", String.class, String.class);
    o = m1.invoke(a1, "hello", "WORLD");
    
    Method m2 = c.getMethod("print");
    m2.invoke(a1);

} catch (Exception e) {
    e.printStackTrace();
}

反射是常用于系统程序中的技术,但由于把程序逻辑插入到运行时、绕过了编译器的判断,从而让编译器无法帮助发现程序中的错误,很可能在运行时出现预期以外的错误而难以解决,因此不应在应用程序中过多使用反射。

泛型的本质

Java 中的集合常使用泛型来防止错误输入(不能放入类型不兼容的元素):

ArrayList list1 = new ArrayList();
ArrayList<String> list2 = new ArrayList<>();
list2.add("hello");
// list2.add(1);

但在以上两个集合中,其类类型是相等的

Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2);

由于反射的操作都是编译后的操作,c1 == c2 返回 true 表明编译之后集合是去泛型化的(编译之后没有泛型),所以泛型只在编译阶段有效,绕过编译就无效了。

实例:通过方法反射操作绕过编译

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