java中多态

(1)向上转型

  1. class TV{  
  2.     public static void show(TV tv){  
  3.         System.out.println("TV");  
  4.     }  
  5. }  
  6.   
  7. public class LeTV extends TV{  
  8.     public static void main(String[] args) {  
  9.         LeTV letv = new LeTV();  
  10.         TV.show(letv);  
  11.     }  
  12. }  

看到没有,程序正常运行。本来参数类型是TV类型,但是子类传进去也可以。这种将LeTV转为父类TV的行为,就是向上转型。


向上转型是安全的,子类必须继承父类的方法(这是继承的定义),自己可以拥有自己的属性和方法,从一个特定的类向一个通用的类转换,是安全的。反过来有普通到特殊却有不对应的风险。


(2)多态

先看例子:

  1. class TV{  
  2.     public  void show(){  
  3.         System.out.println("TV");  
  4.     }  
  5. }  
  6.   
  7. class LeTV extends TV{  
  8.     public void show(){  
  9.         System.out.println("LeTV");  
  10.     }  
  11. }  
  12. class MiTV extends TV{  
  13.     public void show(){  
  14.         System.out.println("MiTV");  
  15.     }  
  16. }  
  17. class SanTV extends TV{  
  18.     public void show(){  
  19.         System.out.println("SanTV");  
  20.     }  
  21. }  
  22. public class EveryTV {  
  23.     public static void tvshow(LeTV tv){  
  24.         tv.show();  
  25.     }  
  26.     public static void tvshow(MiTV tv){  
  27.         tv.show();  
  28.     }  
  29.     public static void tvshow(SanTV tv){  
  30.         tv.show();  
  31.     }  
  32.     public static void main(String[] args) {  
  33.         tvshow(new LeTV());  
  34.         tvshow(new MiTV());  
  35.         tvshow(new SanTV());  
  36.     }  
  37. }  

程序没什么问题,每个方法都有对应的类型。但是除了这三个子类,还有几十,几百个TV子类,那么tvshow方法岂不是也要写上对应的几十甚至几百个


然后,多态出现了。

  1. public class EveryTV {  
  2.     public static void tvshow(TV tv){  
  3.         tv.show();  
  4.     }  
  5.    
  6.     public static void main(String[] args) {  
  7.         tvshow(new LeTV());  
  8.         tvshow(new MiTV());  
  9.         tvshow(new SanTV());  
  10.     }  
  11. }  

程序简洁了很多,并且运行结果和上面的一样。美好了许多。再看一看,这和向上转型很像。


多态,可以理解成多种形式,多种状态。多态也叫动态绑定、后期绑定、运行时绑定。

谈到绑定就要联系前期绑定(前期绑定国内的搜索真的是惨不忍睹,把编程思想的话全部抄进去,不会去真正理解前期绑定是什么!)。


what is Early and Late Binding?(来自stackoverflow)。

The short answer is that early (or static) binding refers to compile time binding and late (or dynamic) binding refers to runtime binding (for example when you use reflection)——byChristian Hagelid.


简单说前期绑定是编译期的绑定,后期绑定是运行时的绑定。

书上指出C的方法全部都是前期绑定,如果上面多态的例子是前期绑定的话,编译时就要绑定,但是tv这个引用只有一个,即使你传进去的是LeTV,它也不知道是要执行TV的show方法还是其他类的show方法。


正因为有了多态,后期绑定的存在,会根据对象来执行对应的方法。


因为多态的存在,我们可以添加多个新的子类而不用去担心要写多少tvshow方法。


向上转型的写法:

  1. public static void main(String[] args) {  
  2.     TV letv = new LeTV();  
  3.     TV mitv = new MiTV();  
  4.     TV santv = new SanTV();  
  5.     tvshow(letv);  
  6.     tvshow(mitv);  
  7.     tvshow(santv);  
  8. }  

各种各样的TV还是TV,可以向上转型,因为多态,又会调用各自的对象的show方法

