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对象,都是我们亟待解决的问题。