第十九章 类型信息

RTTI(RunTime Type Information,运行时类型信息)能够在程序运行时发现和使用类型信息。

Java 主要有两种方式在运行时识别对象和类信息:

  1. “传统的” RTTI:假定我们在编译时已经知道了所有的类型;
  2. “反射”机制:允许我们在运行时发现和使用类的信息。

Class 对象

类是程序的一部分,每个类都有一个 Class 对象。换言之,每当我们编写并且编译了一个新类,就会产生一个 Class 对象(更恰当的说,是被保存在一个同名的 .class 文件中)。为了生成这个类的对象,Java 虚拟机 (JVM) 先会调用"类加载器"子系统把这个类加载到内存中。

类加载器子系统可能包含一条类加载器链,但有且只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是”可信类”(包括 Java API 类)。它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持 Web 服务器应用,或者通过网络下载类),也可以挂载额外的类加载器。

所有的类都是第一次使用时动态加载到 JVM 中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。

其实构造器也是类的静态方法,虽然构造器前面并没有 static 关键字。所以,使用 new 操作符创建类的新对象,这个操作也算作对类的静态成员引用。

Java 程序在它开始运行之前并没有被完全加载,很多部分是在需要时才会加载。这一点与许多传统编程语言不同,动态加载使得 Java 具有一些静态加载语言(如 C++)很难或者根本不可能实现的特性。

类加载器首先会检查这个类的 Class 对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找 .class 文件(如果有附加的类加载器,这时候可能就会在数据库中或者通过其它方式获得字节码)。这个类的字节码被加载后,JVM 会对其进行验证,确保它没有损坏,并且不包含不良的 Java 代码(这是 Java 安全防范的一种措施)。

一旦某个类的 Class 对象被载入内存,它就可以用来创建这个类的所有对象。

Class 对象仅在需要的时候才会被加载,static 初始化是在类加载时进行的。

Class 包含很多有用的方法:

传递给 forName() 的字符串必须使用类的全限定名(包含包名)。使用 getName() 来产生完整类名,使用 getSimpleName() 产生不带包名的类名,getCanonicalName() 也是产生完整类名(除内部类和数组外,对大部分类产生的结果与 getName() 相同)。isInterface() 用于判断某个 Class 对象代表的是否为一个接口。Class.getInterface() 方法返回的是存放 Class 对象的数组,里面的 Class 对象表示的是那个类实现的接口。调用 getSuperclass() 方法来得到父类的 Class 对象。Class 对象的 newInstance() 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。使用 newInstance() 来创建的类,必须带有无参数的构造书。

类字面常量

Java还提供了另一种方法来生成类对象的引用:类字面常量。就像这样:FancyToy.class;

类字面常量不仅不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段 TYPE。TYPE字段是一个引用,指向对应的基本数据类型的 Class 对象,如下所示:

类型 ...等价于...
boolean.class Boolean.TYPE
char.class Character.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE

为了使用类而做的准备工作实际包含三个步骤:

  1. 加载,这是由类加载器执行的。该步骤将查找字节码(通常在 classpath 所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个 Class 对象。
  2. 链接。在链接阶段将验证类中的字节码,为 static 域分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。
  3. 初始化。如果该类具有超类,则对其进行初始化,执行 static 初始化器和 static 初始化块。
class Initable {
static final int STATIC_FINAL = 47;
static final int STATIC_FINAL2 = 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.STATIC_FINAL);
// 触发初始化:
System.out.println(Initable.STATIC_FINAL2);
// 触发初始化:
System.out.println(Initable2.staticNonFinal);
Class initable3 = Class.forName("typeinfo.Initable3");
// 触发初始化:
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
}

初始化有效地实现了尽可能的“惰性”,从对 initable 引用的创建中可以看到,仅使用 .class 语法来获得对类对象的引用不会引发初始化。但与此相反,使用 Class.forName() 来产生 Class 引用会立即就进行初始化,如 initable3。

如果一个 static final 值是“编译期常量”(如 Initable.staticFinal),那么这个值不需要对 Initable 类进行初始化就可以被读取。但是,如果只是将一个域设置成为 static 和 final,还不足以确保这种行为。例如,对 Initable.staticFinal2 的访问将强制进行类的初始化,因为它不是一个编译期常量。

