第十四章 类型信息
第十四章 类型信息
类型信息是指,类的信息。在编程中,一种是面向类型编译,类型在编译时已确定;一种是运行时面向类型,是不知道具体类型的。比如面向接口编程,你只知道范化的接口是做什么的,不知道它的具体实现。这个时候就要用到类型识别RTTI(运行时类型识别)完成。
在Java中识别类和对象的信息主要有两种:一种是传统的RTTI,它假定在编译时已经知道类型;一种是“反射”,它允许我们在运行时发现使用类的信息。
这是一个多态的例子,List集合是以基类Shape作为存储元素的,这个时候存储的具体元素会“向上转型”为基类存储,但是这个数组容器实际是把所有的元素当做Object来持有的,当你取出时会“自动转型”为Shape,这是RTTI的一种体现(转型),运行时识别对象类型。
在Java中,每个类默认都有一个Class对象,它包含了类的有关信息,Java就是用它来执行RTTI的。
Class总是指向目标Class对象的,它可以获取类的信息,制造该类的实例。因此Class表示的就是它所指向的确切类型。
要理解RTTI在Java中的工作原理,必须知道类型信息在运行时是如何表示的。类是程序的一部分,为了生成这个类的对象,Java虚拟机将使用“类加载器”进行加载。所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态调用时,会主动加载这个类。类加载器首先检查这个类的Class对象是否已加载。如果未加载,默认类加载器会根据类名加载.class文件,在这个类被加载时,它会对这个文件进行验证,以确保其没有被破坏,一旦加载入内存,它就被用来创建这个类的所有对象。有关更多JVM的了解,请查绚相关资料。
无论何时,只要你想在运行时使用类型信息,就必须先获得该类Class对象的引用。获取Class对象的方式有两种,Class.forName("包名+类名")、类.class。这两种是有区别的,第一种会使类强行初始化部分,.class不会。例如:
static初始化是在类加载器进行加载时初始化,.class不会执行执行初始化的动作。需要注意,.class对类的常量的使用也是不会触发初始化。
运行时类型信息使得你可以在程序运行时发现和使用类型信息
从只能在编译期间执行面向对象设计中许多有趣(并且复杂的问题),同时也提出了如何组织程序问题,
- 编译时知道了所有的类型
- 反射机制,运行时发现和使用类的信息
14.1 为什么需要RTTI
RTTI(Run-time Type Identification
在运行时,识别对象类型,进行正确性检查
14.2 Class对象
理解RTTI在Java中的运行原理,必须知道类型信息在运行时是如何表示的。
Class对象的特殊对象完成的,它包含类与类有关的信息。用来创建类的所有从“常规”对象的。
每个类都有一个class对象。每当编写并且编译了一个新类,就会产生一个class对象(保存在同名的.class,为了生成这个类的对象,JVM被称为“类加载器”的子系统)
类加载器实际上包含一条加载器链
- 原生加载器,加载可信类,JavaAPI类
- 挂接额外的类加载器 有特殊需求(下载,web服务)
Java在开始运行之前,并非全部加载,各个部分在必须时才加载。
一旦某个类的对象被载入内存,它就被用来创建这个类的所有对象。
Class.forName("Gum")
这个方法是Class类的一个static成员,(所有Class对象都属于这个类)
只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。
- Class.forName()不需要为了获得Class引用而持有该类型的对象,传递给forName必须使用全限定名字(包含包名)
- Class.getClass() 拥有了一个感兴趣的对象
- Class.getInterface()
- 如果有了一个class对象,可以使用 getSuperclass()方法查询其直接基类,这将返回可以用来进一步查询的Class对象,因此你可以在运行时发现一个完整的运行的类继承结构
- newInstance()方法实现的是“虚拟构造器”的一种途径,虚拟构造器允许你声明:我不知道你的确定类型,但是无论如何要正确的创建你自己,必须带有默认的构造器
练习1
// In ToyTest.java, comment out Toy's default constructor and
// explain what happens.
interface HasBatteries {}
interface Waterproof {}
interface Shoots {}
class Toy {
// With default constructor commented out will be
// unable to instantiate this Toy, super of FancyToy:
//Toy() {}
Toy(int i) {}
}
class FancyToy extends Toy
implements HasBatteries, Waterproof, Shoots {
FancyToy() { super(1); }
}
public class ToyTest1 {
static void printInfo(Class cc) {
System.out.println("Class name: " + cc.getName() +
" is interface? [" + cc.isInterface() + "]");
System.out.println("Simple name: " + cc.getSimpleName());
System.out.println("Canonical name: " + cc.getCanonicalName());
}
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("第十四章类型信息.FancyToy");
} catch(ClassNotFoundException e) {
System.out.println("Can't find FancyToy");
System.exit(1);
}
printInfo(c);
for(Class face : c.getInterfaces())
printInfo(face);
Class up = c.getSuperclass();
Object obj = null;
try {
// Requires default constructor in order to
// create a super or Toy object:
obj = up.newInstance();
} catch(InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException i) {
System.out.println("Cannot access");
System.exit(1);
}
printInfo(obj.getClass());
}
}
==============================================================================
Class name: 第十四章类型信息.FancyToy is interface? [false]
Simple name: FancyToy
Canonical name: 第十四章类型信息.FancyToy
Class name: 第十四章类型信息.HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name: 第十四章类型信息.HasBatteries
Class name: 第十四章类型信息.Waterproof is interface? [true]
Simple name: Waterproof
Canonical name: 第十四章类型信息.Waterproof
Class name: 第十四章类型信息.Shoots is interface? [true]
Simple name: Shoots
Canonical name: 第十四章类型信息.Shoots
Cannot instantiate
练习2
// Incorporate a new kind of interface into ToyTest.java and
// verify that it is detected and displayed properly.
package typeinfo.toys;
import static net.mindview.util.Print.*;
interface HasBatteries {}
interface Waterproof {}
interface Shoots {}
interface Flies {}
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, Flies {
FancyToy() { super(1); }
}
public class ToyTest2 {
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;
try {
c = Class.forName("typeinfo.toys.FancyToy");
} catch(ClassNotFoundException e) {
print("Can't find FancyToy");
System.exit(1);
}
printInfo(c);
for(Class face : c.getInterfaces())
printInfo(face);
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 i) {
print("Cannot access");
System.exit(1);
}
printInfo(obj.getClass());
}
}
==============================================================================Class name: 第十四章类型信息.Flies is interface? [true]
Simple name: Flies
Canonical name: 第十四章类型信息.Flies
Cannot instantiate
练习3
/* Add Rhomboid to Shapes.java. Create a Rhomboid, upcast it to a Shape,
* then downcast it back to a Rhomboid. Try downcasting to a Circle and
* see what happens.'
*/
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"; }
}
class Rhomboid extends Shape {
public String toString() { return "Rhomboid"; }
}
public class Shapes3 {
public static void main(String[] args) {
// upcasting to Shape:
List<Shape> shapeList = Arrays.asList(
new Circle(), new Square(), new Triangle(), new Rhomboid()
);
// downcasting back to specific shape:
for(Shape shape : shapeList)
shape.draw();
Rhomboid r = new Rhomboid();
((Shape)r).draw();
// inconvertible types:
// ((Circle)r).draw();
}
}
==============================================================================
Circle.draw()
Square.draw()
Triangle.draw()
Rhomboid.draw()
Rhomboid.draw()
练习4
import java.util.*;
public class Shapes4 {
public static void main(String[] args) {
// upcasting to Shape:
List<Shape> shapeList = Arrays.asList(
new Circle(), new Square(), new Triangle(), new Rhomboid()
);
// downcasting back to specific shape:
for(Shape shape : shapeList)
shape.draw();
Rhomboid r = new Rhomboid();
// Upcast:
Shape s = (Shape)r;
s.draw();
// check type before downcast:
if(s instanceof Circle)
((Circle)s).draw();
else if(!(s instanceof Circle))
System.out.println("(Shape)r is not a Circle");
}
}
========================================================================
Circle.draw()
Square.draw()
Triangle.draw()
Rhomboid.draw()
Rhomboid.draw()
(Shape)r is not a Circle
练习5
/* Implement a rotate(Shape) method in Shapes.java, such that it checks
* to see if it is rotating a Circle (and, if so, doesn't perform the
* operation).
*/
import java.util.*;
public class Shapes5 {
public static void rotate(Shape s) {
if(!(s instanceof Circle))
System.out.println(s + " rotate");
}
public static void main(String[] args) {
// upcasting to Shape:
List<Shape> shapeList = Arrays.asList(
new Circle(), new Square(), new Triangle(), new Rhomboid()
);
// downcasting back to specific shape:
for(Shape shape : shapeList)
shape.draw();
System.out.println();
for(Shape shape : shapeList)
rotate(shape);
}
}
=========================================================================
Circle.draw()
Square.draw()
Triangle.draw()
Rhomboid.draw()
Square rotate
Triangle rotate
Rhomboid rotate
练习6
/* Modify Shapes.java so that it can "highlight" (set a flag in)
* all shapes of a particular type. The toString() method for each
* derived Shape should indicate whether that Shape is "highlighted."
*/
import java.util.*;
class Circle6 extends Shape {
boolean flag = false;
public String toString() {
return (flag ? "H" : "Unh") + "ighlighted " + "Circle";
}
}
class Square6 extends Shape {
boolean flag = false;
public String toString() {
return (flag ? "H" : "Unh") + "ighlighted " + "Square";
}
}
class Triangle6 extends Shape {
boolean flag = false;
public String toString() {
return (flag ? "H" : "Unh") + "ighlighted " + "Triangle";
}
}
public class Shapes6 {
public static void setFlag(Shape s) {
if(s instanceof Triangle6)
((Triangle6)s).flag = true;
}
public static void main(String[] args) {
// upcasting to Shape:
List<Shape> shapeList = Arrays.asList(
new Circle6(), new Square6(), new Triangle6()
);
for(Shape shape : shapeList) {
setFlag(shape);
System.out.println(shape);
}
}
}
==========================================================================
Unhighlighted Circle
Unhighlighted Square
Highlighted Triangle
练习7
/* Modify SweetShop.java so that each type of object creation is controlled
* by a command-line argument. That is, if your command line is "java
* SweetShop Candy," then only the Candy object is created. Notice how you
* can control which Class object are loaded via the command-line argument.
*/
class Candy {
static {
System.out.println("Loading Candy"); }
}
class Gum {
static {
System.out.println("Loading Gum"); }
}
class Cookie {
static {
System.out.println("Loading Cookie"); }
}
public class SweetShop7 {
public static void main(String[] args) {
if(args.length < 1) {
System.out.println("Usage: sweetName");
System.exit(0);
}
Class c = null;
try {
c = Class.forName(args[0]);
System.out.println("Enjoy your " + args[0]);
} catch(ClassNotFoundException e) {
System.out.println("Couldn't find " + args[0]);
}
}
}
=======================================================================
Loading Candy
练习8
// Write a method that takes an object and recursively prints all
// the classes in that object's hierarchy.
class A {}
class B extends A {}
class C extends B {}
public class Ex8 {
public static void Hierarchy(Object o) {
if(o.getClass().getSuperclass() != null) {
System.out.println(o.getClass() + " is a subclass of " +
o.getClass().getSuperclass());
try {
Hierarchy(o.getClass().getSuperclass().newInstance());
} catch(InstantiationException e) {
System.out.println("Unable to instantiate obj");
} catch(IllegalAccessException e) {
System.out.println("Unable to access");
}
}
}
public static void main(String[] args) {
Hierarchy(new C());
}
}
=============================================================================
class 第十四章类型信息.C is a subclass of class 第十四章类型信息.B
class 第十四章类型信息.B is a subclass of class 第十四章类型信息.A
class 第十四章类型信息.A is a subclass of class java.lang.Object
练习9
// Modify the previous exercise so that it uses Class.getDeclaredFields()
// to also display information about the fields in a class.
public class Ex9 {
public static void Hierarchy(Object o) {
Object[] fields = o.getClass().getDeclaredFields();
if(fields.length == 0)
System.out.println(o.getClass() + " has no fields");
if(fields.length > 0) {
System.out.println("Field(s) of " + o.getClass() + ":");
for(Object obj : fields)
System.out.println(" " + obj);
}
if(o.getClass().getSuperclass() != null) {
System.out.println(o.getClass() + " is a subclass of " +
o.getClass().getSuperclass());
try {
Hierarchy(o.getClass().getSuperclass().newInstance());
} catch(InstantiationException e) {
System.out.println("Unabloe to instantiate obj");
} catch(IllegalAccessException e) {
System.out.println("Unable to access");
}
}
}
public static void main(String[] args) {
Hierarchy(new C());
}
}
=================================================================
class 第十四章类型信息.C has no fields
class 第十四章类型信息.C is a subclass of class 第十四章类型信息.B
Field(s) of class 第十四章类型信息.B:
int 第十四章类型信息.B.i
class 第十四章类型信息.B is a subclass of class 第十四章类型信息.A
class 第十四章类型信息.A has no fields
class 第十四章类型信息.A is a subclass of class java.lang.Object
class java.lang.Object has no fields
练习10
// Write a program to determine whether an array of char is a primitive type
// or a true Object.
public class Ex10 {
public static void main(String[] args) {
char[] c = new char[10];
// c is an Object:
System.out.println("Superclass of char[] c: " +
c.getClass().getSuperclass());
System.out.println("char[] c instanceof Object: " +
(c instanceof Object));
}
}
=========================================================================
Superclass of char[] c: class java.lang.Object
char[] c instanceof Object: true
14.2.1 类字面常量
另一种方法来生成对Class对象的引用,类字面常量。
FancyToy.class 编译时就会进行检查
不仅应用于普通类,接口,基本数据类型。
.class 来创建对Class对象的引用时,不会自动地初始化该Class对象,为了使用类而做的准备工作实际包含三个步骤:
- 加载 : 类加载器执行的。查找字节码(在classpath所指定的路径中查找,并非是必须的),并从这些字节码中创建一个Class对象
- 链接:在链接阶段,验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用。
- 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态方法模块。
初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用。
仅通过.class语法来获得对类的引用不会引发初始化,为了产生class引用,class.forName()立即就进行初始化。
如果一个static final值是 “编译期常量”,这个值不需要对Initable类进行初始化就可以被读取。但是,只是将一个域设置为static和final的,还不足以确保这种行为。
如果这个static 域 不是final的,那么对他访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储)和初始化(初始化该存储对象)
14.2.2 泛化的class引用
Class引用总是指向某个Class对象,可以制造类的实例。 并包含可作用于这些实例的所有方法代码。他还包含静态成员
泛型类引用可以只能赋值为指向其声明的类型。
普通的类引用可以被重新赋值为指向任何其他的Class对象。普通的类引用可以被重新赋值为指向任何其他的Class对象。通过泛型语法,可以让编译器强制执行额外的类型检查。
为了使用泛化的class引用时放松限制,我使用了通配符,Java泛型的一部分。通配符就是“?”,表示“任何事物”。
Class<?> 的好处是它表示你并非是碰巧或者由于疏忽,而使用了一个非具体的类引用。
通配符于extends关键字结合,创建一个范围
14.2.3 新的类型转换
Class引用的转型语法,cast()方法
cast()方法接受参数对象,并将其转型为Class引用的类型。
14.3 类型转换前先做检查
迄今为止,我们已知的RTTI形式包括:
- 传统的类型转换,RTTI确保类型转换的正确性,执行错误的类型转换,抛出ClassCastException
- 代表对象类型的Class对象。通过查询Class对象可以获取运行时所需要的信息
- 第三种形式,instanceof 返回布尔值,告诉我们对象是不是某个特定类型的实例
- 只可将其与命名类型进行比较,而不能与class对象作比较
Pet
需要一种方法,随机创建不同类型的宠物,还可以创建宠物数组和List。
Java魔法堂:注解用法详解——@SuppressWarnings
@SuppressWarnings 注解不能直接置于静态初始化子句上
可以用 isInstance(types)方法动态替代 instanceof语句
14.4 注册工厂
fuckbaby!!!!!!!!!!!!!!!!!!!!!!!!
14.5 instanceof 与 Class的等价性
在查询类型信息时,以instanceof的形式(instanceof() 的形式 或isInstance()的形式,它们产生相同的结果)与直接比较Class对象有一个很重要的差别。
Instaceof保持了类型的概念,他指 你是这个类吗,或者你是这个类的派生类吗? == 比较实际的Class对象没有考虑继承(它是这个确切类型或者不是)
14.6 反射:运行时的类信息
RTTI可以告诉你,某个对象的确切类型,但是有一个限制:这个类在编译时必须已知
反射提供了一种机制——用来检查可用的方法,并返回方法名
人们想要在运行时获取类的信息的另一个动机,希望跨网络的远程平台创建和运行对象的能力,远程方法调用
Class类和Java.lang.reflect 类库一起对反射的概念进行了支持。
该类库包含了
- Field,Method,Constructor类(每个类实现了Member接口)。这些类型的对象在JVM运行时创建,用以表示未知类里对应的成员。
- 这样就可以通过 Constructor类来创建位置类的成员
- get和set方法读取和修改未知类Field对象关联的字段
- invoke方法调用未知类Method对象关联的方法
- 还可以调用 getFields() getMethods() getConstructor()等很遍历的方法,已返回表示字段,方法以及构造器的对象的数组。
- 这样匿名类的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情
- 通过反射与一个未知类的对象打交道时,JVM只是简单的检查这个对象,看它属于那个特定的类(就像RTTI那样)。
- 在用它做其他事情之前必须先加载那个类的Class对象。.class文件对于JVM时必须可获得的
RTTI与反射的区别
- RTTI 编译器在编译时打开和检查.class文件(换句话说,我们可以用“普通”方式调用对象的所有方法)
- 反射 .class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件
14.6.1 类方法提取器
反射在需要创建更加动态的代码时会很有用。
反射提供了足够的支持,使能够创建一个在编译时完全未知的对象,并调用此对象的方法,虽然开始的时候认为永远也不需要这些功能,但是反射的价值时很惊人的。
特别是如果不记得一个类是否有某个方法,或者不知道这个类究竟能做什么,又不想去查JDK文档,反射确实能节省很多时间
练习17
// Modify the regular expression in ShowMethods.java to additionally
// strip off the keywords native and final (hint: us the OR operator '|').
// {Args: ShowMethods17}
import java.lang.reflect.*;
import java.util.regex.*;
public class ShowMethods17 {
private static String usage =
"usage:\n" +
"ShowMethods qualified.class.name\n" +
"To show all methods in class or:\n" +
"ShowMethods qualified.class.name word\n" +
"To search for methods involving 'word'";
private static Pattern p = Pattern.compile("(\\w+\\.)|\\s*final|\\s*native");
public static void main(String[] args) {
if(args.length < 1) {
System.out.println(usage);
System.exit(0);
}
int lines = 0;
try {
Class<?> c = Class.forName(args[0]);
Method[] methods = c.getMethods();
Constructor[] ctors = c.getConstructors();
if(args.length == 1) {
for(Method method : methods) {
System.out.println(p.matcher(method.toString()).replaceAll(""));
}
for(Constructor ctor : ctors)
System.out.println(p.matcher(ctor.toString()).replaceAll(""));
lines = methods.length + ctors.length;
} else {
for(Method method : methods)
if(method.toString().indexOf(args[1]) != -1) {
System.out.println(p.matcher(method.toString()).replaceAll(""));
lines++;
}
for(Constructor ctor : ctors)
if(ctor.toString().indexOf(args[1]) != -1) {
System.out.println(p.matcher(ctor.toString()).replaceAll(""));
lines++;
}
}
} catch(ClassNotFoundException e) {
System.out.println("No such class: " + e);
}
}
}
练习18
getDeclaredConstructor
- 在Java反射机制中,此方法为暴力获取一个Class文件的私有构造。
// In ToyTest.java, use reflection to create a Toy object using
// the non-default constructor.
import java.lang.reflect.*;
class Toy19 {
Toy19() {
System.out.println("Creating Toy() object");
}
Toy19(int i) {
System.out.println("Creating Toy(" + i + ") object");
}
}
public class ToyTest19 {
public static void main(String[] args) {
// get appropriate constructor and create new instance:
try {
Toy19.class.getDeclaredConstructor(int.class).newInstance(1);
// catch four exceptions:
} catch(NoSuchMethodException e) {
System.out.println("No such method: " + e);
} catch(InstantiationException e) {
System.out.println("Unable make Toy: " + e);
} catch(IllegalAccessException e) {
System.out.println("Unable access: " + e);
} catch(InvocationTargetException e) {
System.out.println("Target invocation problem: " + e);
}
}
}
====================================================================
Creating Toy(1) object
14.7 动态代理
如果你想要将额外的操作从“实际”对象中分离到不同的地方,特别是当你能够很容易地做出修改,从没有使用额外操作转为使用这些操作,或者反过来时,代理就很有用了。
代理是基本的设计模式之一(设计模式的关键就是封装修改——因此你需要修改书屋以证明这种模式的正确性),她为你提供额外的或不同的操作,通常充当着中间人的角色
Java的动态代理比理想的代理更向前迈进了一步,可以动态地创建代理并动态地处理对所代理方法地调用。在动态代理上所做的所有调用都会被重定向到单一地的用处理器上,它的作用是揭示调用的类型并确定相应的对策。
调用静态方法Proxy.newProxyInstance()可以创建动态代理
练习21
// Modify SimpleProxyDemo.java so that it measures method-call times.
import java.util.*;
interface Interface {
void doSomething();
void somethingElse(String arg);
}
class RealObject implements Interface {
public void doSomething() {
System.out.println("doSomething"); }
public void somethingElse(String arg) {
System.out.println("somethingElse " + arg);
}
}
class SimpleProxy implements Interface {
private Interface proxied;
private static int doCount = 0;
private static int sECount = 0;
public SimpleProxy(Interface proxied) {
this.proxied = proxied;
}
public void doSomething() {
long timeIn = new Date().getTime();
System.out.println("Time called doSomething() " + doCount + ": " + timeIn + " msecs");
System.out.println("on " + new Date());
doCount++;
proxied.doSomething();
System.out.println("Call-return time = " + ((new Date().getTime()) - timeIn) + " msecs");
}
public void somethingElse(String arg) {
long timeIn = new Date().getTime();
System.out.println("Time called somethingElse() " + sECount + ": " + timeIn + " msecs");
System.out.println("on " + new Date());
sECount++;
proxied.somethingElse(arg);
System.out.println("Call-return time = " + ((new Date().getTime()) - timeIn) + " msecs");
}
}
class SimpleProxyDemo21 {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
consumer(new RealObject());
System.out.println();
consumer(new SimpleProxy(new RealObject()));
System.out.println();
consumer(new SimpleProxy(new RealObject()));
System.out.println();
consumer(new SimpleProxy(new RealObject()));
}
}
=========================================================================
doSomething
somethingElse bonobo
Time called doSomething() 0: 1614267771937 msecs
on Thu Feb 25 23:42:51 CST 2021
doSomething
Call-return time = 16 msecs
Time called somethingElse() 0: 1614267771953 msecs
on Thu Feb 25 23:42:51 CST 2021
somethingElse bonobo
Call-return time = 1 msecs
Time called doSomething() 1: 1614267771954 msecs
on Thu Feb 25 23:42:51 CST 2021
doSomething
Call-return time = 0 msecs
Time called somethingElse() 1: 1614267771954 msecs
on Thu Feb 25 23:42:51 CST 2021
somethingElse bonobo
Call-return time = 0 msecs
Time called doSomething() 2: 1614267771954 msecs
on Thu Feb 25 23:42:51 CST 2021
doSomething
Call-return time = 0 msecs
Time called somethingElse() 2: 1614267771954 msecs
on Thu Feb 25 23:42:51 CST 2021
somethingElse bonobo
Call-return time = 0 msecs
练习22
import java.lang.reflect.*;
import java.util.*;
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long timeIn = new Date().getTime();
System.out.println("**** proxy: " + proxy.getClass() +
", method: " + method + ", args: " + args +
", invoked at " + timeIn + " on " + (new Date()));
if(args != null)
for(Object arg : args)
System.out.println(" " + args);
long timeOut = new Date().getTime();
System.out.println("Method call-return time: " + (timeOut - timeIn) + " msecs");
return method.invoke(proxied, args);
}
}
class SimpleDynamicProxy22 {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
// Insert a proxy and call again:
Interface proxy = (Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{ Interface.class },
new DynamicProxyHandler(real));
consumer(proxy);
}
}
========================================================================
doSomething
somethingElse bonobo
**** proxy: class 第十四章类型信息.$Proxy0, method: public abstract void 第十四章类型信息.Interface.doSomething(), args: null, invoked at 1614268046382 on Thu Feb 25 23:47:26 CST 2021
Method call-return time: 18 msecs
doSomething
**** proxy: class 第十四章类型信息.$Proxy0, method: public abstract void 第十四章类型信息.Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@6e0be858, invoked at 1614268046400 on Thu Feb 25 23:47:26 CST 2021
[Ljava.lang.Object;@6e0be858
Method call-return time: 0 msecs
somethingElse bonobo
练习23
/*
* trying to print proxy leads to:
* StackOverFlowError
* at AbstractStringBuilder.<init>(Unknown Source)
* at StringBuilder.<init>(Unknown Source)
* at DynamicProxyHandler.invoke(SimpleDynamicProxy23.java)
* at $Proxy0.toString(Unknown Source)
* at String.valueOf(Unknown Source)
* at StrinbBuilcer.append(Unknown Source)
* at DynamicProxyHandler.invoke(SimpleDynamicProxy23.java), etc,
* probably due to infinite recursion because calls to toString()
* are passed repeatedly back to this invoke method
*/
// System.out.println("proxy: " + proxy); // error
14.8 空对象
当使用内置的null表示缺少对象时,在每次使用引用时都必须测试其是否为null(NullPointerException),这很枯燥。
空对象(接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何“真实”对象的值),通过这种方式,可以假设所有的对象都是有效的,不必浪费编程精力去检查null。
isstanceof可以探测空对象
14.8.1 模拟对象与桩
空对象的逻辑变体是 模拟对象与桩。创建出来是为了处理各种不同的测试情况,桩只是返回桩数据,它通常是重量级的,并且经常在测试之间被复用。桩可以根据它们被调用的方式,通过桩进行修改。
14.9 接口与类型信息
Interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。
14.10 总结
RTTI允许通过匿名基类的引用来发现类型信息,面向对象编程语言的目的是我们凡是在可以使用的地方都使用了多态机制,只在必须的时候使用RTTI
使用多态我们必须拥有基类的控制权
基类是别人的类,由别人控制,RTTI:可继承新类,然后添加你需要的方法。在代码的其他地方,可以检查你自己的特定类型,并调用你自己的方法。
如果是为了某个特定类的利益,而将某个特性放进基类里。意味着从那个基类派生出的其他所有子类都带有这些可能无意义的东西。
反射允许更加动态的编写代码
练习26
// Implement clearSpitValve() as described in the summary.
/* Solution includes, in same package:
* import java.util.*;
* public class RandomInstrumentGenerator {
* private Random rand = new Random();
* public Instrument next() {
* switch(rand.nextInt(7)) {
* default:
* case 0: return new Wind();
* case 1: return new Percussion();
* case 2: return new Stringed();
* case 3: return new Keyboard();
* case 4: return new Brass();
* case 5: return new Woodwind();
* case 6: return new Piano();
* }
* }
* }
*/
import polymorphism.music.Note;
import static net.mindview.util.Print.*;
class Instrument {
void play(Note n) { print("Instrument.play() " + n); }
public String toString() { return "Instrument"; }
void adjust() { print("Adjusting Instrument"); }
}
class Wind extends Instrument {
void play(Note n) { print("Wind.play() " + n); }
public String toString() { return "Wind"; }
void adjust() { print("Adjusting Wind"); }
void clearSpitValve() { print("Wind clearing spit valve"); }
}
class Percussion extends Instrument {
void play(Note n) { print("Percussion.play() " + n); }
public String toString() { return "Percussion"; }
void adjust() { print("Adjusting Percussion"); }
}
class Stringed extends Instrument {
void play(Note n) { print("Stringed.play() " + n); }
public String toString() { return "Stringed"; }
void adjust() { print("Adjusting Stringed"); }
}
class Keyboard extends Instrument {
void play(Note n) { print("Keyboard.play() " + n); }
public String toString() { return "Keyboard"; }
void adjust() { print("Adjusting Keyboard"); }
}
class Brass extends Wind {
void play(Note n) { print("Brass.play() " + n); }
public String toString() { return "Brass"; }
void adjust() { print("Adjusting Brass"); }
void clearSpitValve() { print("Brass clearing spit valve"); }
}
class Woodwind extends Wind {
void play(Note n) { print("Woodwind.play() " + n); }
public String toString() { return "Woodwind"; }
void clearSpitValve() { print("Woodwind clearing spit valve"); }
}
class Piano extends Keyboard {
void play(Note n) { print("Piano.play() " + n); }
public String toString() { return "Piano"; }
}
public class Music26 {
// Doesn't care about type, so new types
// added to the system still work right:
public static void tune(Instrument i) {
//...
i.play(Note.MIDDLE_C);
}
public static void tune
All(Instrument[] e) {
for(Instrument i : e)
tune(i);
}
private static RandomInstrumentGenerator gen = new RandomInstrumentGenerator();
public static void main(String[] args) {
// Upcasting during addition to the array:
Instrument[] orchestra = new Instrument[20];
// fill up orchestra array wth instruments:
for(int i = 0; i < orchestra.length; i++)
orchestra[i] = gen.next();
for(Instrument i : orchestra) {
if(i instanceof Wind) // get RTTI
((Wind)i).clearSpitValve();
i.adjust();
}
tuneAll(orchestra);
}
}