Java基礎(chǔ)篇(二)---三大特性

1、封裝

Thinking in java中說道,“封裝”通過合并特征和行為來創(chuàng)建新的數(shù)據(jù)類型?!皩?shí)現(xiàn)隱藏”則通過將細(xì)節(jié)“私有化”把接口和實(shí)現(xiàn)分離開來。

因此,我們可以這樣來解釋封裝,字面上的意思就是包裝的意思,專業(yè)一點(diǎn)就是信息隱藏,是指利用抽象數(shù)據(jù)類型將數(shù)據(jù)以及基于這些數(shù)據(jù)的操作封裝在一起,成為一個(gè)不可分割的獨(dú)立實(shí)體。

外界不能直接訪問數(shù)據(jù),只能通過包裹在數(shù)據(jù)之外的已授權(quán)的操作進(jìn)行交流和交互。數(shù)據(jù)被保護(hù)在抽象數(shù)據(jù)類型的內(nèi)部,盡可能地隱藏內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),只提供一些可以對(duì)其進(jìn)行訪問的公共的方式來與外部發(fā)生聯(lián)系。

抽象數(shù)據(jù)類型(ADT)是指一個(gè)數(shù)學(xué)模型及定義在該模型上的一組操作。 事實(shí)上,抽象數(shù)據(jù)類型體現(xiàn)了程序設(shè)計(jì)中問題分解和信息隱藏的特征。它把問題分解為多個(gè)規(guī)模較小且容易處理的問題,然后把每個(gè)功能模塊的實(shí)現(xiàn)為一個(gè)獨(dú)立單元,通過一次或多次調(diào)用來實(shí)現(xiàn)整個(gè)問題。

對(duì)于封裝而言,一個(gè)對(duì)象它所封裝的就是自己的屬性和方法,所以它不需要依賴任何對(duì)象就能完成自己的操作。

通常情況下,封裝方式有兩種:

  • 將某一功能、屬性抽離出來,獨(dú)立寫成單獨(dú)的方法或類
  • 設(shè)置訪問權(quán)限,類:public(公共的) 、default(默認(rèn)的,不寫就默認(rèn)是它);類中成員:public、protected、default(默認(rèn)的)、private

封裝的好處

  • 減少耦合度,提高代碼的復(fù)用性
  • 隱藏信息,實(shí)現(xiàn)細(xì)節(jié)
  • 類內(nèi)部的結(jié)構(gòu)可以自由修改。即讓我們更容易修改類的內(nèi)部實(shí)現(xiàn),而無需修改使用了該類的客戶代碼。

首先我們來看下面這個(gè)類:Student.java

public class Student{  
      
    /* 
     * 對(duì)屬性的封裝 
     * 一個(gè)人的姓名、性別、年齡、妻子都是這個(gè)人的私有屬性 
     */  
    private String name ;  
    private String sex ;  
    private int age ;  
      
    /* 
     * setter()、getter()是該對(duì)象對(duì)外開發(fā)的接口 
     */  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public String getSex() {  
        return sex;  
    }  
  
    public void setSex(String sex) {  
        this.sex = sex;  
    }  
  
    public int getAge() {  
        return age;  
    }  
  
    public void setAge(int age) {  
        this.age = age;  
    }  
}  

例如:有一天需要修改該類時(shí),例如將age屬性修改為String類型?倘若你只有一處使用了這個(gè)類還好,如果你有幾十個(gè)甚至上百個(gè)這樣地方,你是不是要改到崩潰?如果使用了封裝,我們完全可以不需要做任何修改,只需要稍微改變下該類的setAge()方法即可。

//將age屬性由int類型轉(zhuǎn)變成String類型
  public void setAge(int age) {  
        //轉(zhuǎn)換即可  
        this.age = String.valueOf(age);  
    }  
  • 可以對(duì)成員進(jìn)行更精確的控制例子