书上的原话讲得很好,我照抄一下,多态是一种让程序员“将改变的事物和未变的事物分离开来”的重要技术。

就上述而言,各自的TV的show方法有自己的重写,但是EveryTV这个类里面的tvshow方法却不用变动。就是多态的用处所在。


是不是觉得没什么用?

  1. List al = new ArrayList();  
  2. List ll = new LinkedList();  

用处就在这里,我先用al也行,ll也行,以后要改可以改,不然很多东西都要改过。


(3)缺陷


1)“覆盖”私有方法

书上举的例子有点牵强,方法不算重写。

  1. public class TV{  
  2.     private void show(){  
  3.         System.out.println("TV");  
  4.     }  
  5.     public static void main(String[] args) {  
  6.         TV tv = new LeTV();  
  7.         tv.show();  
  8.     }  
  9. }  
  10. class LeTV extends TV{  
  11.     public void show(){  
  12.         System.out.println("LeTV");  
  13.     }  
  14. }  

学了多态之后,一看结果是LeTV吧。错了,LeTV根本没有重写show方法,父类的show方法是private的,不可见,不可继承。假的“覆写”。


2)域与静态方法

对域,field概念模糊的,可以看看:java中的域是什么?

  1. public class TV{  
  2.     public int price = 10;  
  3.     public int getprice(){  
  4.         return price;  
  5.     }  
  6.     public static String getString(){  
  7.         return "tv";  
  8.     }  
  9. }  
  10. class LeTV extends TV{  
  11.     public int price = 20;  
  12.     public int getprice(){  
  13.         return price;  
  14.     }  
  15.     public int getsuperprice(){  
  16.         return super.price;  
  17.     }  
  18.       
  19.     public static String getString(){  
  20.         return "letv";  
  21.     }  
  22.       
  23.     public static void main(String[] args) {  
  24.         TV tv = new LeTV();  
  25.         System.out.println(tv.price+" getprice:"+tv.getprice()+tv.getString());  
  26.         LeTV letv = new LeTV();  
  27.         System.out.println(letv.price+" getprice:"+letv.getprice()+" getsuperprice:"+letv.getsuperprice()  
  28.                 +letv.getString());  
  29.     }  
  30. }  

按照多态,tv.price的结果应该为20,但是结果却是10,因为域访问操作是由编译器解析,不是多态,多态是后期绑定,也就是运行期间。

而tv.getString方法也一样,并没有打印出letv,而是tv,因为方法是静态方法,只和类有关,与具体的对象不关联。

这是运用多态时要注意的两个地方。


刚开始看这个多态特性的时候难看懂,也不知道这东西有什么用,其实程序敲多了,其义自现。你会发现你写的东西导出都在用它。


1)构造器和多态

这个问题其实前面写过了,构造器实际上是static方法,只不过是隐式声明,所以构造器并没有多态性。

但是需要知道加载的顺序。

  1. class GrandFather{  
  2.     GrandFather(){  
  3.         print();  
  4.     }  
  5.      
  6.     private int print(){  
  7.         System.out.println("g");  
  8.         return 1;  
  9.     }  
  10. }  
  11. class Father extends GrandFather{  
  12.     Father(){  
  13.         print();  
  14.     }  
  15.     private int print(){  
  16.         System.out.println("f");  
  17.         return 1;  
  18.     }  
  19. }  
  20. public class Son extends Father{  
  21.     Son(){  
  22.         print();  
  23.     }  
  24.     private  int print(){  
  25.         System.out.println("s");  
  26.         return 1;  
  27.     }  
  28.     public static void main(String[] args) {  
  29.         Son s = new Son();  
  30.     }  
  31. }  

其实new出子类的时候,需要调用父类构造器,递归下去。

所以输出结果为g,f,s。


2)继承与清理

