注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

饥民2011

一直在搬砖

 
 
 

日志

 
 
 
 

Java 多态的简单介绍.  

2014-01-22 15:36:49|  分类: Java |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
   作为面向对象三大知识点之一,  多态可以讲是相对最难理解那个. 本人在这里也是简单分析一下.


一, 多态的定义.

        面向对象编程中, 1个超类的引用可以指向属于超类的对象, 也可以根据需要指向超类的派生类族对象.   

        这个过程中根据引用指向不同类型对象, 可以调用不同方法,  这就是多态.


        


1.1  什么是类的引用.


        Java 中, 1个实例化后的对象的内存是分配在内存的Heap(堆)区中的,  但是如果使用这个对象, 必须在Stuck(栈) 区定义1个 变量来存放那个堆区类型的地址.
   
        例如:
        
  1. Human A;  

        上面的代码定义了1个Human类型 的变量A,  这个A只是存放在Stuck区,  我们就说这个A是1个引用,  它可以指向堆区的Human类型的内存.

        但是上面这个A类型并没有指向任何内存, 就是所谓的"对象A没有实例化"

     
         接下来:
  1. A = new Human();  

         new Human() 这个1句是在Heap区动态分配1块Human类型的内存.      A = new Human(); 就是把该内存的头部地址赋给A.
         
         这是我们就说引用A已经有了指向,  也就是所谓的"对象A 被实例化"

          

1.2  类的引用的指向可以改变

       既然应用类型的指向是通过赋值来实现的, 也就代表这个指向可以修改, 意思就是上面的引用A可以指向另一块堆区内存的头部地址.

       看个例子:

       
  1. Human A = new Human();  
  2. Human B = new Human();  

      上面的两句代码就在Heap区分配了两块Human类型的内存,  并把它们的头部地址分别赋予两个不同的引用A 和 B

  1. A = B;  

     再执行上面的语句, 意思就是把A指向了B保存的地址, 也就是说A 和 B 现在是指向同一块内存地址了.

     但是,  A原来指向的Heap区地址就消失了, 如果在C/C++ 中, 这就是明显的内存泄露.  但是Java中会自动把A原来的地址释放的.


