Thinking in Java Chapter 14
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的更多相关文章
- 零元学Expression Blend 4 - Chapter 14 用实例了解布局容器系列-「Pathlistbox」II
原文:零元学Expression Blend 4 - Chapter 14 用实例了解布局容器系列-「Pathlistbox」II 本章将延续上一章的范例,步骤解析. 本章将延续上一章的范例,步骤解析 ...
- 【译】Java SE 14 Hotspot 虚拟机垃圾回收调优指南
原文链接:HotSpot Virtual Machine Garbage Collection Tuning Guide,基于Java SE 14. 本文主要包括以下内容: 优化目标与策略(Ergon ...
- Java SE 14 新增特性
Java SE 14 新增特性 作者:Grey 原文地址:Java SE 14 新增特性 源码 源仓库: Github:java_new_features 镜像仓库: GitCode:java_new ...
- Java EE (14) -- SSH配置
整合Spring与Struts1的三种方法总结 无论用那种方法来整合,第一步都是要装载spring的应用环境,有三种方式: #1. struts-config.xml <?xml version ...
- JAVA进阶14
间歇性混吃等死,持续性踌躇满志系列-------------第14天 1.线程的加入 package code0328; import javax.swing.*; import java.awt.* ...
- Thinking in Java Chapter 13
From Thinking in Java 4th Edition String对象是不可变的.String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包 ...
- Chapter 14. Blocks and Statements
14.5. Statements There are many kinds of statements in the Java programming language. Most correspon ...
- Java虚拟机14:类加载器
类与类加载器 虚拟机设计团队把类加载阶段张的"通过一个类的全限定名来获取此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这 ...
- java基础14 多态(及关键字:instanceof)
面向对象的三大特征: 1.封装 (将一类属性封装起来,并提供set()和get()方法给其他对象设置和获取值.或者是将一个运算方法封装起来,其他对象需要此种做运算时,给此对象调用) 2.继承 ...
随机推荐
- python 一些方法函数
转Python学习笔记十一:列表(3)--列表的一些方法:http://www.cnblogs.com/dabiao/archive/2010/03/12/1683942.html python中的e ...
- ARM中R0-R15寄存器的作用
根据“ARM-thumb 过程调用标准”: 注意:在中断程序中,所有的寄存器都必须保护,编译器会自动保护R4-R11
- intellij idea 配置gitlab ssh key
1 安装git,登录官网https://www.git-scm.com/download/ ,选择相应系统版本,下载后安装好. 公司网慢的可以用第三方的软件管家下载. 2 打开git bash,不需要 ...
- Java学习笔记(十五):import关键字
- m3u8文件什么合成便于播放的MP4的方法
先大家要知道M3U8文件不是一个视频文件,里面全是一些路径,说白了就是一个目录而已,所以要看视频,要找到对应存放视频的文件夹.如果不知道怎么找,可以使用文本格式打开M3U8文件,里面会有路径提示. ...
- C++成员函数在内存中的存储方式
用类去定义对象时,系统会为每一个对象分配存储空间.如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间.按理说,如果用同一个类定义了10个对象,那么就需要分别为10个对象的数据和函数代码分 ...
- PTA 1067 Sort with Swap(0, i) (25 分)(思维)
传送门:点我 Given any permutation of the numbers {0, 1, 2,..., N−1}, it is easy to sort them in increasin ...
- @RequestBody配合@JsonFormat注解实现字符串自动转换成Date
2018年08月28日 16:41:15 混合动力火锅 阅读数:440 在controller中使用@RequestBody可以自动将字符串转换成int或者将int转换成字符串,但是Date类型则 ...
- 213. House Robber II 首尾相同的偷窃问题
[抄题]: You are a professional robber planning to rob houses along a street. Each house has a certain ...
- 396. Rotate Function 移动加权求和,取最大值
[抄题]: Given an array of integers A and let n to be its length. Assume Bk to be an array obtained by ...