如果一个 static 域不是 final 的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间),就像在对 Initable2.staticNonFinal 的访问中所看到的那样。

泛化的 Class 引用

普通的类引用可以重新赋值指向任何其他的 Class 对象,但是使用泛型限定的类引用只能指向其声明的类型。通过使用泛型语法,我们可以让编译器强制执行额外的类型检查。

public class GenericClassReferences {
public static void main(String[] args) {
Class intClass = int.class;
intClass = double.class; Class<Integer> genericIntClass = int.class;
genericIntClass = Integer.class; // Same thing
// genericIntClass = double.class; // 非法,Type mismatch: cannot convert from Class<Double> to Class<Integer>
}
}

为了在使用 Class 引用时放松限制,可以使用通配符 ?,表示“任何事物”,它是 Java 泛型中的一部分。

使用 Class<?> 比单纯使用 Class 要好,虽然它们是等价的,并且单纯使用 Class 不会产生编译器警告信息。使用 Class<?> 的好处是它表示你并非是碰巧或者由于疏忽才使用了一个非具体的类引用,而是特意为之。

cast() 方法

Java 中还有用于 Class 引用的转型语法,即 cast() 方法。

class Building {
} class House extends Building {
} public class ClassCasts {
public static void main(String[] args) {
Building b = new House();
Class<House> houseType = House.class;
House h = houseType.cast(b);
h = (House) b; // ... or just do this.
}
}

Java 类库中另一个没有任何用处的特性就是 Class.asSubclass(),该方法允许你将一个 Class 对象转型为更加具体的类型。

类型转换检测

关键字 instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例,可以用提问的方式使用它,就像这个样子:

if(x instanceof Dog)
((Dog)x).bark();

instanceof 有一个严格的限制:只可以将它与命名类型进行比较,而不能与 Class 对象作比较。

一个动态 instanceof 函数

Class.isInstance() 方法提供了一种动态测试对象类型的方法。

递归计数

Class.isAssignableFrom():确定此Class对象表示的类或接口是否与指定的Class参数表示的类或接口相同,或者是它们的超类或超接口。如果是,则返回true;否则返回false。

类的等价比较

public class FamilyVsExactType {
static void test(Object x) {
System.out.println("====================================================================");
System.out.println("Testing x of type " + x.getClass());
System.out.println("x instanceof Base " + (x instanceof Base));
System.out.println("x instanceof Derived " + (x instanceof Derived));
System.out.println("Base.isInstance(x) " + Base.class.isInstance(x));
System.out.println("Derived.isInstance(x) " + Derived.class.isInstance(x));
System.out.println("x.getClass() == Base.class " + (x.getClass() == Base.class));
System.out.println("x.getClass() == Derived.class " + (x.getClass() == Derived.class));
System.out.println("x.getClass().equals(Base.class)) " + (x.getClass().equals(Base.class)));
System.out.println("x.getClass().equals(Derived.class)) " + (x.getClass().equals(Derived.class)));
} public static void main(String[] args) {
test(new Base());
test(new Derived());
}
}

反射:运行时类信息

反射提供了检测可用方法并生成方法名称的机制。

类 Class 支持反射的概念,以及 java.lang.reflect 库,其中包含类FieldMethodConstructor(每一个都实现了 Member 接口)。这些类型的对象由 JVM 在运行时创建,以表示未知类中的对应成员。然后,可以使用 Constructor 创建新对象,get()set() 方法读取和修改与 Field 对象关联的字段,invoke() 方法调用与 Method 对象关联的方法。此外,还可以调用便利方法 getFields()getMethods()getConstructors() 等,以返回表示字段、方法和构造函数的对象数组。匿名对象的类信息可以在运行时完全确定,编译时不需要知道任何信息。

RTTI 和反射的真正区别在于,使用 RTTI 时,编译器在编译时会打开并检查 .class 文件。换句话说,你可以用“正常”的方式调用一个对象的所有方法。通过反射,.class 文件在编译时不可用;它由运行时环境打开并检查。

类方法提取器

Class 方法 getmethods()getconstructors()分别返回Method数组和Constructor数组。

