Java編程 多態(tài)
前言:
封裝,是合并屬性和行為創(chuàng)建一種新的數(shù)據(jù)類型,繼承是建立數(shù)據(jù)類型之間的某種關(guān)系(is-a),而多態(tài)就是這種關(guān)系在實(shí)際場(chǎng)景的運(yùn)用。
多態(tài)就是把做什么和怎么做分開了;其中,做什么是指調(diào)用的哪個(gè)方法[play 樂(lè)器],怎么做是指實(shí)現(xiàn)方案[使用A樂(lè)器 使用B樂(lè)器],''分開了''指兩件事不在同一時(shí)間確定。
一、向上轉(zhuǎn)型
對(duì)象既可以作為它本身的類型使用,也可以作為它的基類型使用,而這種把對(duì)某個(gè)對(duì)象的引用視為對(duì)其基類型的引用的做法就是向上轉(zhuǎn)型。
example:
public enum Note {
// 演奏樂(lè)符
MIDDLE_C, C_SHARP, B_FLAT;
}
public class Instrument {
// 樂(lè)器基類
public void play(Note n) {
print("Instrument.play()");
}
}
public class Wind extends Instrument{
// Wind是一個(gè)具體的樂(lè)器
// Redefine interface method:
public void play(Note n) {
System.out.println("Wind.play() " + n);
}
}
public class Music {
// 樂(lè)器進(jìn)行演奏
public static void tune(Instrument i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute); // 向上轉(zhuǎn)型
}
}
好處:
在上面例子中,如果讓tune方法接受一個(gè)Wind引用作為自己的參數(shù),似乎看起來(lái)更為直觀,但是會(huì)引發(fā)一個(gè)問(wèn)題:這個(gè)時(shí)候你就需要為系統(tǒng)中Instrument的每種類型都編寫一個(gè)新的tune方法。所以我們只寫一個(gè)簡(jiǎn)單的方法,僅僅接收基類作為參數(shù),而不是特殊的導(dǎo)出類,這么做情況不是變得更好嗎。
example:
class Stringed extends Instrument {
public void play(Note n) {
print("Stringed.play() " + n);
}
}
class Brass extends Instrument {
public void play(Note n) {
print("Brass.play() " + n);
}
}
public class Music2 {
public static void tune(Wind i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Stringed i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Brass i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
Stringed violin = new Stringed();
Brass frenchHorn = new Brass();
tune(flute); // No upcasting
tune(violin);
tune(frenchHorn);
}
}
二、轉(zhuǎn)機(jī)
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
在上面這個(gè)方法中,它接收一個(gè)Instrument引用,那么在這種情況下,編譯器怎么樣才能知道這個(gè)instrument引用指向的是Wind對(duì)象呢? ——通過(guò)后期綁定
1、綁定
將一個(gè)方法調(diào)用同一個(gè)方法主體關(guān)聯(lián)起來(lái)稱為綁定。
在程序執(zhí)行前進(jìn)行綁定,就是前期綁定,比如C語(yǔ)言就只有一種方法調(diào)用,就是前期綁定。
在運(yùn)行時(shí)根據(jù)對(duì)象的類型進(jìn)行綁定就是后期綁定,也叫做動(dòng)態(tài)綁定或者運(yùn)行時(shí)綁定。
Java中除了static方法和final方法之外,其它所有方法都是后期綁定,這意味著通常情況下,我們不必判定是否應(yīng)該進(jìn)行后期綁定——它會(huì)自動(dòng)發(fā)生。
2、擴(kuò)展性
由于有多態(tài)機(jī)制,所以可根據(jù)自己的需要向系統(tǒng)里加入任意多的新類型,同時(shí)毋需更改 true()方法。在一個(gè)設(shè)計(jì)良好的 OOP 程序中,我們的大多數(shù)或者所有方法都會(huì)遵從 tune()的模型,而且只與基礎(chǔ)類接口通信。我們說(shuō)這樣的程序具有“擴(kuò)展性”,因?yàn)榭梢詮耐ㄓ玫幕A(chǔ)類繼承新的數(shù)據(jù)類型,從而新添一些功能。如果是為了適應(yīng)新類的要求,那么對(duì)基礎(chǔ)類接口進(jìn)行操縱的方法根本不需要改變,
對(duì)于樂(lè)器例子,假設(shè)我們?cè)诨A(chǔ)類里加入更多的方法[what/adjust],以及一系列新類[Woodwind/Brass],,
例子:
class Instrument {
void play(Note n) { print("Instrument.play() " + n); }
String what() { return "Instrument"; }
void adjust() { print("Adjusting Instrument"); }
}
class Wind extends Instrument {
void play(Note n) { print("Wind.play() " + n); }
String what() { return "Wind"; }
void adjust() { print("Adjusting Wind"); }
}
class Percussion extends Instrument {
void play(Note n) { print("Percussion.play() " + n); }
String what() { return "Percussion"; }
void adjust() { print("Adjusting Percussion"); }
}
class Stringed extends Instrument {
void play(Note n) { print("Stringed.play() " + n); }
String what() { return "Stringed"; }
void adjust() { print("Adjusting Stringed"); }
}
class Brass extends Wind {
void play(Note n) { print("Brass.play() " + n); }
void adjust() { print("Adjusting Brass"); }
}
class Woodwind extends Wind {
void play(Note n) { print("Woodwind.play() " + n); }
String what() { return "Woodwind"; }
}
public class Music3 {
// Doesn't care about type, so new types
// added to the system still work right:
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
public static void tuneAll(Instrument[] e) {
for(Instrument i : e)
tune(i);
}
public static void main(String[] args) {
// Upcasting during addition to the array:
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
}
為樂(lè)器系統(tǒng)添加更多的類型,而不用改動(dòng)tune方法。tune方法完全可以忽略它周圍代碼所發(fā)生的全部變化,依舊正常運(yùn)行。
3、缺陷
私有方法
private方法被自動(dòng)修飾為final,而且對(duì)導(dǎo)出類是屏蔽的,所以在子類Derived類中的f方法是一個(gè)全新的方法。既然基類中的f方法在在子類Derived中不可見(jiàn),那么也不能被重載。
域與靜態(tài)方法
任何域(field)的訪問(wèn)操作都是由編譯器解析的,因此不是多態(tài)的。
如果某個(gè)方法是靜態(tài)的,那么它就不具有多態(tài)性
三、構(gòu)造器與多態(tài)
通常,構(gòu)造器不同于其它方法,涉及到多態(tài)時(shí)也是如此。構(gòu)造器是不具有多態(tài)性的
1、構(gòu)造器的調(diào)用順序
基類的構(gòu)造器總是在導(dǎo)出類的構(gòu)造過(guò)程中被調(diào)用,而且按照繼承層次逐漸向上鏈接。使得每個(gè)基類的構(gòu)造器都能得到調(diào)用。
2、構(gòu)造器內(nèi)部的多態(tài)方法的行為
構(gòu)造器調(diào)用的層次結(jié)構(gòu)帶來(lái)一個(gè)問(wèn)題:如果在一個(gè)構(gòu)造器內(nèi)部調(diào)用正在構(gòu)造的對(duì)象的某個(gè)動(dòng)態(tài)綁定方法,會(huì)發(fā)生什么?
public class Glyph {
void draw() { print("Glyph.draw()"); }
Glyph() {
print("Glyph() before draw()");
draw(); // 調(diào)用正在構(gòu)造的對(duì)象的某個(gè)動(dòng)態(tài)綁定方法,對(duì)象的字段radius被初始化為0
print("Glyph() after draw()");
}
}
public class RoundGlyph extends Glyph{
private int radius = 1;
RoundGlyph(int r) {
radius = r;
print("RoundGlyph.RoundGlyph(), radius = " + radius);
}
void draw() {
print("RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
/* Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*///:~
在Glyph的構(gòu)造器中,我們調(diào)用了draw方法,因?yàn)檫@個(gè)是動(dòng)態(tài)綁定方法的緣故,我們就會(huì)調(diào)用導(dǎo)出類RoundGlyph中的draw方法,但是這個(gè)方法操縱的成員radius還沒(méi)初始化,所以就體現(xiàn)出問(wèn)題了,結(jié)果中第一次輸出radius為0。
所以初始化的實(shí)際過(guò)程是:
- 1 在其他任何事物之前,將分配給對(duì)象的存儲(chǔ)空間初始化成二進(jìn)制的零
- 2 如前所述調(diào)用基類構(gòu)造器
- 3 按照聲明的順序調(diào)用成員的初始化方法
- 4 調(diào)用導(dǎo)出類的構(gòu)造器主體
四、協(xié)變返回類型
在面向?qū)ο蟪绦蛟O(shè)計(jì)中,協(xié)變返回類型指的是子類中的成員函數(shù)的返回值類型不必嚴(yán)格等同于父類中被重寫的成員函數(shù)的返回值類型,而可以是更 "狹窄" 的類型。
Java 5.0添加了對(duì)協(xié)變返回類型的支持,即子類覆蓋(即重寫)基類方法時(shí),返回的類型可以是基類方法返回類型的子類。協(xié)變返回類型允許返回更為具體的類型。
例子:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
class Base
{
//子類Derive將重寫此方法,將返回類型設(shè)置為InputStream的子類
public InputStream getInput()
{
return System.in;
}
}
public class Derive extends Base
{
@Override
public ByteArrayInputStream getInput()
{
return new ByteArrayInputStream(new byte[1024]);
}
public static void main(String[] args)
{
Derive d=new Derive();
System.out.println(d.getInput().getClass());
}
}
/*程序輸出:
class java.io.ByteArrayInputStream
*/
五、繼承進(jìn)行設(shè)計(jì)
class Actor {
public void act() {
}
}
class HappyActor extends Actor {
public void act() {
System.out.println("HappyActor");
}
}
class SadActor extends Actor {
public void act() {
System.out.println("SadActor");
}
}
class Stage {
private Actor actor = new HappyActor();
public void change() {
actor = new SadActor();
}
public void performPlay() {
actor.act();
}
}
public class Transmogrify {
public static void main(String[] args) {
Stage stage = new Stage();
stage.performPlay();
stage.change();
stage.performPlay();
}
}
輸出:
HappyActor
SadActor
一條通用的準(zhǔn)則是:“用繼承表達(dá)行為間的差異,并用字段表達(dá)狀態(tài)上的變化”。在上述例子中,兩者都用到了:通過(guò)繼承得到了兩個(gè)不同的類,用于表達(dá) act()方法的差異:而 Stage通過(guò)運(yùn)用組合使自己的狀態(tài)發(fā)生了變化。在這種情況下,這種狀態(tài)的改變也就產(chǎn)生了行為的改變。
總結(jié):
多態(tài)意味著 不同的形式。在面向?qū)ο蟮脑O(shè)計(jì)中,我們持有從基類繼承而來(lái)的相同接口,以及使用該接口的不同形式不同版本的多態(tài)綁定方法。 運(yùn)用數(shù)據(jù)的抽象和繼承,能更好的類型和創(chuàng)造多態(tài)的例子。
到此這篇關(guān)于Java編程 多態(tài)的文章就介紹到這了,更多相關(guān)Java多態(tài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Eureka注冊(cè)不上或注冊(cè)后IP不對(duì)(多網(wǎng)卡的坑及解決)
這篇文章主要介紹了Eureka注冊(cè)不上或注冊(cè)后IP不對(duì)(多網(wǎng)卡的坑及解決),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
Spring IOC源碼之bean的注冊(cè)過(guò)程講解
這篇文章主要介紹了Spring IOC源碼之bean的注冊(cè)過(guò)程講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
springsecurity實(shí)現(xiàn)登錄驗(yàn)證以及根據(jù)用戶身份跳轉(zhuǎn)不同頁(yè)面
Spring?Security是一種基于Spring框架的安全技術(shù),用于實(shí)現(xiàn)身份驗(yàn)證和訪問(wèn)控制,本文介紹了如何使用Spring?Security,結(jié)合session和redis來(lái)存儲(chǔ)用戶信息,并通過(guò)編寫特定的登錄處理類和Web配置,實(shí)現(xiàn)用戶登錄和注銷功能2024-09-09
基于Java SSM框架開發(fā)圖書借閱系統(tǒng)源代碼
本文給大家介紹了基于Java SSM框架開發(fā)圖書借閱系統(tǒng),開發(fā)環(huán)境基于idea2020+mysql數(shù)據(jù)庫(kù),前端框架使用bootstrap4框架,完美了實(shí)現(xiàn)圖書借閱系統(tǒng),喜歡的朋友快來(lái)體驗(yàn)吧2021-05-05
Java中BigInteger與BigDecimal類用法總結(jié)
在Java中有兩個(gè)用于大數(shù)字運(yùn)算的類,分別是java.math.BigInteger類 和 java.math.BigDecimal類,這兩個(gè)類都可以用于高精度計(jì)算,BigInteger類是針對(duì)整型大數(shù)字的處理類,而BigDecimal類是針對(duì)大小數(shù)的處理類,接下來(lái)帶大家來(lái)學(xué)習(xí)一下,在Java中如何處理大數(shù)字2023-05-05
Spring中的之啟動(dòng)過(guò)程obtainFreshBeanFactory詳解
這篇文章主要介紹了Spring中的之啟動(dòng)過(guò)程obtainFreshBeanFactory詳解,在refresh時(shí),prepareRefresh后,馬上就調(diào)用了obtainFreshBeanFactory創(chuàng)建beanFactory以及掃描bean信息(beanDefinition),并通過(guò)BeanDefinitionRegistry注冊(cè)到容器中,需要的朋友可以參考下2024-02-02
JDK9對(duì)String字符串的新一輪優(yōu)化
這篇文章主要介紹了JDK9對(duì)String字符串的新一輪優(yōu)化,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
java web請(qǐng)求和響應(yīng)中出現(xiàn)中文亂碼問(wèn)題的解析
這篇文章主要為大家解析了java web請(qǐng)求和響應(yīng)中出現(xiàn)中文亂碼問(wèn)題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10

