这一章看下来,感觉比较混乱。乱感觉主要乱在8.4 8.5。 开始读的时候,感觉8.1 8.2 8.3都挺乱的。读了两遍后发现前三节还是比较有条理的。
8.1主要讲了什么是多态,多态的好处。
8.2主要讲了什么情况会发生多态??
8.3主要讲了构造器内部里面的方法调用会发生多态。
8.4就一页,而且感觉一般用不到。用到了再看也行。
8.5也很简单,相当于一个总结,一个补充(向下转型)
我主要根据书上的内容,总结两个内容: 1.什么是多态,多态的好处; 2.什么情况下会发生多态?为什么这些情况下会发生多态,而别的情况不会发生多态?
多态是面向对象的三个基本特征之一。也是比较不容易理解的一个。
多态,按照我的理解,应该是:根据调用对象的不同,而执行不同的方法。 这个应该是比较表面的,比较浅的定义。
多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术(《Java编程思想》155页)。 这个说法或许说明了最本质的东西,但是没有几年道行,很难理解。
其实这两种说法是一致的,正是因为多态可以 在运行是根据不同的对象,调用不同的方法。所以才能将改变的事物与未变的事物分开。不容易理解,就看书上149页的例子。函数tune(Instrment i)里面,调用了i.play()方法。实际的执行过程中,就会根据i的具体类型,而执行这个具体类型里的方法。
《java编程思想》148页头两段,写的真是好。摘抄一下:
多态是通过分离做什么和怎么做,从另一个角度将接口和实现分离开来。多态不但能够改善代码的组织结构和可读性,还能创建可扩展的程序。
这四小句话,每一小句,都很耐人寻味的。下面的一段开始解释了。
注:多态一般是消除了类使用时的耦合,我们只要使用抽象类或者接口就行。系统根本不需要知道我们到底使用了哪个具体类。 而这个类实例的创建,很容易发生强耦合,new 一个具体类。 这个问题解决是依靠了设计模式里面的工厂模式,将类的创建和类的使用彻底分开。《Java编程思想》也多次举例说明工厂模式。
书上8.2 8.3都是讲的哪些情况的调用会发生多态。但是,感觉讲的比较乱,如果从java虚拟机的角度出发,就很好搞定,也不用记太多的东西了。
8.2.1说道,有些方法是在编译期间绑定的,叫做前期绑定。有些方法是在运行时绑定的,叫做后期绑定的。后期绑定也叫做动态绑定或运行时绑定。显然,前期绑定不会发生多态,应为调用哪个方法已经确定了。 后期绑定才会发生多态。牢记:static方法,final方法,private方法是前期绑定,不会发生多态。 其余的方法是后期绑定,会发生多态。
我们知道,Java经过编译后,会编译成字节码。那么方法调用会编译成什么样的字节码呢??一共有四种方法调用字节码指令。分别是:
只要能被invokstatic invokespecial调用的方法,都可以在解析阶段确定唯一调用的版本。与之相反,其它方法就成为虚方法(final方法出外,虽然final方法是使用invokevirtual调用的,但是final方法无法被覆盖,没有其它版本,调用的结果肯定是唯一的。所以,java语言规范明确说明了final方法是一种非虚方法)。看下面的例子:
public class DynamicDispatch { static abstract class Human{ protected abstract void sayHello(); } static class Man extends Human{ @Override protected void sayHello(){ System.out.println("man say hello"); } } static class Women extends Human{ @Override protected void sayHello(){ System.out.println("women say hello"); } } public static void main(String[] args){ Human man = new Man(); Human women = new Women(); man.sayHello(); women.sayHello(); man = new Women(); man.sayHello(); } }
主要看main方法,第0行new了一个对象实例。创建对象的过程中,会调用<init>方法,这个方法是Java编译器自动生成的,用来初始化对象实例的。第3行,是把0行的指针复制一下,因为4行的语句会耗费一个指针。7行是保存这个对象到局部变量。8到15行类似。
第16行,是将第一个局部变量载入内存,那么第一个局部变量是谁呢??我们知道,一个实例方法的第一个局部变量保存的是this,然后是各个函数参数的依次保存。然后是方法里面声明的变量。而静态方法是没有this变量的。所有这个main方法保存的第一个是函数的参数String[],保存在位置0。而位置1就是我们函数里面
Human man = new Man();这个变量了。关键看第17行,这个指令会根据上一个变量的实际类型,来选择所应该调用的方法。只有到了运行期才会确定出这个方法的具体位置。#6指向的是常量池,里面是一个字符串“sayHello:()V",根据这个16行的实际类型,会先定位到这个实际类型本身,查找这个实际类型的所有方法,如果找到了这个sayHello()方法,就调用。否则的话,就搜索父类。这样一直搜索到Object()类。就是这样,实现了多态。 当然,有可能会优化这个搜索过程。具体可以看《深入理解Java虚拟机》,周志明老师写的。
类似的,可以这样反编译一下private方法,final方法的情况,很容易,就可以知道的了。
其实到这里,我感觉已经把这一章的主要东西说完了。为了让思路更加顺畅,下面我想总结一下,为什么private final方法不发生多态???
其实,很多时候都是这样的:能在尽早搞定的事儿,就不要拖到后面。 比如 尽早的警告代码里的错误等。 所以,能在解析期就确定的方法,就不要到运行期再确定。因为在运行时再去确认哪个方法,可能会影响执行速度。看下面的代码:
public class DynamicDispatch { static abstract class Human{ protected abstract void sayHello(); public final void love(){ System.out.println("I love you"); } } static class Man extends Human{ @Override protected void sayHello(){ System.out.println("man say hello"); } } public static void main(String[] args){ Human h = new Man(); h.love(); } }
注:编译期间只是把方法的调用,转化到了调用常量池的某个字符串。在解析期间,才会把一起方法的常量池字符从转化为具体的方法位置,private方法,final方法都是在这个阶段失去了多态的可能性。而虚方法是不会转化的,所以会发生多态。
原文地址:http://blog.csdn.net/guoweiguoweiguo/article/details/37691045