Java 多态的简单介绍.

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


一, 多态的定义.

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

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


        


1.1  什么是类的引用.


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

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

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

     
         接下来:
A = new Human();

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

          

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

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

       看个例子:

       
Human A = new Human();
Human B = new Human();

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

A = B;

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

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


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

      这个就是多态关键了.

      看下面的例子:

 
package Object_kng.Ploy_kng;

class Human_1{
	private int id;
	private int name;
}

class Student_1 extends Human_1{
	private String school;
}

class Section_monitor_1 extends Student_1{
	private String subject;
}

public class Poly_1{
	public static void f(){
		Human_1 hm = new Human_1();
		Student_1 st = new Student_1();
		Section_monitor_1 sm = new Section_monitor_1();

		//st = hm;  //error
		//st = (Student_1)hm; //error (can pass the compilation, but will fail in excution)

		//ok
		hm = st; 	

		//ok
		hm = sm;
		//st = hm;  //error
		st = (Student_1)hm; //ok
	}
}

  

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

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

      接下来一句一句讲:
//st = hm;  //error
//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.

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

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



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

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

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



hm = sm;
//st = hm;  //error
st = (Student_1)hm; //ok
	

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

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

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

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

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



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

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

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


      下面的看下面的例子:

     
package Object_kng.Poly_kng;

class Human_2{
	public void print(){
		System.out.printf("it's Human\n");
	}
}

class Student_2 extends Human_2{
	public void print(){
		System.out.printf("it's Student\n");
	}
}

class Section_monitor_2 extends Student_2{
	public void print(){
		System.out.printf("it's Section_monitor\n");
	}
}

public class Poly_2{
	public static void f(){
		Human_2 hm = new Human_2();
		Student_2 st = new Student_2();
		Section_monitor_2 sm = new Section_monitor_2();

		hm.print();
		hm = st;
		hm.print();
		hm = sm;
		hm.print();
	}
}

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

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

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

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

输出:
     [java] it's Human
     [java] it's Student
     [java] it's Section_monitor


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



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


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

class Human_3{
	private int id;
	private String name;	

	public Human_3(int id,String name){
		this.id = id;
		this.name = name;	
	}	

	public String get_mem(){
		return "Human: " + id + "  " +  name;
	}
}

class Student_3 extends Human_3{
	public Student_3(int id, String name){
		super(id,name);	
	}

	public String get_mem(){
		return "student: " + super.get_mem();
	}
}



class Section_monitor_3 extends Student_3{
	public Section_monitor_3(int id, String name){
		super(id,name);	
	}

	public String get_mem(){
		return "section_monitor: " + super.get_mem();
	}
}

public class Poly_3{
	public static String extend_mem(Human_3 hm){
		java.util.Date now = new java.util.Date();
		java.text.DateFormat d1 = java.text.DateFormat.getDateInstance();
						
		return d1.format(now) + ": " + hm.get_mem();	
	}

	public static void print(){
		Human_3 hm = new Human_3(1,"Jack");
		Student_3 st = new Student_3(2,"Dick");
		Section_monitor_3 sm = new Section_monitor_3(3,"Cindy");	

		System.out.printf("%s\n",extend_mem(hm));
		System.out.printf("%s\n",extend_mem(st));
		System.out.printf("%s\n",extend_mem(sm));

	}
}

上面一样定义了那个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. 但是其他情况下不允许把超类引用强制转化为子类引用, 否则会抛出异常, 上面说过了.


        

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值