例如:如果有時(shí)候腦袋犯渾,不小心把年齡設(shè)置為300歲,麻煩就大了。但是使用封裝我們就可以避免這個(gè)問題,我們對(duì)age的訪問入口做一些控制(setter)如:

  public void setAge(int age) {  
        if(age > 120){  
            System.out.println("ERROR:error age input....");    //提示錯(cuò)誤信息  
        }else{  
            this.age = age;  
        }     
    }  

2、繼承

講解之前我們先看一個(gè)例子

public class Student{
    private String name ;  
    private String sex ;  
    private int age ;  
    private Teacher teacher;
}
public class Teacher{
    private String name ;  
    private String sex ;  
    private int age ;  
    private Student student;
}

從這里我們可以看出,Student、Teacher兩個(gè)類除了各自的Student、Teacher外其余部分全部相同,這樣子就造成了重復(fù)的代碼。盡可能地復(fù)用代碼是程序員一直追求的,而繼承就是復(fù)用代碼的方式之一。

這個(gè)例子我們可以發(fā)現(xiàn)不管是學(xué)生還是老師,他們都是人,他們都擁有人的屬性和行為,同時(shí)也是從人那里繼承來的這些屬性和行為的。

因此代碼可以如下改進(jìn):

public class Person{
    private String name ;  
    private String sex ;  
    private int age ;  
}
public class Teacher extends Person{ 
    private Student student;
}
public class Student extends Person{ 
    private Teacher teacher;
}

可以看出這個(gè)例子使用繼承后,除了代碼量的減少我們還能夠非常明顯的看到他們的關(guān)系。

繼承所描述的是“is-a”的關(guān)系,實(shí)際上繼承者是被繼承者的特殊化,它除了擁有被繼承者的特性外,還擁有自己獨(dú)有的特性。

例如貓有抓老鼠、爬樹等其他動(dòng)物沒有的特性。同時(shí)在繼承關(guān)系中,繼承者完全可以替換被繼承者,反之則不可以,例如我們可以說貓是動(dòng)物,但不能說動(dòng)物是貓就是這個(gè)道理,這樣將貓看做動(dòng)物稱之為“向上轉(zhuǎn)型”。

向上轉(zhuǎn)型將子類轉(zhuǎn)換成父類,在繼承關(guān)系上面是向上移動(dòng)的,所以一般稱之為向上轉(zhuǎn)型。由于向上轉(zhuǎn)型是從一個(gè)叫專用類型向較通用類型轉(zhuǎn)換,所以它總是安全的,唯一發(fā)生變化的可能就是屬性和方法的丟失。這就是為什么編譯器在“未曾明確表示轉(zhuǎn)型”活“未曾指定特殊標(biāo)記”的情況下,仍然允許向上轉(zhuǎn)型的原因。

誠(chéng)然,繼承定義了類如何相互關(guān)聯(lián),共享特性。對(duì)于若干個(gè)相同或者相識(shí)的類,我們可以抽象出他們共有的行為或者屬相并將其定義成一個(gè)父類或者超類,然后用這些類繼承該父類,他們不僅可以擁有父類的屬性、方法還可以定義自己獨(dú)有的屬性或者方法。

同時(shí)在使用繼承時(shí)需要記住三句話:

  1. 子類擁有父類非private的屬性和方法。
  2. 子類可以擁有自己屬性和方法,即子類可以對(duì)父類進(jìn)行擴(kuò)展。
  3. 子類可以用自己的方式實(shí)現(xiàn)父類的方法。

綜上所述,使用繼承確實(shí)有許多的優(yōu)點(diǎn),除了將所有子類的共同屬性放入父類,實(shí)現(xiàn)代碼共享,避免重復(fù)外,還可以使得修改擴(kuò)展繼承而來的實(shí)現(xiàn)比較簡(jiǎn)單。

組合和繼承

組合和繼承是兩種復(fù)用代碼的方法:

