目录
1.2 Class对象用来生成常规对象(常规对象:非Class对象)
运行时类型信息使得你可以在程序运行时发现和使用类型信息。
本章将讨论Java 是如何让我们在运行时识别对象和类的信息的(如何在运行阶段,识别对象和类的信息)。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型,另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
RTTI:在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI的含义:在运行时,识别一个对象的类型。
本章的主题围绕着”Java如何让我们在程序在运行的时候,识别出对象和类型信息“问题进行展开的。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型,另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
多态中表现的类型转换是RTTI最基本的使用形式,但这种转换并不彻底。如数组容器实际上将所有元素当作Object持有,取用时再自动将结果转型回声明类型。而数组在填充(持有)对象时,具体类型可能是声明类型的子类,这样放到数组里就会向上转型为声明类型,持有的对象就丢失了具体类型。而取用时将由Object只转型回声明类型,并不是具体的子类类型,所以这种转型并不彻底。
多态中表现了具体类型的行为,但那只是“多态机制”的事情,是由引用所指向的具体对象而决定的,并不等价于在运行时识别具体类型。
以上揭示了一个问题就是具体类型信息的丢失!有了问题,就要解决问题,这就是RTTI的需要,即在运行时确定对象的具体类型。
- package net.mrliuli.rtti;
-
- import java.util.Arrays;
- import java.util.List;
-
- /**
- * Created by leon on 2017/12/3.
- */
-
- abstract class Shape{
- void draw(){
- System.out.println(this + ".draw()");
- }
- abstract public String toString(); //要求子类需要实现 toString()
- }
-
- class Circle extends Shape{
- @Override
- public String toString() {
- return "Circle";
- }
- public void drawCircle(){}
- }
-
- class Square extends Shape{
- @Override
- public String toString() {
- return "Square";
- }
- }
-
- class Triangle extends Shape{
- @Override
- 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() // 向上转型为 Shape,此处会丢失原来的具体类型信息!!对于数组而言,它们只是Shape类对象!
- );
- for(Shape shape : shapeList){
- shape.draw(); // 数组实际上将所有事物都当作Object持有,在取用时会自动将结果转型回声明类型即Shape。
- }
- //shapeList.get(0).drawCircle(); //这里会编译错误:在Shape类中找不到符号drawCircle(),证实了具体类型信息的丢失!!
- }
- }
在这个例子中,当把Shape对象放入List
在这个例子中,RTTI类型转换并不彻底:Object被转型为Shape,而不是转型为Cirele、Square或者Triangle。这是因为目前我们只知道这个Liste
接下来就是多态机制的事情了,Shape对象实际执行什么样的代码,是由引用所指向的具体对象Circle、Square或者Triangle而决定的。通常,也正是这样要求的,你希望大部分代码尽可能少地了解对象的具体类型,而是只与对象家族中的一个通用表示打交道(在这个例子中Shape)。这样代码会更容易写,更容易读,且更便于维护,设计也更容易实现、理解和改变。所以“多态”是面向对象编程的基本目标。
详细讲解请参考:
Think in java(五)RTTI的的三种形式、类型信息、class.forname与.class的区别___2018__的博客-CSDN博客
要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的。
要能够在运行时识别具体类型,说明必然有东西在运行时保存了具体类型信息,这个东西就是Class对象,一种特殊对象。即Class对象表示了运行时的类型信息,它包含了与类有关的信息。只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。
类是程序的一部分,每个类都有一个Class对象(包括接口、抽象类)。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统(原生类加载器加载的是所调的可信类,包括lava API类,它们通常是从本地盘加载的)。
所有的类都是在对其第一次使用时,动态加载到JVM中的。Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。
类加载流程:加载、验证、准备、解析、初始化(静态方法、静态代码块)。
.class是获取Class对象引用的另一种方法。如 FancyToy.class。建议使用这种方法。以下程序展示Class包含的很多有用的方法:
Class引用在编译期不具备任何更进一步的类型信息,所以它返回的只是一个Object引用,但是这个Object引用指向的是这个Class引用所代表的具体类型。即需要转型到具体类型才能给它送Object以外的消息。
newInstance()这个方法依赖于Class对象所代表的类必须具有可访问的默认的构造函数(Nullary constructor,即无参的构造器),否则会抛出InstantiationException 或IllegalAccessException 异常
- package net.mrliuli.rtti;
-
-
-
- interface HasBatteries{}
- interface Waterproof{}
- interface Shoots{}
-
- class Toy{
- Toy(){}
- Toy(int i){}
- }
-
- class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots{
- FancyToy(){ super(1); }
- }
-
- public class ToyTest {
- 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("net.mrliuli.rtti.FancyToy");
- }catch (ClassNotFoundException e){
- System.out.println("Can't find FancyToy");
- System.exit(1);
- }
- printInfo(c);
- System.out.println("=============================");
- for(Class face : c.getInterfaces()){
- printInfo(face);
- }
- System.out.println("=============================");
- Class up = c.getSuperclass();
- Object obj = null;
- try{
- // Requires default constructor:
- obj = up.newInstance();
- }catch (InstantiationException e){
- System.out.println("Cannot instantiate");
- System.exit(1);
- }catch (IllegalAccessException e){
- System.out.println("Cannot access");
- System.exit(1);
- }
- printInfo(obj.getClass());
- }
- }
初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行,即初始化有效地实现了尽可能 的“惰性”。
以下程序证实了上述观点。注意,将一个域设置为static 和 final的,不足以成为“编译期量”或“常数静态域”,如 static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);就不是编译期常量,对它的引用将强制进行类的初始化。
- package net.mrliuli.rtti;
-
- import java.util.Random;
-
- 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 initalbe = Initable.class; // 使用类字面常量.class获取Class对象引用,不会初始化
- System.out.println("After creating Initable ref");
- System.out.println(Initable.staticFinal); // 常数静态域首次引用,不会初始化
- System.out.println(Initable.staticFinal2); // 非常数静态域首次引用,会初始化
- System.out.println(Initable2.staticNonFinal); // 非常数静态域首次引用,会初始化
- Class initable3 = Class.forName("net.mrliuli.rtti.Initable3"); // 使用Class.forName()获取Class对象引用,会初始化
- System.out.println("After creating Initable3 ref");
- System.out.println(Initable3.staticNonFinal); // 已初始化过
- }
-
- }
Class引用总是指向某个Class对象,此时,这个Class对象可以是各种类型的,当使用泛型语法对Class引用所指向的Class对象的类型进行限定时,这就使得Class对象的类型变得具体,这样编译器编译时也会做一些额外的类型检查工作。如
- package net.mrliuli.rtti;
-
- public class GenericClassReferences {
- public static void main(String[] args){
- Class intClass = int.class;
- Class
genericIntClass = int.class; - genericIntClass = Integer.class; // Same thing
- intClass = double.class;
- // genericIntClass = double.class; // Illegal, genericIntClass 限制为Integer 的Class对象
- }
- }
通配符?是Java泛型的一部分,?表示“任何事物”。以下程序中Class> intClass = int.class; 与 Class intClass = int.class; 是等价的,但使用Class>优于使用Class,因为它说明了你是明确要使用一个非具体的类引用,才选择了一个非具体的版本,而不是由于你的疏忽。
- package net.mrliuli.rtti;
-
-
- public class WildcardClassReferences {
- public static void main(String[] args){
- Class> intClass = int.class;
- intClass = double.class;
- }
- }
将通配符与extends关键字相结合如Class extends Number>,就创建了一个范围,使得这个Class引用被限定为Number类型或其子类型。
- package net.mrliuli.rtti;
-
-
- public class BoundedClassReferences {
- public static void main(String[] args){
- Class extends Number> bounded = int.class;
- bounded = double.class;
- bounded = Number.class;
- // Or anything derived from Number
- }
- }
当调用newInstance()方法的时候,返回的是Number类型,非具体类型。
泛型类语法示例:
- package net.mrliuli.rtti;
-
- import java.util.ArrayList;
- import java.util.List;
-
-
-
- class CountedInteger{
- private static long counter;
- private final long id = counter++;
- public String toString(){
- return Long.toString(id);
- }
- }
-
- public class FilledList
{ - private Class
type; - public FilledList(Class
type) { - this.type = type;
- }
- public List
create(int nElements){ - List
result = new ArrayList(); - try{
- for(int i = 0; i < nElements; i++){
- result.add(type.newInstance());
- }
- }catch(Exception e){
- throw new RuntimeException(e);
- }
- return result;
- }
- public static void main(String[] args){
- FilledList
fl = new FilledList(CountedInteger.class); // 存储一个类引用 - System.out.println(fl.create(15)); // 产生一个list
- }
- }
总结,使用泛型类后
.newInstance()将返回确切类型的对象,而不是Object对象RTTI有三种形式。
前面我们讲了前两种,现在我们开始讲解第三种instanceof的形式:
它返回一个布尔值,告诉我们对象是不是某个特定类型或其子类。如if(x instanceof Dog)语句会检查对象x是否从属于Dog类。
还一种形式是动态的instanceof:Class.isInstance()方法提供了一种动态地测试对象的途径。Class.isInstance()方法使我们不再需要instanceof表达式。
在查询类型信息时,以instanceof的形式(即以instanceof的形式或isInstance0的形式,它们产生相同的结果)与直接比较Class对象有一个很重要的差别。下面的例子展示了这种差别:
- //: typeinfo/FamilyVsExactType.java
- // The difference between instanceof and class
- package typeinfo;
- import static net.mindview.util.Print.*;
-
- class Base {}
- class Derived extends Base {}
-
- public class FamilyVsExactType {
- static void test(Object x) {
- print("Testing x of type " + x.getClass());
- print("x instanceof Base " + (x instanceof Base));
- print("x instanceof Derived "+ (x instanceof Derived));
- print("Base.isInstance(x) "+ Base.class.isInstance(x));
- print("Derived.isInstance(x) " +
- Derived.class.isInstance(x));
- print("x.getClass() == Base.class " +
- (x.getClass() == Base.class));
- print("x.getClass() == Derived.class " +
- (x.getClass() == Derived.class));
- print("x.getClass().equals(Base.class)) "+
- (x.getClass().equals(Base.class)));
- print("x.getClass().equals(Derived.class)) " +
- (x.getClass().equals(Derived.class)));
- }
- public static void main(String[] args) {
- test(new Base());
- test(new Derived());
- }
- }
-
- /* Output:
- Testing x of type class typeinfo.Base
- x instanceof Base true
- x instanceof Derived false
- Base.isInstance(x) true
- Derived.isInstance(x) false
- x.getClass() == Base.class true
- x.getClass() == Derived.class false
- x.getClass().equals(Base.class)) true
- x.getClass().equals(Derived.class)) false
- Testing x of type class typeinfo.Derived
- x instanceof Base true
- x instanceof Derived true
- Base.isInstance(x) true
- Derived.isInstance(x) true
- x.getClass() == Base.class false
- x.getClass() == Derived.class true
- x.getClass().equals(Base.class)) false
- x.getClass().equals(Derived.class)) true
- *///:~
instanceof 和 isInstance() 保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”== 和 equals() 没有考虑继承——它要么是这个确切的类型,要么不是。
反射的概念:在程序运行时,动态的获取类的属性和方法。
Class类与jaya.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组(在JDK文档中,通过查找Class类可了解更多相关资料)。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
反射机制提供了一种方法,使我们能够编写可以自动展示完整接口的简单工具。下面就是其工作方式:
- //: typeinfo/ShowMethods.java
- // Using reflection to show all the methods of a class,
- // even if the methods are defined in the base class.
- // {Args: ShowMethods}
- import java.lang.reflect.*;
- import java.util.regex.*;
- import static net.mindview.util.Print.*;
-
- public class ShowMethods {
- 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+\\.");
- public static void main(String[] args) {
- if(args.length < 1) {
- print(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)
- print(
- p.matcher(method.toString()).replaceAll(""));
- for(Constructor ctor : ctors)
- print(p.matcher(ctor.toString()).replaceAll(""));
- lines = methods.length + ctors.length;
- } else {
- for(Method method : methods)
- if(method.toString().indexOf(args[1]) != -1) {
- print(
- p.matcher(method.toString()).replaceAll(""));
- lines++;
- }
- for(Constructor ctor : ctors)
- if(ctor.toString().indexOf(args[1]) != -1) {
- print(p.matcher(
- ctor.toString()).replaceAll(""));
- lines++;
- }
- }
- } catch(ClassNotFoundException e) {
- print("No such class: " + e);
- }
- }
- } /* Output:
- public static void main(String[])
- public native int hashCode()
- public final native Class getClass()
- public final void wait(long,int) throws InterruptedException
- public final void wait() throws InterruptedException
- public final native void wait(long) throws InterruptedException
- public boolean equals(Object)
- public String toString()
- public final native void notify()
- public final native void notifyAll()
- public ShowMethods()
- *///:~
代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色。下面是一个用来展示代理结构的简单示例:
- //: typeinfo/SimpleProxyDemo.java
- import static net.mindview.util.Print.*;
-
- interface Interface {
- void doSomething();
- void somethingElse(String arg);
- }
-
- class RealObject implements Interface {
- public void doSomething() { print("doSomething"); }
- public void somethingElse(String arg) {
- print("somethingElse " + arg);
- }
- }
-
- class SimpleProxy implements Interface {
- private Interface proxied;
- public SimpleProxy(Interface proxied) {
- this.proxied = proxied;
- }
- public void doSomething() {
- print("SimpleProxy doSomething");
- proxied.doSomething();
- }
- public void somethingElse(String arg) {
- print("SimpleProxy somethingElse " + arg);
- proxied.somethingElse(arg);
- }
- }
-
- class SimpleProxyDemo {
- public static void consumer(Interface iface) {
- iface.doSomething();
- iface.somethingElse("bonobo");
- }
- public static void main(String[] args) {
- consumer(new RealObject());
- consumer(new SimpleProxy(new RealObject()));
- }
- } /* Output:
- doSomething
- somethingElse bonobo
- SimpleProxy doSomething
- doSomething
- SimpleProxy somethingElse bonobo
- somethingElse bonobo
- *///:~
因为consumer()接受的Interface,所以它无法知道正在获得的到底是RealObject还是SimpleProxy,因为这二者都实现了Interface。但是SimpleProxy已经被插入到了客户端和RealObject之间,因此它会执行操作,然后调用RealObject上相同的方法。
Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。下面是用动态代理重写的SimpleProxyDemojava:
- //: typeinfo/SimpleDynamicProxy.java
- import java.lang.reflect.*;
-
- 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 {
- System.out.println("**** proxy: " + proxy.getClass() +
- ", method: " + method + ", args: " + args);
- if(args != null)
- for(Object arg : args)
- System.out.println(" " + arg);
- return method.invoke(proxied, args);
- }
- }
-
- class SimpleDynamicProxy {
- 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);
- }
- } /* Output: (95% match)
- doSomething
- somethingElse bonobo
- **** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null
- doSomething
- **** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@42e816
- bonobo
- somethingElse bonobo
- *///:~
通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器(你通常可以从已经被加载的对象中获取其类加载器,然后传递给它),一个你希望该代理实现的接口列表(不是类或抽象类),以及InvocationHlandler接口的一个实现。
invoke()方法中传递进来了代理对象,以防你需要区分请求的来源,但是在许多情况下,你并不关心这一点。然而,在invoke()内部,在代理上调用方法时需要格外当心,因为对接口的调用将被重定向为对代理的调用。
通常,你会执行被代理的操作,然后使用Method.invoke()将请求转发给被代理对象,并传人必需的参数。这初看起来可能有些受限,就像你只能执行泛化操作一样。但是,你可以通过传递其他的参数,来过滤某些方法调用:
- //: typeinfo/SelectingMethods.java
- // Looking for particular methods in a dynamic proxy.
- import java.lang.reflect.*;
- import static net.mindview.util.Print.*;
-
- class MethodSelector implements InvocationHandler {
- private Object proxied;
- public MethodSelector(Object proxied) {
- this.proxied = proxied;
- }
- public Object
- invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- if(method.getName().equals("interesting"))
- print("Proxy detected the interesting method");
- return method.invoke(proxied, args);
- }
- }
-
- interface SomeMethods {
- void boring1();
- void boring2();
- void interesting(String arg);
- void boring3();
- }
-
- class Implementation implements SomeMethods {
- public void boring1() { print("boring1"); }
- public void boring2() { print("boring2"); }
- public void interesting(String arg) {
- print("interesting " + arg);
- }
- public void boring3() { print("boring3"); }
- }
-
- class SelectingMethods {
- public static void main(String[] args) {
- SomeMethods proxy= (SomeMethods)Proxy.newProxyInstance(
- SomeMethods.class.getClassLoader(),
- new Class[]{ SomeMethods.class },
- new MethodSelector(new Implementation()));
- proxy.boring1();
- proxy.boring2();
- proxy.interesting("bonobo");
- proxy.boring3();
- }
- } /* Output:
- boring1
- boring2
- Proxy detected the interesting method
- interesting bonobo
- boring3
- *///:~
这里,我们只查看了方法名,但是你还可以查看方法签名的其他方面,甚至可以搜索特定的参数值。
动态代理并非是你日常使用的工具,但是它可以非常好地解决某些类型的问题。你在《Thinking in Patterns》(查看www.MindView.net)和Erich Gamma等人撰写的《DesignPatterns》°这两本书中了解到有关代理和其他设计模式的更多知识。
通过使用反射,可以到达并调用一个类的所有方法,包括私有方法!如果知道方法名,就可以在其
Method对象上调用setAccessible(true),然后访问私有方法。
以下命令显示类的所有成员,包括私有成员。-private标志表示所有成员都显示。
javap -private 类名
因此任何人都可以获取你最私有的方法的名字和签名,即使这个类是私有内部类或是匿名内部类。
- package net.mrliuli.rtti;
-
- /**
- * Created by li.liu on 2017/12/6.
- */
-
- import java.lang.reflect.Method;
-
- /**
- * 通过反射调用所有方法(包括私有的)
- */
- public class HiddenImplementation {
-
- static void callHiddenMethod(Object obj, String methodName, Object[] args) throws Exception{
- Method method = obj.getClass().getDeclaredMethod(methodName);
- method.setAccessible(true);
- method.invoke(obj, args);
- }
-
- public static void main(String[] args) throws Exception{
- callHiddenMethod(new B(), "g", null);
- }
- }
-
- interface A {
- void f();
- }
- class B implements A{
- @Override
- public void f(){}
- private void g(){
- System.out.println("B.g()");
- }
- }