From Thinking in Java 4th Edition

RTTI(Run-Time Type Information),运行时类型信息,使得你可以在程序运行时发现和使用类型信息。对RTTI的需要,揭示了面向对象设计中许多有趣(并且复杂)的问题,同时也提出了如何组织程序的问题。

Java是如何让我们在运行时识别对象和类的信息的。主要有两种方式:

1. “传统的”RTTI,它假定我们在编译时已经知道了所有的类型

2. “反射”机制,它允许我们在运行时发现和使用类型信息

通常会建立一个具体对象(Circle, Square, or Triangle),把它向上转型成Shape(忽略对象的具体类型),并在后面的程序中使用匿名的Shape引用:

import java.util.*;

abstract class Shape {
void draw() { System.out.println(this + ".draw()"); }
abstract public String toString();
} class Circle extends Shape {
public String toString() { return "Circle"; }
} class Square extends Shape {
public String toString() { return "Square"; }
} class Triangle extends Shape {
public String toString() { return "Triangle"; }
} public class Shapes {
public static void main(String[] args){
List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle()); for(Shape shape : shapeList)
shape.draw();
}
} /* Output:
Circle.draw()
Square.draw()
Triangle.draw()
*/

当从数组中取出元素时,这种容器——实际上它将所有的事物都当作Object持有——会自动将结果转换为Shap。这是RTTI最基本的使用形式,所有的类型转换都是在运行时进行正确检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。

这个例子中,RTTI的转换并不彻底: Object被转型为Shape,而不是转型为Circle, Square or Triangle。这是因为目前我们从List<Shape>只知道保存的都是Shape,在编译时,将由容器和Java泛型来强制确保这一点;而在运行时,由类型转换操作来确保这一点。

接下来就是多态机制了,Shape对象实际执行什么样的代码,是由引用所指向的具体对象Circle, Square o Triangle而决定的。

使用RTTI,可以查询某个Shape引用所指向的对象的确切类型,然后选择或者剔除特列。

Class对象

要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由称为Class对象的特殊对象完成的,它包含了与类有关的信息。

类是程序的一部分,每个类都有一个Class对象。每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行在这个程序的Java虚拟机将使用被称为“类加载器”的子系统。

类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。

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

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

// Examination of the way the class loader works.
import static net.mindview.util.Print.*; class Candy {
static { print("Loading Candy"); }
} class Gum {
static { print("Loading Gum"); }
} class Cookie {
static { print("Loading Cookie"); }
} public class SweetShop {
public static void main(String[] args){
print("inside main"); new Candy();
print("After creating Candy"); try {
Class.forName("Gum");
} catch(ClassNotFoundException e){
print("Couldn't find Gum");
}
print("After Class.forName(\"Gum\")"); new Cookie();
print("After creating Cookie");
}
} /* Output:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
*/

forName()是取得Class对象的引用的一种方法。它是由一个包含目标类的文本名的String作输入参数,返回的是一个Class对象的引用。对forName()的调用是为了它产生的“副作用”:如果类Gum还没有被加载,那么就加载它。在加载过程中,Gum的static子句就被执行。

无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径,因为你不需要为了获得Class的引用而持有该类型的对象

但是,如果你已经有了一个感兴趣的类型的对象,那就可以通过调用getClass()方法来获取Class引用(这个方法属于根类Object的一部分,它将返回表示该对象的实际类型的Class引用):