組合:只需要在新的類中產(chǎn)生現(xiàn)有類的對(duì)象,由于新的類是由現(xiàn)有類的對(duì)象組成的,所以這個(gè)方法稱為組合,該方法只是復(fù)用了現(xiàn)有程序代碼的功能,而并非它的形式。

繼承:按照現(xiàn)有的類的類型進(jìn)行創(chuàng)建新類。無需改變現(xiàn)有類的形式,采用現(xiàn)有類的形式并在其中添加新的代碼,稱之為繼承。

總得來說,繼承表達(dá)的是“is-a"(是一個(gè))的關(guān)系,而組合表達(dá)的是“has-a”(有一個(gè))的關(guān)系。

在面向?qū)ο缶幊讨?,生成和使用程序最有可能采用的方法就是直接將?shù)據(jù)和方法包裝進(jìn)一個(gè)類中,并使用該類的對(duì)象。也可以運(yùn)用組合技術(shù)使用現(xiàn)有類來開發(fā)新的類;而繼承技術(shù)其實(shí)是不太常用的。

謹(jǐn)慎繼承

上面講了繼承所帶來的諸多好處,那我們是不是就可以大肆地使用繼承呢?送你一句話:慎用繼承。

首先我們需要明確,繼承存在如下缺陷:

  1. 父類變,子類就必須變。
  2. 繼承破壞了封裝,對(duì)于父類而言,它的實(shí)現(xiàn)細(xì)節(jié)對(duì)與子類來說都是透明的。
  3. 繼承是一種強(qiáng)耦合關(guān)系。

所以說當(dāng)我們使用繼承的時(shí)候,我們需要確信使用繼承確實(shí)是有效可行的辦法。那么到底要不要使用繼承呢?《Thinking in java》中提供了解決辦法:?jiǎn)栆粏栕约菏欠裥枰獜淖宇愊蚋割愡M(jìn)行向上轉(zhuǎn)型。如果必須向上轉(zhuǎn)型,則繼承是必要的,但是如果不需要,則應(yīng)當(dāng)好好考慮自己是否需要繼承。

多態(tài)

1、概念定義

一個(gè)引用變量究竟會(huì)指向哪一個(gè)實(shí)例對(duì)象,該引用變量發(fā)出的方法調(diào)用究竟是哪一個(gè)類的實(shí)現(xiàn)方法,必須由程序運(yùn)行期間才能決定。

好處:不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實(shí)現(xiàn)上,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變,即不修改程序代碼就可以改變程序運(yùn)行時(shí)所綁定的具體代碼,讓程序可以選擇多個(gè)運(yùn)行狀態(tài),這就是多態(tài)性。

指向子類的父類引用:既能引用父類的共性,也能使用子類強(qiáng)大的實(shí)現(xiàn)。該引用既可以處理父類對(duì)象,也可以處理子類對(duì)象。當(dāng)相同的消息發(fā)送給子類或者父類的對(duì)象時(shí),該對(duì)象會(huì)根據(jù)自己所屬的引用來執(zhí)行不用的方法。

向上轉(zhuǎn)型:只能使用父類的屬性和方法。對(duì)于子類中存在而父類中不存在的方法,該方法是不能引用的,例如重載;對(duì)于子類重寫父類的方法,調(diào)用這些方法時(shí),使用子類定義的方法。

編譯時(shí)多態(tài)(靜態(tài),運(yùn)行時(shí)不是多態(tài)):重載
運(yùn)行時(shí)多態(tài)(動(dòng)態(tài)綁定,運(yùn)行時(shí)多態(tài)):重寫

2、多態(tài)的實(shí)現(xiàn)

2.1 實(shí)現(xiàn)條件(三個(gè)):繼承、重寫、向上轉(zhuǎn)型(詳講)。

向上轉(zhuǎn)型:由導(dǎo)出類轉(zhuǎn)型為基類,即在多態(tài)中需要將子類的引用賦給父類對(duì)象(指向子類的父類引用),只有這樣該引用才能夠具備技能調(diào)用父類的方法和子類的方法。這是從較專用類型向較通用類型轉(zhuǎn)換,所以總是很安全的。
例子:

public enum Note {
    MIDDLE_C, C_SHARP, B_FLAT; // Etc.
}

class Instrument {
  public void play(Note n) {
    print("Instrument.play()");
  }
}
//繼承
public class Wind extends Instrument {
  // 重寫:
  public void play(Note n) {
    System.out.println("Wind.play() " + n);
  }
}

public class Music {
  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)型
  }
} 
/* Output:
 *  Wind.play() MIDDLE_C
 */

分析: Music.tune()方法接受一個(gè)Instrument引用,同時(shí)也接受任何導(dǎo)出自Instrument的類。在main方法中,當(dāng)一個(gè)Wind引用傳遞到tune()方法時(shí),就會(huì)出現(xiàn)這種情況,而不需要任何類型轉(zhuǎn)換。這樣做是允許的,因?yàn)閃ind是從Instrument繼承而來,所以Instrument的接口必定存在于Wind中。從Wind向上轉(zhuǎn)型到Instrument可能會(huì)“縮小”接口,但不會(huì)比Instrument的全部接口更窄。

忘記對(duì)象類型
Music.Java看起來似乎有些奇怪。為什么所有人都故意忘記對(duì)象的類型呢?在進(jìn)行向上轉(zhuǎn)型時(shí),就會(huì)產(chǎn)生這種情況;并且如果讓tune()方法直接接受一個(gè)Wind引用作為自己的參數(shù),似乎會(huì)更為直觀。但這樣引發(fā)的一個(gè)重要問題是:如果那樣做,就需要?jiǎng)?chuàng)建多個(gè)tune()方法來接受不同類型的參數(shù)。假設(shè)按這種推理,現(xiàn)在再加入Stringed(弦樂)這種Instrument(樂器):

class Stringed extends Instrument {
  public void play(Note n) {
    print("Stringed.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 main(String[] args) {
    Wind flute = new Wind();
    Stringed violin = new Stringed();
    tune(flute); //沒有向上轉(zhuǎn)型
    tune(violin);
  }
} /* Output:
Wind.play() MIDDLE_C
Stringed.play() MIDDLE_C
*/

這樣做行得通,但是有一個(gè)主要缺點(diǎn):必須為添加的每一個(gè)新Instrument類編寫特定類型的方法,這意味著在開始時(shí)就需要更多的編程。

如果我們只寫這樣一個(gè)簡(jiǎn)單的方法,它僅接受基類作為參數(shù),而不是那些特殊的導(dǎo)出類,這樣做情況就會(huì)變得更好,也就是說,如果我們不管導(dǎo)出類的存在,編寫的代碼只是與基類打交道,是更好的方式。這也正是多態(tài)多允許的。

2.2 轉(zhuǎn)機(jī)

運(yùn)行這個(gè)程序后,我們便會(huì)發(fā)現(xiàn)Music.java的難點(diǎn)所在。

public static void tune(Instrument i){
    //...
    i.play(Note.MIDDLE_C);

tune()方法它接受一個(gè)Instrument引用。那么在這種情況下,編譯器怎么樣才能知道這個(gè)Instrument引用指向的是Wind對(duì)象,而不是Brass對(duì)象或Stringed對(duì)象呢?實(shí)際上編譯器無法得知。為了理解這么問題,有必要說明下有關(guān)綁定的問題。

2.2.1 方法調(diào)用綁定

將一個(gè)方法調(diào)用同一個(gè)方法主體關(guān)聯(lián)起來被稱為綁定。若在程序執(zhí)行前進(jìn)行綁定,叫作前期綁定。在運(yùn)行時(shí)根據(jù)對(duì)象的類型進(jìn)行綁定,叫作后期綁定(也叫作動(dòng)態(tài)綁定或運(yùn)行時(shí)綁定)。

Java中除了static方法和final方法(private方法屬于final方法)之外,其他所有的方法都是后期綁定。這意味著通常情況下,我們不必判定是否應(yīng)該進(jìn)行后期綁定,它會(huì)自動(dòng)發(fā)生。
注意:只有非public的方法才可以被覆蓋。只有在導(dǎo)出類中是覆蓋了基類的方法這種情況時(shí),才會(huì)有所謂的基類引用調(diào)用指向的導(dǎo)出類的方法。

為什么要將某個(gè)方法聲明為final呢?它可以防止其他人覆蓋該方法,但更重要的一點(diǎn)或許是:這樣做可以有效地“關(guān)閉”動(dòng)態(tài)綁定,或者說,告訴編譯器不需要對(duì)其進(jìn)行動(dòng)態(tài)綁定。

2.2.2 產(chǎn)生正確的行為

一旦知道Java中所有方法都是通過動(dòng)態(tài)綁定實(shí)現(xiàn)多態(tài)這個(gè)事實(shí)之后,我們就可以編寫只與基類打交道的代碼了,并且這些代碼對(duì)所有的導(dǎo)出類都可以正確運(yùn)行。或者換一種說法,發(fā)送消息給某個(gè)對(duì)象,讓該對(duì)象去斷定應(yīng)該做什么事。

面向?qū)ο蟪绦蛟O(shè)計(jì)中,有一個(gè)經(jīng)典例子就是“幾何形狀”。這個(gè)例子中,有一個(gè)基類Sharp,以及多個(gè)導(dǎo)出類,如Circle、Square、Triangle等。

向上轉(zhuǎn)型可以像下面這條語句這么簡(jiǎn)單:Sharp s = new Cicle();這里創(chuàng)建了一個(gè)Circle對(duì)象,并把得到的引用立即賦值給Sharp,這樣做看似錯(cuò)誤(將一種類型賦值給另一種類型);但實(shí)際上是沒問題的,因?yàn)橥ㄟ^繼承,Circle就是一種Shape。因此,編譯器認(rèn)可這條語句。