20190908 On Java8 第十九章 类型信息的更多相关文章

  1. 《Java编程思想》笔记 第十四章 类型信息

    1.RTTI:在运行时识别一个对象类型 JAVA在运行时 有时要 识别对象和类的信息这个机制叫RTTI.Java提供了两种机制去做这件事.传统的RTTI 和 反射. 传统的RTTI  假定编译时就已经 ...

  2. 《Thinking in Java》十四章类型信息_习题解

    1~10    Page 318 练习1. 在ToyTest.java中,将Toy的默认构造器注释掉,并解释发生的现象. 书中代码如下(略有改动): package org.cc.foo_008; p ...

  3. java编程思想第四版第十四章 类型信息习题

    fda dfa 第三题u package net.mindview.typeinfo.test4; import java.util.ArrayList; import java.util.Array ...

  4. java编程思想第四版第十四章 类型信息总结

    1. Class 对象: 所有的类都是在对其第一次使用的时候,动态加载到JVM中的.当程序创建第一个对类的静态成员的引用时,就会加载这个类.这说明构造器也是类的静态方法.即使在构造器之前并没有stat ...

  5. 第十九章——使用资源调控器管理资源(1)——使用SQLServer Management Studio 配置资源调控器

    原文:第十九章--使用资源调控器管理资源(1)--使用SQLServer Management Studio 配置资源调控器 本系列包含: 1. 使用SQLServer Management Stud ...

  6. 第十九章 Django的ORM映射机制

    第十九章 Django的ORM映射机制 第一课 Django获取多个数据以及文件上传 1.获取多选的结果(checkbox,select/option)时: req.POST.getlist('fav ...

  7. Gradle 1.12用户指南翻译——第四十九章. Build Dashboard 插件

    本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  8. Gradle 1.12用户指南翻译——第二十九章. Checkstyle 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

  9. Gradle 1.12用户指南翻译——第三十九章. IDEA 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

随机推荐

  1. linux c++下遍历文件

    https://blog.csdn.net/u013617144/article/details/44807333

  2. CQRS架构下的Saga流程重构

  3. java实现队列和栈

    队列:队列其实就是我们生活中的排队现象,先进入的先出,后进入的后出,代码实现如下: public class Queue<E> { private int front;//队头一端,只允许 ...

  4. pyqt5-QPlainTextEdit普通文本

    继承:QAbstractScrollArea QPlainText和QTextEdit大致功能实现差不多,不能有图片.框架.表格等 QPlainText是安行滚动的,QTextEdit是安像素滚动的 ...

  5. [每日一讲] Python系列:数字与运算符

    数字(数值)型 Python 数字数据类型用于存储数值.数据类型是不可变(immutable)的,这就意味着如果改变数字数据类型的值,将重新分配内存空间. Python 支持三种不同的数值类型: 整型 ...

  6. hihocoder 1582 : Territorial Dispute (计算几何)(2017 北京网络赛E)

    题目链接 题意:给出n个点.用两种颜色来给每个点染色.问能否存在一种染色方式,使不同颜色的点不能被划分到一条直线的两侧. 题解:求个凸包(其实只考虑四个点就行.但因为有板子,所以感觉这样写更休闲一些. ...

  7. Task6.PyTorch理解更多神经网络优化方法

    1.了解不同优化器 2.书写优化器代码3.Momentum4.二维优化,随机梯度下降法进行优化实现5.Ada自适应梯度调节法6.RMSProp7.Adam8.PyTorch种优化器选择 梯度下降法: ...

  8. 事物Spring boot @Transactional

    事物:dr @Override @UDS(value="fq") @Transactional public BaseResultMessage testTransactional ...

  9. prefetches

    用于设置预请求的所有url的列表,该部分URL,会在进入小程序后自动发起请求(优于开发者代码加载).当开发者再次发起request请求时可以增加cache参数,如果配置的prefetch请求已返回,则 ...

  10. es关闭不使用的index、真正删除文档

    因为只要索引处于open状态,就会占用内存+磁盘: 如果将索引close,只会占用磁盘 Curl -XPOST ‘hadoop01:9200/index/_close’ ------ 在es中删除文档 ...