// Testing Class Class.
package Leetcode.DrJava;
import static net.mindview.util.Print.*; interface HasBatteries {}
interface Waterproof {}
interface Shoots {} class Toy {
// Comment out the following default constructor
// to see NoSuchMethodError from (*1*)
Toy() {}
Toy(int i) {}
} class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots {
FancyToy() { super(1); }
} public class ToyTest {
static void printInfo(Class cc){
print("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]");
print("Simple name: " + cc.getSimpleName());
print("Canonical name: " + cc.getCanonicalName());
} public static void main(String[] args){
Class c = null; // get the reference of a Class
try {
c = Class.forName("Leetcode.DrJava.FancyToy");
} catch(ClassNotFoundException e){
print("Can't find FancyToy");
System.exit(1);
}
printInfo(c); // return the interfaces in "c" Class.
for(Class face : c.getInterfaces())
printInfo(face); // return the superclass of the "c" class
Class up = c.getSuperclass();
Object obj = null;
try {
// Requires default constructor
obj = up.newInstance();
} catch(InstantiationException e){
print("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e){
print("Can't access");
System.exit(1);
}
printInfo(obj.getClass());
}
} /* Output:
Class name: Leetcode.DrJava.FancyToy is interface? [false]
Simple name: FancyToy
Canonical name: Leetcode.DrJava.FancyToy
Class name: Leetcode.DrJava.HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name: Leetcode.DrJava.HasBatteries
Class name: Leetcode.DrJava.Waterproof is interface? [true]
Simple name: Waterproof
Canonical name: Leetcode.DrJava.Waterproof
Class name: Leetcode.DrJava.Shoots is interface? [true]
Simple name: Shoots
Canonical name: Leetcode.DrJava.Shoots
Class name: Leetcode.DrJava.Toy is interface? [false]
Simple name: Toy
Canonical name: Leetcode.DrJava.Toy
*/

1. 在main()中,用forName()方法在适当的try语句块中,创建了一个Class引用,并将其初始化为指向FancyToy的Class。(forName中的参数用的是全限定名,即包含包名)。

2. printInfo()中使用getName()来产生权限定的类名;运用getSimpleName()来产生不包含包名的类名;运用getCanonicalName()来产生全限定的类名。

(getName()返回的是虚拟机里面的class的表示, 而getCanonicalName()返回的是更容易理解的表示)

3. Class.getInterfaces()方法返回的是Class对象,它们表示感兴趣的Class对象中所包含的接口

4. 如果有一个Class的对象,还可以使用getSuperclass()方法查询其直接基类。

5. Class的newInstance()方法是实现“虚拟构造器”的一种途径:“我不知道你的具体类型,但无论如何要正确地创建你自己”。

使用newInstance()方法来创建的类,必须带有默认的构造器。之后我们会看到如何利用Java的反射API,用任意的构造器来动态地创建类的对象。

类型字面常量

Java提供了另外一种方法来生成对Class对象的引用,即使用类型字面常量。对以上程序可以这样写:

FancyToy.class

这样做更简单、安全,并且在编译时就会受到检查。

当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象。为了使用类而做的准备工作实际包含三个步骤:

1. 加载,这是由类加载器执行的,检查字节码,并创建Class对象

2. 链接。验证类中的字节码,为静态域分配空间

3. 初始化。执行静态初始化器和静态初始化块。

初始化被延迟到了对静态方法或者非常数静态域进行首次引用时执行:

import java.util.*;

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"); // Does not trigger initialization:
System.out.println(Initable.staticFinal); // Does not trigger initialization:
System.out.println(Initable.staticFinal2); // Does not trigger initialization:
System.out.println(Initable2.staticNonFinal); Class initable3 = Class.forName("Initable3"); System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
} /* Output:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
*/

1. 从对initable引用的创建中可以看到,仅使用.class语法来获得对类的引用不会引发初始化。

2. 但是从initable3引用的创建可以看出,Class.forName()立即就进行了初始化。

3. 如果一个static final值是“编译期常量”,就像Initable.staticFinal那样,那么这个值不需要对Initable类进行初始化就可以读取

4. 但如果一个域仅仅是static和final的,还不足以确保3的行为,例如对Initable.staticFinal2的访问将强制进行类的初始化,因为它不是编译期常量

5. 如果一个static域不是final的,那么对它访问时,就要求在访问前进行链接(为这个域分配存储空间)和初始化(初始化该存储空间),就像对Initable2.statiNonFinal的访问。