2.3 構(gòu)造器和多態(tài)

通常,構(gòu)造器不用于其他種類的方法。涉及到多態(tài)時(shí)仍是如此。盡管構(gòu)造器并不具有多態(tài)性(它們實(shí)際上是static方法,只不過該static聲明是隱式的),但還是非常有必要理解構(gòu)造器怎樣通過多態(tài)在復(fù)雜的層次結(jié)構(gòu)中運(yùn)作。

構(gòu)造器的調(diào)用順序
基類的構(gòu)造器總是在導(dǎo)出類的構(gòu)造過程中被調(diào)用,而且按照繼承層次逐漸向上鏈接,以便每個(gè)基類的構(gòu)造器都能得到調(diào)用。這樣做是有意義的,因?yàn)闃?gòu)造器具有一項(xiàng)特殊任務(wù):檢查對(duì)象是否被正確的構(gòu)造。

導(dǎo)出類只能訪問它自己的成員,不能訪問基類中的成員(基類成員通常是private類型)。只有基類的構(gòu)造器才具有恰當(dāng)?shù)闹R(shí)和權(quán)限來對(duì)自己的元素進(jìn)行初始化。因此,必須令所有構(gòu)造器都得到調(diào)用,否則就不可能正確構(gòu)造完整對(duì)象。這正是編譯器為什么要強(qiáng)制每個(gè)導(dǎo)出類部分都必須調(diào)用構(gòu)造器的原因。

在導(dǎo)出類的構(gòu)造器主體中,如果沒有明確指定調(diào)用某個(gè)基類構(gòu)造器,它就會(huì)“默默”調(diào)用默認(rèn)構(gòu)造器。如果不存在默認(rèn)構(gòu)造器,編譯器就會(huì)報(bào)錯(cuò)(若某個(gè)類沒有構(gòu)造器,編譯器會(huì)自動(dòng)合成出一個(gè)默認(rèn)構(gòu)造器)。