虽然有GC,但是书上还是用了一次引用技术的方法来模拟清理对象。

  1. class Count{  
  2.     private int reference;  
  3.     private static int counter;  
  4.     private final long id = counter++;//注意一下,虽然是final,或许会觉得它不是不可变  
  5.     //为什么还给他赋值呢,对,但是现在值没确定,我们给他赋值之后就不会再变了。  
  6.       
  7.     Count(){  
  8.         System.out.println("count"+id);  
  9.     }  
  10.       
  11.     public void addReference(){  
  12.         reference ++;  
  13.     }  
  14.       
  15.     protected void dispose(){  
  16.         if(--reference == 0){  
  17.             System.err.println("dispose count"+id);  
  18.         }     
  19.     }  
  20. }  
  21.   
  22. public class Rubbish {  
  23.     private Count count;  
  24.     private static int counter;  
  25.     private final long id = counter++;  
  26.       
  27.     Rubbish(Count count){  
  28.         System.out.println("Rubbish"+id);  
  29.         this.count = count;  
  30.         this.count.addReference();  
  31.     }  
  32.       
  33.      
  34.     protected void dispose(){  
  35.         System.out.println("dispose Rubbish"+id);  
  36.         count.dispose();  
  37.     }  
  38.       
  39.     public static void main(String[] args) {  
  40.         Count count = new Count();  
  41.         Rubbish rubbish[] = {new Rubbish(count),new Rubbish(count),  
  42.                 new Rubbish(count),new Rubbish(count)};  
  43.         for(Rubbish r:rubbish){  
  44.             r.dispose();  
  45.         }  
  46.     }  
  47. }  

每new一个对象的时候,计数器counter计算count对象的数量,id为final是我们确定之后不希望被改变,reference是引用计数,每每对象增加一个,便会加一,当引用都没有的时候,我们也要将计算引用的这个对象清理。

原来GC里面的引用计数法是这样的一个原理。


3)用继承进行设计

TV大变身:

  1. class TV{  
  2.     public  String getString(){  
  3.         return "tv";  
  4.     }  
  5.   
  6.     public TV change() {  
  7.         // TODO Auto-generated method stub  
  8.         return new TV();  
  9.     }  
  10. }  
  11. class SanTV extends TV{  
  12.     public  String getString(){  
  13.         return "santv";  
  14.     }  
  15. }  
  16. public class LeTV extends TV{  
  17.     public String getString(){  
  18.         return "letv";  
  19.     }  
  20.     public SanTV change(){  
  21.         return new SanTV();  
  22.     }  
  23.     public static void main(String[] args) {  
  24.         TV letv = new LeTV();  
  25.         System.out.println(letv.getString());  
  26.         TV newtv = letv.change();  
  27.         System.out.println(newtv.getString());  
  28.     }  
  29. }  

之前犯了一个错误,TV类里面是没有change方法的,我直接用了

  1. TV newtv = letv.change();  
发现报错,TV没有定义change方法,我子类不是可以有自己的新方法吗,为什么会报错?

后面搞明白了,父类引用指向子类对象,其实一开始调用的是父类的方法,由于多态的存在,后期绑定之后,才会结合具体的重写方法。但是我现在父类方法都没定义,肯定报错。为了验证,修改代码。

  1. public class LeTV extends TV{  
  2.     public String getString(){  
  3.         return "letv";  
  4.     }  
  5.     public static void main(String[] args) {  
  6.         TV letv = new LeTV();  
  7.         System.out.println(letv.getString());  
  8.         TV newtv = letv.change();  
  9.         System.out.println(newtv.getString());  
  10.     }  
  11. }  

现在子类没有重写change方法,默认就是继承父类的change方法,这样的输出结果就是letv,tv。


其实上面这种转换是一种特殊的模式——状态模式。

其实TV可以看成List,两个具体的TV可以看成LinkedList和ArrayList。两种状态可以灵活的切换。


多态就讲到这里了——不同的形式。

要真正的理解,实需多敲敲代码,写完之后自己会发现自己是用了多态。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值