来自Class对象的RTTI(一)

        The normal goal in object-oriented programming is for your code to manipulate references to the base type.(面向对象编程中的基本目的是:让代码只操纵对基类的引用)——引自Thinking in java

        如果有一天,当你发现编程对你来说变得愈发简单时,回头看一看你写过的代码,你会恍然大悟——原来多态无处不在。书中常说,面向基类(接口)的编程使代码更容易写、更容易读、更容易维护,设计上也更容易实现、理解和改变。我觉得这不是重点,重要的是这些特性会让你越来越喜欢他,而当你越来越喜欢它的时候,你所感受到的那种快乐已经远远超过了这些优点所带来的快感。
        直入正题,为了进一步的了解多态,我们应该先了解一下动态绑定(Dynamic Binding)与静态绑定(Static Binding)。
一、动态绑定与静态绑定
        1、动态绑定
        动态绑定是指,在执行期间判断所引用对象的实际类型,根据其实际类型调用其方法。动态绑定又名后期绑定(Late Binding),可以这样理解动态绑定,在编译期无法解析调用的方法,只有在运行时才能正确解析。如下示例。
class SuperClass {	
	public void doSomething(){
		System.out.println("SuperClass.doSomething");
	}
}
class SubClass extends SuperClass{
	public void doSomething(){
		System.out.println("SubClass.doSomething");
	}
}
public class Test {
	public static void main(String[] args){
		SuperClass sup = new SuperClass();
		SuperClass sub = new SubClass();
		sup.doSomething();
		sub.doSomething();
	}
}

        Output:
SuperClass.doSomething
SubClass.doSomething

        可以看到,在编译阶段无论是sup还是sub都是Super的引用,而在运行时,它们各自指向了SuperClass和SubClass。因此,我们可以看到,在Java中动态绑定绑定的方法是基于实际的对象类型的,而不是声明时对象的引用类型。(注:这些方法通常是可以被重写的派生方法,因此编译期无法去识别方法的版本
        2、静态绑定
        能被编译器在编译期解析的绑定称为静态绑定或早期绑定(Early Binding)。所有的实例方法调用都是在运行时进行解析的,而所有的static方法调用都是在编译期完成解析的。由于静态的方法是class的方法,而非对象的方法(当然,对象也可以调用此类方法),因此解析它们只需在编译期就可以了。(静态的方法可以被重写吗?
        同样的,Java中的成员变量也是静态绑定的。Java没有提供成员变量的多态。如下示例。
class SuperClass {	
	String variable = "the variable of SuperClass";
}
class SubClass extends SuperClass{
	String variable = "the variable of SubClass";
}
public class Test {
	public static void main(String[] args){
		SuperClass sup = new SuperClass();
		SuperClass sub = new SubClass();
		System.out.println(sup.variable);
		System.out.println(sub.variable);
	}
}

       Output:
the variable of SuperClass
the variable of SuperClass

        输出结果相同,说明成员变量在编译器完成了绑定,而非运行时。根据这些线索,我们也可以推测出,private方法也是静态绑定的,因为无法实现它的派生方法。
        3、链接
        可以说,动态绑定使我们对多态有了更深的认识,当然它的底层实现还有待研究。这里之所以提到多态,是因为多态与RTTI是相辅相成的。所以怎样去理解它们,RTTI是什么,为什么要使用RTTI,等等这些问题还有待解决。
二、多态与RTTI
        1、为什么需要RTTI
        一个典型的例子,来自Thinking in Java。
import java.util.List;
import java.util.Arrays;
public class Shapes {
	public static void main(String[] args){
		/**
		 *1、 当把Shape对象放入List<Shape>的数组时会向上转型。但向上转型为Shape的时候也丢失了Shape对象的具体类型。
		 *2、 对于数组而言,它们只是Shape类的对象——实际上它将所有的事物都当作Object持有。
		 *3、 当从数组中取出元素时,这种容器会自动将结果转型回Shape,这是RTTI最基本的使用形式。
		 *4、 Shape对象执行什么样的代码,是由引用所指向的具体对象Circle、Square或Triangle而决定的(多态)。
		 */
		List<Shape> shapeList = Arrays.asList(new Circle(),new Square(),new Triangle());
		for(Shape shape : shapeList){
			shape.draw();
		}
	}
}
abstract class Shape{
	void draw(){System.out.println(this + ".draw()");}
	/**
	 * 如果某个对象出现在字符串表达式中,toString()方法就会被自动调用,以生成表示该对象的String。
	 */
	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";
	}
}

        Output:
Circle.draw()
Square.draw()
Triangle.draw()

        在Java中,所有的类型转换都是在运行时进行正确检查的。这也正是RTTI名字的含义:在运行时,识别一个对象的类型。
        这个实例用泛型、RTTI和多态共同完成了一项任务,泛型确保类编译器的类型转换;RTTI完成了运行时的类型转换;多态使对应的对象执行了正确的行为。
        但是,如果仅仅至于一个对象家族的通用类打交道无法满足我们的需求,我们该怎么办呢?如果我们在运行时能够确定识别一个泛化引用的确切类型,这样我们就能满足我们对特殊问题的需求,那么这个技术点又该如何解决呢?
        2、链接
        RTTI能够为我们解决上面提到的问题,它能够使我们在运行时查询泛化引用的确切类型。理解RTTI的关键,还需从Class对象入手!
三、Class对象
        1、什么是Class对象
        每个类都有一个Class对象。Class对象包含了与类有关的信息,它用来创建类的所有的常规对象。
        2、Class对象的由来
        位于堆区中的Class对象,是类加载的最终产物——类加载器的行为目标。
        3、类的加载
        类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.class对象,用来封装类的方法区内的数据结构。
        1、所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员引用时,就会加载这个类。
        2、程序在它开始运行之前并非被完全加载,其个部分是在必需时才加载的。(动态加载)
        3、类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器(System ClassLoader)就会根据类名查找.class文件。
        4、一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。
        如下示例:
class First{
	static { System.out.println("Loading First!"); }
}
class Second{
	static {System.out.println("Loading Second!");}
}
class Third{
	static {System.out.println("Loading Third!");}
}
public class LoadTest {
	//当程序创建第一个对类的静态成员的引用时,就会加载这个类
	public static void main(String[] args){
		System.out.println("start from main method");
		new First();
		new First();
		new First();
		System.out.println("After creating First");
		try{
			Class.forName("Second");
			new Second();
		}catch(ClassNotFoundException ep){
			System.out.println("Couldn't find Second");
		}
		System.out.println("After Creating Second");
		new Third();
		System.out.println("After creating Third");
	}
}

        Output:
start from main method
Loading First!
After creating First
Loading Second!
After Creating Second
Loading Third!
After creating Third

        4、链接
        如果要使用一个类,仅仅是加载它是不够的。要使用它,还必需进行链接与初始化。这些仅仅是个开始,如何在运行时确定一个泛化引用的具体类型,如何安全严谨地使用Class对象,都是我们亟待解决的问题。

你可能感兴趣的:(java,jvm)