class Meal {
  Meal() { print("Meal()"); }
}

class Bread {
  Bread() { print("Bread()"); }
}

class Cheese {
  Cheese() { print("Cheese()"); }
}

class Lunch extends Meal {
  Lunch() { print("Lunch()"); }
}

class PortableLunch extends Lunch {
  PortableLunch() { print("PortableLunch()");}
}

public class Sandwich extends PortableLunch {
  private Bread b = new Bread();
  private Cheese c = new Cheese();
  public Sandwich() { print("Sandwich()"); }
  public static void main(String[] args) {
    new Sandwich();
  }
} /* Output:
   * Meal()
   * Lunch()
   * PortableLunch()
   * Bread()
   * Cheese()
   * Sandwich()
   */

這也表明了這一復(fù)雜對(duì)象調(diào)用構(gòu)造器要遵照下面的順序:

  1. 在其他任何事物發(fā)生之前,將分配給對(duì)象的存儲(chǔ)空間初始化成二進(jìn)制的零。(防止如果在一個(gè)構(gòu)造器的內(nèi)部調(diào)用正在構(gòu)造的某個(gè)對(duì)象的某個(gè)動(dòng)態(tài)綁定方法,而這個(gè)方法所操作的成員可能還未初始化的災(zāi)難)
  2. 調(diào)用基類構(gòu)造器。這個(gè)步驟會(huì)不斷的反復(fù)遞歸下去,首先是構(gòu)造這種層次結(jié)構(gòu)的根,然后是下一層導(dǎo)出類,等等,直到最低層的導(dǎo)出類。
  3. 按聲明順序調(diào)用成員的初始化方法。
  4. 調(diào)用導(dǎo)出類構(gòu)造器的主體。

2.4 實(shí)現(xiàn)方法

基于繼承的多態(tài):對(duì)于引用子類的父類類型,在處理該引用時(shí),它適用于繼承該父類的所有子類,子類對(duì)象的不同,對(duì)方法的實(shí)現(xiàn)也就不同,執(zhí)行相同動(dòng)作產(chǎn)生的行為也就不同。即當(dāng)子類重寫父類的方法被調(diào)用時(shí),只有對(duì)象繼承鏈中的最末端的方法才會(huì)被調(diào)用。

基于接口的多態(tài): 在接口的多態(tài)中,指向接口的引用必須是指定這實(shí)現(xiàn)了該接口的一個(gè)類的實(shí)例程序,在運(yùn)行時(shí),根據(jù)對(duì)象引用的實(shí)際類型來執(zhí)行對(duì)應(yīng)的方法。

2.5 多態(tài)機(jī)制遵循的原則

當(dāng)父類對(duì)象引用變量 引用 子類對(duì)象時(shí),被引用對(duì)象的類型(子類)而不是引用變量的類型(父類)決定了調(diào)用誰的成員方法,但是這個(gè)被調(diào)用的方法必須是在父類中定義過的,也就是說被子類覆蓋的方法(重寫)。

參考資料:
http://blog.csdn.net/chenssy/article/details/12757911

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,740評(píng)論 18 399
  • 一:java概述:1,JDK:Java Development Kit,java的開發(fā)和運(yùn)行環(huán)境,java的開發(fā)工...
    ZaneInTheSun閱讀 2,815評(píng)論 0 11
  • (一)Java部分 1、列舉出JAVA中6個(gè)比較常用的包【天威誠(chéng)信面試題】 【參考答案】 java.lang;ja...
    獨(dú)云閱讀 7,265評(píng)論 0 62
  • 本文出自 Eddy Wiki ,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 1,275評(píng)論 0 5
  • 右鍵添加"Open in Terminal"選項(xiàng) 如果刷的系統(tǒng)是正版Ubuntu,想在右鍵添加"Open in T...
    Niling閱讀 2,566評(píng)論 0 1

友情鏈接更多精彩內(nèi)容