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" classClass up = c.getSuperclass();Object obj = null;        try {// Requires default constructorobj = 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的访问。

转载于:https://www.cnblogs.com/kid551/p/4349098.html

Thinking in Java Chapter 14相关推荐

  1. 我的世界java一键修复_我的世界JAVA 1.14.2最新预览版发布 修复光源BUG

    原标题:我的世界JAVA 1.14.2最新预览版发布 修复光源BUG Minecraft 1.14.2 Pre-Release 3正式发布 更改内容: 修复几个BUG 现在加载存档时若遇到错误会向日志 ...

  2. halcon算子盘点:Chapter 13:对象、Chapter 14 区域

    Chapter 13:Object 13.1 Information 1. count_obj  功能:统计一个元组中的对象. 2. get_channel_info  功能:一幅目标图像组成部分的信 ...

  3. Java安装与环境配置(Java SE 14)

    Java(Java SE 14)安装与环境配置 前言 Java SE 14 下载 Java安装(新老版Java有区别,这里要看) Java配置 1.右击此电脑,打开属性.(不同的电脑,可能略有不同,可 ...

  4. Java学习-14 CSS与CSS3美化页面及网页布局

    Java学习-14 CSS与CSS3美化页面及网页布局 1. CSS简介 什么是CSS? CSS 指层叠样式表 (Cascading Style Sheets) 样式定义如何显示控制 HTML 元素, ...

  5. Chapter 14 Greenplum PL/R Language Extension

    Chapter 14 Greenplum PL/RLanguage Extension This chaptercontains the following information: •    Abo ...

  6. 14版计算机专业英语,Chapter 14 Virtual Reality 《计算机专业英语》电子教案.ppt

    Chapter 14 Virtual Reality <计算机专业英语>电子教案 Computer English Chapter 14 Virtual Reality *计算机专业英语 ...

  7. java ubuntu 14.04,Ubuntu 14.04安装java的方法

    摘要 腾兴网为您分享:Ubuntu 14.04安装java的方法,掌上电力,虚拟按键,新东方,小度等软件知识,以及公务用车平台,手机秀色直播,新视觉影院,浩辰cad批量打印,bartender2016 ...

  8. 【Java】14 JDBC编程学习总结

    文章目录 一.JDBC是什么? 二.为什么要使用JDBC? 三.DDL.DML.DCL分别代表什么 四.JDBC有三个操作步骤 实例代码 实例代码 实例代码 五.使用PreparedStatement ...

  9. java se 14 虚拟机规范

    https://cr.openjdk.java.net/~iris/se/14/latestSpec/java-se-14-jvms-diffs.pdf 只有链接,pdf

  10. java定时14点30分_单机定时任务的四种基本实现方式

    引言 在实际项目开发中,定时任务调度是经常会出现的一类需求. 定时任务的场景可以说非常广泛,例如: 购买某些视频网站的会员后,每天给会员送成长值,每月给会员送电影券 在保证最终一致性的场景中,利用定时 ...

最新文章

  1. 深入理解ES6 - var-let-const
  2. Nodejs 环境配置终极解决方案
  3. spring-注入list集合对象(值是对象)
  4. [XPath] XPath 与 lxml (三)XPath 坐标轴
  5. 《Ceph源码分析》——第2章,第2节Buffer
  6. android url回调json,【求助】本地页面如何取某个URL返回的json
  7. 前端学习(2968):实现路由跳转的两种方式
  8. iOS: 环信的推送
  9. 求1+2+3+...(信息学奥赛一本通-T1158)
  10. 【Flink】Flink 状态恢复报错 StateMigrationException For heap backendsthe new state serializer must not be
  11. mac os 设置汇总
  12. PostMan Pre-request Script 生成api签名
  13. Jetty使用教程(四:24-27)—Jetty开发指南
  14. Python: 生成器,yield
  15. php curl 请求失败,PHP CURL库之GET、POST数据大小限制导致请求失败解决方案
  16. BZOJ-1069 [SCOI2007]最大土地面积
  17. 批量下载哨兵数据的方法探索
  18. 五类推荐系统算法,非常好使,非常全面
  19. 怎么用html制作求职登记表,有步骤的编写个人简历 其效果更好
  20. 机器学习理论知识概述

热门文章

  1. c语言文本编辑器源代码_程序员专属的10个免费编程文本编辑器,哪个是你的最爱?...
  2. python入门到熟练要多久_python_入门到熟练_day02
  3. mysql基础之mariadb galera集群(多主)
  4. c#结构体与类的区别
  5. Android studio 不能识别(显示?????)部分手机的解决办法
  6. 阅读《构建执法》11-12章
  7. C++虚基类详解(转)
  8. iOS app启动图片及icon图片及应用名设置
  9. python 3 导入 迭代判断
  10. Python采集深圳美莱(仅思路)