标签:
一、多态是在继承/实现的基础上的
1.简单的讲,类之间连继承/实现的关系都不存在,多态无从谈起
2.关于动态绑定:java中大部分方法都是动态绑定的,见下例:
1 public class Test { 2 public static void main(String[]args) { 3 A a = new B(); 4 A aa = new C(); 5 6 a.f(); 7 aa.f(); 8 } 9 } 10 11 class A{ 12 13 public void f() { 14 System.out.println("子类f()"); 15 } 16 } 17 18 class B extends A { 19 public void f() { 20 System.out.println("子类B的f()"); 21 } 22 } 23 24 class C extends A { 25 public void f() { 26 System.out.println("子类C的f()"); 27 } 28 }
结果如下:
上面是个多态的简单例子A a = new B(); A aa = new C();处,只需要用父类A的声明就可以接收不同的子类的对象,这有两点重要的意义:
从代码编写角度来看,如果想要修改实现类,只需要替换B或者C即可,其他部分不需要改变;
从设计的角度来看, 多态“将改变的事物和未变的事物分离”。
这种多态功能能够实现,根源于java的动态绑定技术
对于编译器来说,没有真正运行代码,能够知道的仅仅是这个地方使用的是A的对象(有种指鹿为马的感觉,只是这个指鹿为马要求有继承或者实现接口的关系存在),因此在使用父类声明指向子类对象的时候,只能调用父类已经存在的方法,而不能调用子类中新定义的方法;
在运行期间才真正去堆中查找具体的对象。事实上,在java的堆中,是分为数据区和方法区的,不同的对象,在数据区中有不同的拷贝,里面存放各自对象的数据;但是每一种类型的方法,在方法区中只有一份拷贝,JVM运行的时候先去数据区找数据,然后去方法区中找对应的方法执行。
3.前面讲到的,java中大部分方法都是采用的动态绑定,在运行期间才决定其具体执行的,也有例外:static,final(private是默认final的)的方法是在编译期间就已经指定好的。
1)易忽略点1:覆盖private方法
1 package com.proxy; 2 3 public class Super { 4 private void f() { 5 System.out.println("Super"); 6 } 7 8 public static void main(String[] args) { 9 Super s = new Sub();//注意这里,父类声明指向子类对象 10 s.f(); 11 } 12 } 13 14 class Sub extends Super { 15 public void f() { 16 System.out.println("Sub"); 17 } 18 19 20 }
上面的代码,输出的结果是:Super
不要习惯了看到父类声明指向子类对象,就认为这里输出的结果应该是Sub,实际上因为父类中的f()方法是私有的(private默认就是final的)
所以在执行的时候f()不存在多态现象,所以结果就是Super。 这里如果把main方法写在Sub里面,编译就会报错。
private的方法不能够被覆盖,没有多态
2)易忽略点2:覆盖static方法
1 package com.proxy; 2 3 public class Super { 4 public static void f() { 5 System.out.println("Super"); 6 } 7 8 public static void main(String[] args) { 9 Super s = new Sub(); 10 s.f(); 11 } 12 } 13 14 class Sub extends Super { 15 public static void f() { 16 System.out.println("Sub"); 17 } 18 19 20 }
结果为:Super
这里,也是用的父类声明指向子类对象,但是父类和子类的f()方法都是static的。静态方法是在编译期间就被绑定的,编译器只认Super s,
然后去找Super的静态方法f(), 我们知道静态成员是属于类的而不是属于对象的,所以这里的结果就是Super。Super.f()和Sub.f()实际上是两个方法
static的方法不能被覆盖,没有多态
3)易忽略点3:构造器与多态
构造方法默认就是static的,构造方法没有多态。那么就有一个尴尬的情景:如果在构造方法中调用了一个有动态性的普通方法会怎样?
1 package com.proxy; 2 3 public class Super { 4 5 public Super() { 6 f();//构造方法中,调用具有动态性的f()方法 7 } 8 9 public void f() { 10 System.out.println("Super"); 11 } 12 13 public static void main(String[] args) { 14 Super s = new Sub(5); 15 } 16 } 17 18 class Sub extends Super { 19 20 private int i=1; 21 22 public Sub(int i) { 23 this.i = i; 24 f(); 25 } 26 27 @Override 28 public void f() { 29 System.out.println("Sub , i="+i); 30 } 31 32 }
结果如下:
这里就很奇怪了,调用的f()方法命名是子类的f(),但是为是第一个i的值是0?
这里的步骤如下:
首先是JVM读取到Super s = new Sub(); 自动去堆中分配空间,并且自动初始化(此时i的值是自动初始化得到的0);
然后创建子类对象前会先调用其父类的构造方法,父类构造方法里面调用f() ,由于多态的存在实际上是去方法区中找Sub的f(),所以输出的是Sub,i此时为0;
最后调用子类的构造方法,i变为5
由于这种比较奇怪的现象存在,所以遵循的一个原则就是:在构造方法中,不要去调用其他具有动态性的方法。
4)易忽略点4:与继承多态相关的清理
1 package com.proxy; 2 3 public class Super { 4 5 @Override 6 protected void finalize() throws Throwable { 7 System.out.println("父类清理方法"); 8 } 9 10 public static void main(String[] args) throws Throwable { 11 Super s = new Sub(); 12 s.finalize();//执行清理 13 } 14 } 15 16 class Sub extends Super { 17 18 @Override 19 protected void finalize() throws Throwable { 20 System.out.println("子类清理方法"); 21 } 22 }
结果如下:
可以看到,由于多态的存在,这里调用清理方法的时候只执行了子类的finalize(), 也就是说如果这么写在JVM执行清理操作的时候父类清理前的操作将不会被执行。所以有继承的时候如果要执行清理操作,一定要在子类finalize()中显式的调用super.finalize()
5)易忽略点5:继承清理时,如果有共享对象的情景
1 public class Test { 2 public static void main(String[]args) { 3 Shared shared = new Shared(); 4 5 Composing[] composing = {new Composing(shared),new Composing(shared), 6 new Composing(shared),new Composing(shared)};//创建四个Composing,每一个都需要引用Shared对象 7 8 for(Composing c : composing) {//这里,数组也能够使用for-each循环 9 c.dispose(); 10 } 11 } 12 } 13 14 class Shared { 15 private int refcount = 0; //用来记录Shared的对象给引用数 16 private static long counter = 0; //counter用来记录Shared类型创建的对象的数目 17 private final long id = counter++; //id用来记录每一个对象 每创建一个Shared,就会加1 ,用这个id来代表不同的Shared对象 18 19 public Shared() { 20 System.out.println( "creating "+this); 21 } 22 23 public void addRef() { 24 refcount++; 25 } 26 27 protected void dispose() { 28 if (--refcount == 0) { 29 System.out.println( "disposing "+this); 30 } 31 } 32 33 public String toString() { 34 return "Shared "+id; 35 } 36 } 37 38 class Composing { 39 private Shared shared; 40 private static long counter = 0; 41 private final long id = counter++; //每创建一个Composing,就会加1 ,用这个id来代表不同的Composing对象 42 public Composing(Shared shared) { 43 System.out.println( "creating "+this); 44 this.shared = shared; 45 this.shared.addRef(); //Composing中引用Shared对象,则refcount 加1 46 } 47 48 protected void dispose() { 49 System.out.println( "disposing "+this); 50 shared.dispose();// Composing引用了shared, 在composing 进行清理的时候,调用shared的清理方法,这样就确保引用的shared能够得到正确的清理处理 51 } 52 53 public String toString() { 54 return "Composing "+id;// 55 } 56 }
结果如下:
这种子类对象里面均持有一个共享的对象,在清理的时候,必须要考虑到共享对象完全没有再被另一个持有的情况下面才释放该共享对象
三、协变返回类型:简单的讲就是返回值也适应多态的规则
1 class Grain{ 2 public String toString() { 3 return "Grain"; 4 } 5 } 6 7 class Wheat extends Grain { 8 public String toString() { 9 return "Wheat"; 10 } 11 } 12 13 class Mill { 14 Grain process() { 15 return new Grain(); 16 } 17 } 18 19 class WheatMill extends Mill { 20 Wheat process() { 21 return new Wheat(); 22 } 23 } 24 25 public class Test{ 26 public static void main(String[]args) { 27 Mill m = new Mill(); //建立一个Mill对象 28 Grain g = m.process(); //通过Mill对象的process方法返回一个Grain的对象 29 System.out.println(g); //打印g,调用Grain的toString方法,这里结果是Grain 30 31 m = new WheatMill(); //m = 新建的WheatMill对象 32 g = m.process(); //g再次被赋值 ,这次返回的是Wheat对象 33 System.out.println(g); //因为Wheat继承了 Grain ,返回的时候返回Wheat的toString 34 } 35 }
结果如下:
标签:
原文地址:http://www.cnblogs.com/kaiguoguo/p/4678929.html