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. 转载:c++深拷贝和浅拷贝

    文章来自:http://blog.csdn.net/u010700335/article/details/39830425 C++中类的拷贝有两种:深拷贝,浅拷贝:当出现类的等号赋值时,即会调用拷贝函 ...

  2. python大法好——面向对象

    python大法好——面向对象 Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的.本章节我们将详细介绍Python的面向对象编程. 如果你以前没 ...

  3. leetcode3

    public class Solution { public int LengthOfLongestSubstring(string s) { var dic = new Dictionary< ...

  4. jqGrid基本用法与示例

    转自:https://chuanlu.iteye.com/blog/1953544 一.jqGrid的基本用法 1.html页面 <!DOCTYPE html PUBLIC "-//W ...

  5. jschDemo

    jsch是java的sftp实现 import com.jcraft.jsch.*; import java.io.OutputStream; public class JschStart { pub ...

  6. mvc:view-controller标签使用

    mvc:view-controller可以在不需要Controller处理request的情况,转向到设置的View,完成无Controller的path和view的直接映射. 1.重定向 <m ...

  7. java.util.Stack类中的peek()方法

    java.util.stack类中常用的几个方法:isEmpty(),add(),remove(),contains()等各种方法都不难,但需要注意的是peek()这个方法. peek()查看栈顶的对 ...

  8. 3、支付结果 /items/result?point=1&orderNo=201903211035400001

    <template> <div> <div class="toppic"> <img src="../../../assets/ ...

  9. jq 切换功能toggle

    ---恢复内容开始--- $(document).ready(function () { $(".jianjie").click(function () { $(this).tog ...

  10. Python利用PIL生成随机验证码图片

    安装pillow: pip install pillow PIL中的Image等模块提供了创建图片,制作图片的功能,大致的步骤就是我们利用random生成6个随机字符串,然后利用PIL将字符串绘制城图 ...