1.3  超类的引用可以指向其子孙派生类的内存地址.

      这个就是多态关键了.

      看下面的例子:

 
  1. package Object_kng.Ploy_kng;  
  2.   
  3. class Human_1{  
  4.     private int id;  
  5.     private int name;  
  6. }  
  7.   
  8. class Student_1 extends Human_1{  
  9.     private String school;  
  10. }  
  11.   
  12. class Section_monitor_1 extends Student_1{  
  13.     private String subject;  
  14. }  
  15.   
  16. public class Poly_1{  
  17.     public static void f(){  
  18.         Human_1 hm = new Human_1();  
  19.         Student_1 st = new Student_1();  
  20.         Section_monitor_1 sm = new Section_monitor_1();  
  21.   
  22.         //st = hm;  //error  
  23.         //st = (Student_1)hm; //error (can pass the compilation, but will fail in excution)  
  24.   
  25.         //ok  
  26.         hm = st;      
  27.   
  28.         //ok  
  29.         hm = sm;  
  30.         //st = hm;  //error  
  31.         st = (Student_1)hm; //ok  
  32.     }  
  33. }  

  

     上面定义了3个业务类, 其中学生类继承 Human_1类,   而课代表类继承学生类.

     在f() 函数中,  分别对上述3个类各实例化1个对象,  也就是3个引用 hm, st, sm分别具有了自己的指向.

      接下来一句一句讲:
  1. //st = hm;  //error  
  2. //st = (Student_1)hm; //error (can pass the compilation, but will fail in excution)  


      看上面两句,   首先 st = hm;  这句意识是把引用hm保存的头部地址赋给 引用st.      
   
      注意, 这时st 和 hm属于不同的类. 但是hm所属的类Student_1 继承自 st所属的类 Human_1.   
 
       这一句会编译失败,  是因为多态中, 不允许派生类的引用  指向 超类的对象.

      原因也不难理解:

              首先可以看看Human_1 类, 有两个私有成员,   Student_1 貌似只有1个私有成员, 但是因为继承关系,  Student_1 会隐藏继承 Human_1的所有成员的. 只不过这里没有对Human_1的成员进行封装, 所以不能直接使用隐藏的成员.

              因为隐藏 也就是说超类对象内存肯定是小于等于 其派生类的对象的.

              可就是说应用类型st  (Student_1) 类本身是可以直接或间接使用3个成员的(id, name, school),  而 hm本身指向的对象只有两个成员(id, name).

             如果st指向h的对象地址,  那么当st使用school成员时, 就会在h对象内存里找不到school这个成员.

              这个就是java里不允许  派生类应用类型 指向 超类对象的原因.

       如果按现实意义理解也可以:
              
               st = hm 的意思就是把人类 作为学生来处理,  这个肯定是不和常理的.  因为不是所有人类都是学生. 反过来就可以了.


        st = (Student_1)hm;
        
        这一句是把hm这个对象强制转换成Student_1类的对象(注意hm对象本身并没有改变), 然后赋予st.

         这一句是能通过编译的, 但是当执行时就会抛出异常:
  1. [java] java.lang.ClassCastException: Object_kng.Poly_kng.Human_1 cannot be cast to Object_kng.Poly_kng.Student_1  

         java里不允许把超类对象转成 派生类对象....  也就是不是任意1个普通人类都能作为学生来看待了.



         
  1. hm = st;  
         上面这句就是正确的,   意思是把引用hm 指向 引用st 指向的对象.

         也就是Human_1 的引用类型 指向了他的子派生类 的对象.  这是可以的.    

          因为派生类Student 隐藏继承了Human_1 的所有成员.  
 
         现实意义就是任意1个学生都可以作为人来看待.



  1. hm = sm;  
  2. //st = hm;  //error  
  3. st = (Student_1)hm; //ok  
  4.       

        这段就稍稍复杂, 首先把 hm 指向它的 孙派生类的对象, 这个也没问题.
          
         然后再执行 
  1. st = (Student_1)hm;  

       这一句在上面是错误的, 为何在这里是正确的呢.

        因为上面的 hm当时是指向 Human_1 类的对象  .   而在这里hm 已经指向了 Section_monitor_1类的对象.   

        而把 Section_monitor_1对象转化成 它的父超类 Student_1  是没问题的..

       也就说任何1个科代表都可以被看作学生来看待嘛.



1.4 据引用指向不同类型对象, 可以调用不同方法

      因为面向对象中, 派生类继承的方法是可以重写的.  两个继承关系的类里面分别有两个相同名字相同参数相同返回值的 函数. 

     但是函数体可以是不同的.


      下面的看下面的例子:

     
  1. package Object_kng.Poly_kng;  
  2.   
  3. class Human_2{  
  4.     public void print(){  
  5.         System.out.printf("it's Human\n");  
  6.     }  
  7. }  
  8.   
  9. class Student_2 extends Human_2{  
  10.     public void print(){  
  11.         System.out.printf("it's Student\n");  
  12.     }  
  13. }  
  14.   
  15. class Section_monitor_2 extends Student_2{  
  16.     public void print(){  
  17.         System.out.printf("it's Section_monitor\n");  
  18.     }  
  19. }  
  20.   
  21. public class Poly_2{  
  22.     public static void f(){  
  23.         Human_2 hm = new Human_2();  
  24.         Student_2 st = new Student_2();  
  25.         Section_monitor_2 sm = new Section_monitor_2();  
  26.   
  27.         hm.print();  
  28.         hm = st;  
  29.         hm.print();  
  30.         hm = sm;  
  31.         hm.print();  
  32.     }  
  33. }  

上面一样定义了3个类...  他们是继承关系

这3个类都有1个print 方法.  其中 Studnet_2类重写了 Human_2 的print方法..  Section_monitor_2 类重写了Student_2类的print 方法

