① java面向對象中,多態性表現在哪些方面
數據抽象、繼承和多態是面向對象程序設計語言的三大特性。多態,我覺得它的作用就是用來將介面和實現分離開,改善代碼的組織結構,增強代碼的可讀性。在某些很簡單的情況下,或許我們不使用多態也能開發出滿足我們需要的程序,但大多數情況,如果沒有多態,就會覺得代碼極其難以維護。
在Java中,談論多態就是在討論方法調用的綁定,綁定就是將一個方法調用同一個方法主體關聯起來。在C語言中,方法(在C中稱為函數)的綁定是由編譯器來實現的,在英文中稱為early binding(前期綁定),因此,大家自然就會想到相對應的late binding(後期綁定),這在Java中通常叫做run-time binding(運行時綁定),我個人覺得這樣稱呼更貼切,運行時綁定的目的就是在代碼運行的時候能夠判斷對象的類型。通過一個簡單的例子說明:
/**
* 定義一個基類
*/
public Class Parents {
public void print() {
System.out.println(「parents」);
}
}
/**
* 定義兩個派生類
*/
public Class Father extends Parents {
public void print() {
System.out.println(「father」);
}
}
public Class Mother extends Parents {
public void print() {
System.out.println(「mother」);
}
}
/**
* 測試輸出結果的類
*/
public Class Test {
public void find(Parents p) {
p.print();
}
public static void main(String[] args) {
Test t = new Test();
Father f = new Father();
Mother m = new Mother();
t.find(f);
t.find(m);
}
}
最後的輸出結果分別是father和mother,將派生類的引用傳給基類的引用,然後調用重寫方法,基類的引用之所以能夠找到應該調用那個派生類的方法,就是因為程序在運行時進行了綁定。
學過Java基礎的人都能很容易理解上面的代碼和多態的原理,但是仍有一些關鍵的地方需要注意的,算是自己對多態的一個小結:
1. Java中除了static和final方法外,其他所有的方法都是運行時綁定的。在我另外一篇文章中說到private方法都被隱式指定為final的,因此final的方法不會在運行時綁定。當在派生類中重寫基類中static、final、或private方法時,實質上是創建了一個新的方法。
2.在派生類中,對於基類中的private方法,最好採用不同的名字。
3.包含抽象方法的類叫做抽象類。注意定義裡麵包含這樣的意思,只要類中包含一個抽象方法,該類就是抽象類。抽象類在派生中就是作為基類的角色,為不同的子類提供通用的介面。
4.對象清理的順序和創建的順序相反,當然前提是自己想手動清理對象,因為大家都知道Java垃圾回收器。
5.在基類的構造方法中小心調用基類中被重寫的方法,這里涉及到對象初始化順序。
6.構造方法是被隱式聲明為static方法。
7.用繼承表達行為間的差異,用欄位表達狀態上的變化。
如何理解Java多態性?通過類型轉換,把一個對象當作它的基類對象對待。
從相同的基類派生出來的多個派生類可被當作同一個類型對待,可對這些不同的類型進行同樣的處理。
這些不同派生類的對象響應同一個方法時的行為是有所差別的,這正是這些相似的類之間彼此區別的不同之處。
動態綁定
將一個方法調用和一個方法主體連接到一起稱為綁定(Binding)。
根據綁定的時機不同,可將綁定分為「早期綁定」和「後期綁定」兩種。
如果在程序運行之前進行綁定(由編譯器和鏈接程序完成),稱為早期綁定。
如果在程序運行期間進行綁定,稱為後期綁定,後期綁定也稱為「動態綁定」或「運行時綁定」。
在Java中,多態性是依靠動態綁定實現的,即Java虛擬機在運行時確定要調用哪一個同名方法。
多態的應用
由於多態性,一個父類的引用變數可以指向不同的子類對象,並且在運行時根據父類引用變數所指向對象的實際類型執行相應的子類方法。
利用多態性進行二次分發。
利用多態性設計回調方法。
多態的例子
Shape類是幾個具體圖形類的父類
package cn.e.uibe.poly;
public class Shape {
public void draw(){
System.out.println("Shape.draw()");
}
}
Rectangle類是Shape類的一個子類
package cn.e.uibe.poly;
public class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("畫矩形");
}
}
Circle類也是Shape類的子類
package cn.e.uibe.poly;
public class Circle extends Shape{
@Override
public void draw() {
System.out.println("畫圓");
}
}
Triangle類是Shape類的另外一個子類
package cn.e.uibe.poly;
public class Triangle extends Shape{
@Override
public void draw() {
System.out.println("畫三角形");
}
}
ShapeDemo類中隨機生成矩形、圓、三角形,然後用Shape類型的引用調用。
package cn.e.uibe.poly;
import java.util.*;
public class ShapeDemo {
Random rand = new Random();
public Shape createShape(){
int c = rand.nextInt(3);
Shape s = null;
switch(c){
case 0:
s = new Rectangle();
break;
case 1:
s = new Circle();
break;
case 2:
s = new Triangle();
break;
}
return s;
}
public static void main(String[] args) {
ShapeDemo demo = new ShapeDemo();
Shape[] shapes = new Shape[10];
for(int i=0;i<shapes.length;i++){
shapes[i] = demo.createShape();
}
for(int i=0;i<shapes.length;i++){
shapes[i].draw();//同樣的消息,不同的響應
}
}
}
Java的多態性
面向對象編程有三個特徵,即封裝、繼承和多態。
封裝隱藏了類的內部實現機制,從而可以在不影響使用者的前提下改變類的內部結構,同時保護了數據。
繼承是為了重用父類代碼,同時為實現多態性作準備。那麼什麼是多態呢?
方法的重寫、重載與動態連接構成多態性。Java之所以引入多態的概念,原因之一是它在類的繼承問題上和C++不同,後者允許多繼承,這確實給其帶來的非常強大的功能,但是復雜的繼承關系也給C++開發者帶來了更大的麻煩,為了規避風險,Java只允許單繼承,派生類與基類間有IS-A的關系(即「貓」is a 「動物」)。這樣做雖然保證了繼承關系的簡單明了,但是勢必在功能上有很大的限制,所以,Java引入了多態性的概念以彌補這點的不足,此外,抽象類和介面也是解決單繼承規定限制的重要手段。同時,多態也是面向對象編程的精髓所在。
要理解多態性,首先要知道什麼是「向上轉型」。
我定義了一個子類Cat,它繼承了Animal類,那麼後者就是前者是父類。我可以通過
Cat c = new Cat();
實例化一個Cat的對象,這個不難理解。但當我這樣定義時:
Animal a = new Cat();
這代表什麼意思呢?
很簡單,它表示我定義了一個Animal類型的引用,指向新建的Cat類型的對象。由於Cat是繼承自它的父類Animal,所以Animal類型的引用是可以指向Cat類型的對象的。那麼這樣做有什麼意義呢?因為子類是對父類的一個改進和擴充,所以一般子類在功能上較父類更強大,屬性較父類更獨特,
定義一個父類類型的引用指向一個子類的對象既可以使用子類強大的功能,又可以抽取父類的共性。
所以,父類類型的引用可以調用父類中定義的所有屬性和方法,而對於子類中定義而父類中沒有的方法,它是無可奈何的;
同時,父類中的一個方法只有在在父類中定義而在子類中沒有重寫的情況下,才可以被父類類型的引用調用;
對於父類中定義的方法,如果子類中重寫了該方法,那麼父類類型的引用將會調用子類中的這個方法,這就是動態連接。
看下面這段程序:
class Father{
public void func1(){
func2();
}
//這是父類中的func2()方法,因為下面的子類中重寫了該方法
//所以在父類類型的引用中調用時,這個方法將不再有效
//取而代之的是將調用子類中重寫的func2()方法
public void func2(){
System.out.println("AAA");
}
}
class Child extends Father{
//func1(int i)是對func1()方法的一個重載
//由於在父類中沒有定義這個方法,所以它不能被父類類型的引用調用
//所以在下面的main方法中child.func1(68)是不對的
public void func1(int i){
System.out.println("BBB");
}
//func2()重寫了父類Father中的func2()方法
//如果父類類型的引用中調用了func2()方法,那麼必然是子類中重寫的這個方法
public void func2(){
System.out.println("CCC");
}
}
public class PolymorphismTest {
public static void main(String[] args) {
Father child = new Child();
child.func1();//列印結果將會是什麼?
}
}
上面的程序是個很典型的多態的例子。子類Child繼承了父類Father,並重載了父類的func1()方法,重寫了父類的func2()方法。重載後的func1(int i)和func1()不再是同一個方法,由於父類中沒有func1(int i),那麼,父類類型的引用child就不能調用func1(int i)方法。而子類重寫了func2()方法,那麼父類類型的引用child在調用該方法時將會調用子類中重寫的func2()。
那麼該程序將會列印出什麼樣的結果呢?
很顯然,應該是「CCC」。
對於多態,可以總結它為:
一、使用父類類型的引用指向子類的對象;
二、該引用只能調用父類中定義的方法和變數;
三、如果子類中重寫了父類中的一個方法,那麼在調用這個方法的時候,將會調用子類中的這個方法;(動態連接、動態調用)
四、變數不能被重寫(覆蓋),」重寫「的概念只針對方法,如果在子類中」重寫「了父類中的變數,那麼在編譯時會報錯。
****************************************************************************************************************************
多態詳解(整理)2008-09-03 19:29多態是通過:
1 介面 和 實現介面並覆蓋介面中同一方法的幾不同的類體現的
2 父類 和 繼承父類並覆蓋父類中同一方法的幾個不同子類實現的.
一、基本概念
多態性:發送消息給某個對象,讓該對象自行決定響應何種行為。
通過將子類對象引用賦值給超類對象引用變數來實現動態方法調用。
java 的這種機制遵循一個原則:當超類對象引用變數引用子類對象時,被引用對象的類型而不是引用變數的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。
1. 如果a是類A的一個引用,那麼,a可以指向類A的一個實例,或者說指向類A的一個子類。
2. 如果a是介面A的一個引用,那麼,a必須指向實現了介面A的一個類的實例。
二、Java多態性實現機制
SUN目前的JVM實現機制,類實例的引用就是指向一個句柄(handle)的指針,這個句柄是一對指針:
一個指針指向一張表格,實際上這個表格也有兩個指針(一個指針指向一個包含了對象的方法表,另外一個指向類對象,表明該對象所屬的類型);
另一個指針指向一塊從java堆中為分配出來內存空間。
三、總結
1、通過將子類對象引用賦值給超類對象引用變數來實現動態方法調用。
DerivedC c2=new DerivedC();
BaseClass a1= c2; //BaseClass 基類,DerivedC是繼承自BaseClass的子類
a1.play(); //play()在BaseClass,DerivedC中均有定義,即子類覆寫了該方法
分析:
* 為什麼子類的類型的對象實例可以覆給超類引用?
自動實現向上轉型。通過該語句,編譯器自動將子類實例向上移動,成為通用類型BaseClass;
* a.play()將執行子類還是父類定義的方法?
子類的。在運行時期,將根據a這個對象引用實際的類型來獲取對應的方法。所以才有多態性。一個基類的對象引用,被賦予不同的子類對象引用,執行該方法時,將表現出不同的行為。
在a1=c2的時候,仍然是存在兩個句柄,a1和c2,但是a1和c2擁有同一塊數據內存塊和不同的函數表。
2、不能把父類對象引用賦給子類對象引用變數
BaseClass a2=new BaseClass();
DerivedC c1=a2;//出錯
在java裡面,向上轉型是自動進行的,但是向下轉型卻不是,需要我們自己定義強制進行。
c1=(DerivedC)a2; 進行強制轉化,也就是向下轉型.
3、記住一個很簡單又很復雜的規則,一個類型引用只能引用引用類型自身含有的方法和變數。
你可能說這個規則不對的,因為父類引用指向子類對象的時候,最後執行的是子類的方法的。
其實這並不矛盾,那是因為採用了後期綁定,動態運行的時候又根據型別去調用了子類的方法。而假若子類的這個方法在父類中並沒有定義,則會出錯。
例如,DerivedC類在繼承BaseClass中定義的函數外,還增加了幾個函數(例如 myFun())
分析:
當你使用父類引用指向子類的時候,其實jvm已經使用了編譯器產生的類型信息調整轉換了。
這里你可以這樣理解,相當於把不是父類中含有的函數從虛擬函數表中設置為不可見的。注意有可能虛擬函數表中有些函數地址由於在子類中已經被改寫了,所以對象虛擬函數表中虛擬函數項目地址已經被設置為子類中完成的方法體的地址了。
4、Java與C++多態性的比較
jvm關於多態性支持解決方法是和c++中幾乎一樣的,
只是c++中編譯器很多是把類型信息和虛擬函數信息都放在一個虛擬函數表中,但是利用某種技術來區別。
Java把類型信息和函數信息分開放。Java中在繼承以後,子類會重新設置自己的虛擬函數表,這個虛擬函數表中的項目有由兩部分組成。從父類繼承的虛擬函數和子類自己的虛擬函數。
虛擬函數調用是經過虛擬函數表間接調用的,所以才得以實現多態的。
Java的所有函數,除了被聲明為final的,都是用後期綁定。
四. 1個行為,不同的對象,他們具體體現出來的方式不一樣,
比如: 方法重載 overloading 以及 方法重寫(覆蓋)override
class Human{
void run(){輸出 人在跑}
}
class Man extends Human{
void run(){輸出 男人在跑}
}
這個時候,同是跑,不同的對象,不一樣(這個是方法覆蓋的例子)
class Test{
void out(String str){輸出 str}
void out(int i){輸出 i}
}
這個例子是方法重載,方法名相同,參數表不同
ok,明白了這些還不夠,還用人在跑舉例
Human ahuman=new Man();
這樣我等於實例化了一個Man的對象,並聲明了一個Human的引用,讓它去指向Man這個對象
意思是說,把 Man這個對象當 Human看了.
比如去動物園,你看見了一個動物,不知道它是什麼, "這是什麼動物? " "這是大熊貓! "
這2句話,就是最好的證明,因為不知道它是大熊貓,但知道它的父類是動物,所以,
這個大熊貓對象,你把它當成其父類 動物看,這樣子合情合理.
這種方式下要注意 new Man();的確實例化了Man對象,所以 ahuman.run()這個方法 輸出的 是 "男人在跑 "
如果在子類 Man下你 寫了一些它獨有的方法 比如 eat(),而Human沒有這個方法,
在調用eat方法時,一定要注意 強制類型轉換 ((Man)ahuman).eat(),這樣才可以...
② java中多態性什麼意思
java中多態性的意思是作為面向對象的程序設計語言最核心的特徵,表示一個對象有著多重特徵,可以在特定的情況下表現出不同的狀態,從而對應著不同的屬性和方法。
1、就java而言,多態性就是允許將父對象設置成為一個或更多與自身子對象相等的技術,賦值之後父對象就可以根據當前賦值給自身子對象的特性以不同的方式運作。
2、據了解,java中多態性可以把不同的子類對象都當作父類來看,從而屏蔽不同子類對象之間的差異,寫出通用的代碼,做出通用的編程,以適應需求的不斷變化。
(2)java對象的多態性擴展閱讀
java中多態性的注意事項
1、java中多態性使程序定義引用變數所指向的具體類型和通過該引用變數發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變數倒底會指向哪個類的實例對象,該引用變數發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。
2、由此看來,java中多態性是在程序運行時才確定具體的類的,這樣就不用修改源程序代碼,當然也就可以讓引用變數綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態。