Thinking in Java Chapter 14的更多相关文章

  1. 零元学Expression Blend 4 - Chapter 14 用实例了解布局容器系列-「Pathlistbox」II

    原文:零元学Expression Blend 4 - Chapter 14 用实例了解布局容器系列-「Pathlistbox」II 本章将延续上一章的范例,步骤解析. 本章将延续上一章的范例,步骤解析 ...

  2. 【译】Java SE 14 Hotspot 虚拟机垃圾回收调优指南

    原文链接:HotSpot Virtual Machine Garbage Collection Tuning Guide,基于Java SE 14. 本文主要包括以下内容: 优化目标与策略(Ergon ...

  3. Java SE 14 新增特性

    Java SE 14 新增特性 作者:Grey 原文地址:Java SE 14 新增特性 源码 源仓库: Github:java_new_features 镜像仓库: GitCode:java_new ...

  4. Java EE (14) -- SSH配置

    整合Spring与Struts1的三种方法总结 无论用那种方法来整合,第一步都是要装载spring的应用环境,有三种方式: #1. struts-config.xml <?xml version ...

  5. JAVA进阶14

    间歇性混吃等死,持续性踌躇满志系列-------------第14天 1.线程的加入 package code0328; import javax.swing.*; import java.awt.* ...

  6. Thinking in Java Chapter 13

    From Thinking in Java 4th Edition String对象是不可变的.String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包 ...

  7. Chapter 14. Blocks and Statements

    14.5. Statements There are many kinds of statements in the Java programming language. Most correspon ...

  8. Java虚拟机14:类加载器

    类与类加载器 虚拟机设计团队把类加载阶段张的"通过一个类的全限定名来获取此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这 ...

  9. java基础14 多态(及关键字:instanceof)

    面向对象的三大特征: 1.封装   (将一类属性封装起来,并提供set()和get()方法给其他对象设置和获取值.或者是将一个运算方法封装起来,其他对象需要此种做运算时,给此对象调用) 2.继承   ...

随机推荐

  1. logback的使用和logback.xml详解,在Spring项目中使用log打印日志

    logback的使用和logback.xml详解 一.logback的介绍 Logback是由log4j创始人设计的另一个开源日志组件,官方网站: http://logback.qos.ch.它当前分 ...

  2. Django02-路由系统urls

    一.路由配置系统(URLconf) 分为:静态路由动态路由 1.URL配置 URL配置(URLconf)就像Django所支撑网站的目录.它的本质是URL与该URL调用的视图函数之间的映射表 语法: ...

  3. 用react编写一个可以编辑的表格

    这只一个雏形,但是可以用了.难点是如何点击每行后面的编辑按钮,让当前行的格子都变成input. import {Component} from 'react' const Action = props ...

  4. spring的ioc与aop原理

    ioc(反向控制) 原理:    在编码阶段,既没有实例化对象,也没有设置依赖关系,而把它交给Spring,由Spring在运行阶段实例化.组装对象.这种做法颠覆了传统的写代码实例化.组装对象.然后一 ...

  5. [phvia/firman] PHP多进程服务器模型中的惊群

    [ 典型场景 ] 典型的多进程服务器模型是这样的,主进程绑定ip,监听port,fork几个子进程,子进程安装信号处理器,随后轮询资源描述符检查是否可读可写: 子进程的轮询又涉及到 IO复用,acce ...

  6. java应用健康检查

    本文主要针对自己手写shell监控应用状态,有可系统解决方案的,比如K8S,可以略过 #!/bin/sh#health_check.sh count=`ps -ef | grep test.jar | ...

  7. Linux Tomcat安装部署项目

    一.上传Tomcat服务器

  8. Cocos2dx开发之屏幕适配

    由于各种智能手机的屏幕大小都不一致,会出现同一张图片资源在不同的设备分辨率下显示不一样的问题.为避免这样的情况,需要Cocos引擎能提供多分辨率的支持,也就是说要求实现这样的效果 — 开发者不需要考虑 ...

  9. Python+Selenium学习--异常截图

    前言 Webdriver 提供错误截图函数get_screenshot_as_file(),可以帮助我们跟踪bug,在脚本无法继续执行时候, get_screenshot_as_file()函数将截取 ...

  10. GCD - Extreme (II) (欧拉函数妙用)

    https://cn.vjudge.net/problem/UVA-11426 题意:求 解题思路:我们可以定义一个变量dis[n],dis[n]意为1~(n-1)与n的gcd(最大公约数)的总和,那 ...