在接下来的f() 函数中.

根据引用hm指向的不同,  
hm.print() 分别执行的是3个不同类的方法:

输出:
  1. [java] it's Human  
  2. [java] it's Student  
  3. [java] it's Section_monitor  


这个就是多态的最重要的特性之一了.



二, 多态的一个简单应用.


接下来就利用上面的特征作1个简单的应用.

  1. class Human_3{  
  2.     private int id;  
  3.     private String name;      
  4.   
  5.     public Human_3(int id,String name){  
  6.         this.id = id;  
  7.         this.name = name;     
  8.     }     
  9.   
  10.     public String get_mem(){  
  11.         return "Human: " + id + "  " +  name;  
  12.     }  
  13. }  
  14.   
  15. class Student_3 extends Human_3{  
  16.     public Student_3(int id, String name){  
  17.         super(id,name);   
  18.     }  
  19.   
  20.     public String get_mem(){  
  21.         return "student: " + super.get_mem();  
  22.     }  
  23. }  
  24.   
  25.   
  26.   
  27. class Section_monitor_3 extends Student_3{  
  28.     public Section_monitor_3(int id, String name){  
  29.         super(id,name);   
  30.     }  
  31.   
  32.     public String get_mem(){  
  33.         return "section_monitor: " + super.get_mem();  
  34.     }  
  35. }  
  36.   
  37. public class Poly_3{  
  38.     public static String extend_mem(Human_3 hm){  
  39.         java.util.Date now = new java.util.Date();  
  40.         java.text.DateFormat d1 = java.text.DateFormat.getDateInstance();  
  41.                           
  42.         return d1.format(now) + ": " + hm.get_mem();      
  43.     }  
  44.   
  45.     public static void print(){  
  46.         Human_3 hm = new Human_3(1,"Jack");  
  47.         Student_3 st = new Student_3(2,"Dick");  
  48.         Section_monitor_3 sm = new Section_monitor_3(3,"Cindy");      
  49.   
  50.         System.out.printf("%s\n",extend_mem(hm));  
  51.         System.out.printf("%s\n",extend_mem(st));  
  52.         System.out.printf("%s\n",extend_mem(sm));  
  53.   
  54.     }  
  55. }  

上面一样定义了那个3个类, 而且具有两个成员, 进行了简单的封装...

每个类都有1个get_mem()函数,  返回不同的字符串.


现在需要1个函数, 为每1个类的get_mem()返回值前增加1个日期字符串:

利用多态技术:

可以简单地定义1个  extend_mem() 函数,  参数是超类的对象.
那么这个函数就可以根据参数的不同类型而调用不同对象的 get_mem()函数.


如果没有多态技术,  就必须根据参数的类型不同  而写上3个函数了.


这个还不是最重要的,  假如以后第三个类 以后还再派生出第4个类,  那么这个函数一样适用不必改写.

也就是说多态可以实现面向对象程序的扩展性, 这个就是多态最重要的意义.



, 多态的一些简单要点:

这里也是算是1个简单总结吧:


1. 派生类对象更可以直接赋给父类引用, 但父类对象任何情况下都不能直接赋给超类引用.

2. 如果1个超类引用指向了1个派生类对象, 只能访问派生类对象继承自己超类的成员or方法. 而不能访问派生类独有的成员or方法.

3. 超类引用永远不可能直接赋给派生类引用.

   例如用 B extends A.    a 是 A的一个引用, b 是B的一个引用

   b = a; 肯定是错的.


4.  只有在超类引用本身指向的就是1个派生类或其子孙类(派生类的派生类)对象时, 才可以把超类引用强制转化为派生类引用.

  上面的例子, 假如a 指向的实际是B的一个对象.

  那么

  b = (B)a;   是正确的. 这里必须强制转换.


5. 但是其他情况下不允许把超类引用强制转化为子类引用, 否则会抛出异常, 上面说过了.
  评论这张
 
阅读(130)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017