Java面向對象-封裝、繼承和多態


第一關

任務描述

本關任務:構造一個類,把對象的屬性封裝起來,同時提供一些可以被外界訪問屬性的方法。

相關知識

為了完成本關任務,你需要掌握:
1.什么是封裝;
2.封裝的意義;
3.實現Java封裝的步驟。

什么是封裝

封裝:就是隱藏對象的屬性和實現細節,僅對外提供公共訪問方式。
封裝時的權限控制符區別如下:
link

封裝的意義

對於封裝而言,一個對象它所封裝的是自己的屬性和方法,所以它是不需要依賴其他對象就可以完成自己的操作。使用封裝有四大好處:

  • 良好的封裝能夠減少耦合。
  • 類內部的結構可以自由修改。
  • 可以對成員進行更精確的控制。
  • 隱藏信息,實現細節。
    封裝把一個對象的屬性私有化,同時提供一些可以被外界訪問屬性的方法,如果不想被外界訪問,我們大可不必提供方法給外界訪問。但是如果一個類沒有提供給外界訪問的方法,那么這個類也沒有什么意義了。
實現封裝的步驟
  1. 修改屬性的可見性來限制對屬性的訪問(一般限制為 private ),例如:
public class Person{	
	private String name;
	private int age;
}

這段代碼中,將 name age 屬性設置為私有的,只能本類才能訪問,其他類都不能訪問,如此就對信息進行了隱藏。
2. 對每個值屬性提供對外的公共方法訪問,也就是創建一對賦取值方法,用於對私有屬性的訪問,例如:

/*
*封裝演示
*/
public class Person {
    /*
     * 對屬性的封裝 一個人的姓名、性別和年齡都是這個人的私有屬性
     */
    private String name;
    private String sex;
    private int age;
    /*
     * setter()、getter()是該對象對外開放的接口
     */
     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;
    }
}

采用 this 關鍵字是為了解決實例變量( private String name )和局部變量(setName(String name)中的name變量)之間發生的同名的沖突。
封裝可以使我們容易地修改類的內部實現,而無需修改使用了該類的客戶代碼,就可以對成員變量進行更精確的控制。

public void setAge(int age) {
    if (age > 120) {
        System.out.println("ERROR:error age input...."); // 提示錯誤信息
    } else {
        this.age = age;
    }
}
public String getSexName() {
    if ("0".equals(sex)) {
        sexName = "女";
    } else if ("1".equals(sex)) {
        sexName = "男";
    } else {
        sexName = "人妖";
    }
    return sexName;
}

編程要求

根據提示,在右側編輯器Begin-End處補充代碼:

  • 聲明一個Person類,私有化屬性name和age,並將字段封裝起來;
  • 在Person類中定義一個talk()方法,打印姓名和年齡信息;
  • 在main方法中聲明並實例化一Person對象p,給p中的屬性賦值,調用talk()方法打印 我是:張三,今年:18歲。

測試說明

測試輸入:無
預期輸出:
我是:張三,今年:18歲

實現代碼:

package case1;
public class TestPersonDemo {
 public static void main(String[] args) {
  /********* begin *********/
  // 聲明並實例化一Person對象p
        Person p = new Person();
  // 給p中的屬性賦值
        p.getName();        //不用寫
        p.setName("張三");
        p.getAge();         //不用寫
        p.setAge(18);        
  // 調用Person類中的talk()方法
        p.talk();
  /********* end *********/
 }
}
// 在這里定義Person類
class Person {
 /********* begin *********/
    private static String name;
    private static int age;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
    public static void talk(){
        System.out.println("我是:"+name+",今年:"+age+"歲");
    }
 /********* end *********/
} 

實驗分析

  1. 封裝把一個對象的屬性私有化,同時提供一些可以被外界訪問屬性的方法。(get() 和set())
    • set() :向外部提供設置屬性的set方法
    • get() :向外部提供訪問屬性的get方法
      參考鏈接
  2. 屬性私有化后,不能被其他類直接使用修改,即
    Person p = new Person();
    
    p.name = "張三";        //這樣是不允許的。name是Person類的私有變 量,不能再別的類中修改
    p.setName("張三");     //只能通過調用setName()方法來修改name的值。因為setName是在Person類中的方法
    
  3. static的使用
    靜態方法中不能使用非靜態變量,因為靜態方法不能實例化。
    出錯代碼:
	package case1;
public class TestPersonDemo {
 public static void main(String[] args) {
  /********* begin *********/
  // 聲明並實例化一Person對象p
        Person p = new Person();
  // 給p中的屬性賦值
        p.getName();        //不用寫
        p.setName("張三");
        p.getAge();         //不用寫
        p.setAge(18);        
  // 調用Person類中的talk()方法
        p.talk();
  /********* end *********/
 }
}
// 在這里定義Person類
class Person {
 /********* begin *********/
    private String name; //此處是非靜態變量
    private int age; //此處是非靜態變量
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
    public static void talk(){  //靜態方法
        System.out.println("我是:"+name+",今年:"+age+"歲");
    }
 /********* end *********/
}

此例中,talk()是靜態方法,而name 和 age是非靜態變量,會出錯

src/case1/TestPersonDemo.java:42: error: non-static variable name cannot be referenced from a static context

        System.out.println("我是:"+name+",今年:"+age+"歲");

第2關:什么是繼承,怎樣使用繼承

任務描述

本關任務:掌握繼承的基本概念以及怎么使用繼承。

相關知識

為了完成本關任務,你需要掌握:1.繼承的基本概念;2.繼承的特性;3.子類對象的實例化過程。

繼承的基本概念

所謂繼承:是指可以讓某個類型的對象獲得另一個類型的對象的屬性的方法.
在這里插入圖片描述
兔子和羊屬於食草動物類,獅子和豹屬於食肉動物類。
食草動物和食肉動物又是屬於動物類。
所以繼承需要符合的關系是:is-a,父類更通用,子類更具體。
雖然食草動物和食肉動物都是屬於動物,但是兩者的屬性和行為上有差別,所以子類會具有父類的一般特性也會具有自身的特性。
在講解繼承的基本概念之前,讀者可以先想一想這樣一個問題:現在假設有一個Person類,里面有name與age兩個屬性,而另外一個Student類,需要有name、age、school三個屬性,如圖所示,從這里可以發現Person中已經存在有name和age兩個屬性,所以不希望在Student類中再重新聲明這兩個屬性,這個時候就需要考慮是不是可以將Person類中的內容繼續保留到Student類中,也就是引出了接下來所要介紹的類的繼承概念。在這里插入圖片描述
在這里希望Student類能夠將 Person類的內容繼承下來后繼續使用:
在這里插入圖片描述
Java類的繼承,可用下面的語法來表示:

class 父類 // 定義父類
{
    ...
}
class 子類 extends 父類 // 用extends關鍵字實現類的繼承
{
    ...
}

范例:

public class TestPersonStudentDemo {
    public static void main(String[] args) {
        Student s = new Student();
        // 訪問Person類中的name屬性
        s.name = "張三";
        // 訪問Person類中的age屬性
        s.age = 18;
        // 訪問Student類中的school屬性
        s.school = "哈佛大學";
        System.out.println("姓名:" + s.name + ",年齡:" + s.age + ",學校:" + s.school);
    }
}
class Person {
    String name;
    int age;
}
class Student extends Person {
    String school;
}

輸出結果:
姓名:張三,年齡:18,學校:哈佛大學
由上面的程序可以發現,在Student類中雖然並未定義name與age屬性,但在程序外部卻依然可以調用name或age,這是因為Student類直接繼承自Person類,也就是說Student類直接繼承了Person類中的屬性,所以Student類的對象才可以訪問到父類中的成員。
在這里插入圖片描述

繼承的特性
  • 子類擁有父類非private的屬性和方法;
  • 子類可以擁有自己的屬性和方法,即子類可以對父類進行擴展;
  • 子類可以用自己的方式實現父類的方法;
  • 在Java中只允許單繼承,而不允許多重繼承,也就是說一個子類只能有一個父類,但是Java中卻允許多層繼承,多層繼承就是,例如類C繼承類B,類B繼承類A,所以按照關系就是類A是類B的父類,類B是類C的父類,這是Java繼承區別於C++繼承的一個特性;
  • 提高了類之間的耦合性(繼承的缺點,耦合度高就會造成代碼之間的聯系)。
    多重繼承:
    在這里插入圖片描述
class A{
    ...
}
class B{
    ...
}
class C extends A,B{
    ...
}

由上面可以發現類C同時繼承了類A與類B,也就是說類C同時繼承了兩個父類,這在Java中是不允許的。多層繼承:
在這里插入圖片描述

class A{
    ...
}
class B extends A{
    ...
}
class C extends B{
    ...
}

由上面可以發現類B繼承了類A,而類C又繼承了類B,也就是說類B是類A的子類,而類C則是類A的孫子類。

子類對象的實例化過程

既然子類可以繼承直接父類中的方法與屬性,那父類中的構造方法呢?請看下面的范例:

public class TestPersonStudentDemo1 {
    public static void main(String[] args) {
        Student s = new Student();
    }
}

class Person {
    String name;
    int age;

    // 父類的構造方法
    public Person() {
        System.out.println("1.public Person(){}");
    }
}

class Student extends Person {
    String school;

    // 子類的構造方法
    public Student() {
        System.out.println("2.public Student(){}");
    }
}

輸出結果:
1. public Person(){}
2. public Student(){}
從程序輸出結果中可以發現,雖然程序第3行實例化的是子類的對象,但是程序卻先去調用父類中的無參構造方法,之后再調用了子類本身的構造方法。所以由此可以得出結論,子類對象在實例化時會默認先去調用父類中的無參構造方法,之后再調用本類中的相應構造方法。
實際上在本范例中,在子類構造方法的第一行默認隱含了一個super()語句,上面的程序如果改寫成下面的形式,也是可以的:

class Student extends Person{
    String school ;

    // 子類的構造方法
    public Student(){
        super() ; //實際上在程序的這里隱含了這樣一條語句
        System.out.println("2.public Student(){}");
    }
}

繼承條件下構造方法調用規則如下:

  • 如果子類的構造方法中沒有通過super顯示調用父類的有參構造方法,也沒有通過this顯示調用自身的其他構造方法,則系統會默認先調用父類的無參構造方法。在這種情況下寫不寫super()語句效果都是一樣;
  • 如果子類的構造方法中通過super顯示調用父類的有參構造方法,那將執行父類相應構造方法,而不執行父類無參構造方法;
  • 如果子類的構造方法中通過this顯示調用自身的其他構造方法,在相應構造方法中應用以上兩條規則;
  • 特別注意的是,如果存在多級繼承關系,在創建一個子類對象時,以上規則會多次向更高一級父類應用,一直到執行頂級父類Object類的無參構造方法為止。

編程要求

根據提示,在右側編輯器Begin-End處補充代碼:

  • 聲明一個Animal類,將屬性name和age封裝起來,提供對外的公共訪問方法;
  • 聲明一個Cat類和Dog類,都繼承Animal類,分別定義各自的voice方法和eat方法;
  • 在main方法中分別實例化一個Cat對象和Dog對象,設置各自的屬性並調用這兩個方法,再打印出名字和年齡信息;
  • 具體具體輸出要求請看測試說明。

測試說明:

測試輸入:無
預期輸出:

大花貓喵喵叫

大花貓吃魚
大花貓6歲
大黑狗汪汪叫
大黑狗吃骨頭
大黑狗8歲

實現代碼

package case2;

public class extendsTest {
    public static void main(String args[]) {
        // 實例化一個Cat對象,設置屬性name和age,調用voice()和eat()方法,再打印出名字和年齡信息
        /********* begin *********/
        Cat cat = new Cat();
        cat.setName("大花貓");
        cat.setAge(6);
        cat.voice();
        cat.eat();
        System.out.println(cat.getName() + cat.getAge() + "歲");
        /********* end *********/
        // 實例化一個Dog對象,設置屬性name和age,調用voice()和eat()方法,再打印出名字和年齡信息
        /********* begin *********/
        Dog dog = new Dog();
        dog.setName("大黑狗");
        dog.setAge(8);
        dog.voice();
        dog.eat();
        System.out.println(dog.getName() + dog.getAge() + "歲");
        /********* end *********/
    }
}

class Animal {
    /********* begin *********/
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    /********* end *********/

}

class Cat extends Animal {
    // 定義Cat類的voice()和eat()方法
    /********* begin *********/
    public void voice() {
        System.out.println(super.getName() + "喵喵叫");
    }

    public void eat() {
        System.out.println(super.getName() + "吃魚");
    }
    /********* end *********/
}

class Dog extends Animal {
    // 定義Dog類的voice()和eat()方法
    /********* begin *********/
    public void voice() {
        System.out.println(super.getName() + "汪汪叫");
    }

    public void eat() {
        System.out.println(super.getName() + "吃骨頭");
    }
    /********* end *********/
}

實驗分析


  1. 2.錯誤代碼:

        Cat cat = new Cat();
        cat.name = "大花貓";      //此處錯誤,name是私有變量,只能在Cat類中使用。
        cat.age = 6;
        

第3關 super關鍵字的使用

任務描述

本關任務:掌握super關鍵字的使用。

相關知識

為了完成本關任務,你需要掌握:1.super關鍵字;2.super關鍵字的使用;3.superthis關鍵字的比較。

super關鍵字

在上一節中曾經提到過super的使用,那super到底是什么呢?super關鍵字出現在子類中,我們new子類的實例對象的時候,子類對象里面會有一個父類對象。怎么去引用里面的父類對象呢?使用super來引用,所以可以得出結論:super主要的功能是完成子類調用父類中的內容,也就是調用父類中的屬性或方法。
super關鍵字的使用
super關鍵字的用法如下:

  • super可以用來引用直接父類的實例變量。
  • super可以用來調用直接父類方法。
  • super()可以用於調用直接父類構造函數。

1.super用於引用直接父類實例變量

public class TestSuper1 {
    public static void main(String args[]) {
        Dog d = new Dog();
        d.printColor();
    }
}

class Animal {
    String color = "white";
}

class Dog extends Animal {
    String color = "black";

    void printColor() {
        System.out.println(color);// prints color of Dog class
        System.out.println(super.color);// prints color of Animal class
    }
}

輸出結果:
black
white
在上面的例子中,AnimalDog都有一個共同的屬性:color。 如果我們打印color屬性,它將默認打印當前類的顏色。要訪問父屬性,需要使用super關鍵字指定。
2.通過super來調用父類方法

public class TestSuper2 {
    public static void main(String args[]) {
        Dog d = new Dog();
        d.work();
    }
}

class Animal {
    void eat() {
        System.out.println("eating...");
    }
}

class Dog extends Animal {
    void eat() {
        System.out.println("eating bread...");
    }

    void bark() {
        System.out.println("barking...");
    }

    void work() {
        super.eat();
        bark();
    }
}

輸出結果:
eating...
barking...
在上面的例子中,AnimalDog兩個類都有eat()方法,如果要調用Dog類中的eat()方法,它將默認調用Dog類的eat()方法,因為當前類的優先級比父類的高。所以要調用父類方法,需要使用super關鍵字指定。
3.使用super來調用父類構造函數

public class TestSuper3 {
    public static void main(String args[]) {
        Dog d = new Dog();
    }
}

class Animal {
    Animal() {
        System.out.println("animal is created");
    }
}

class Dog extends Animal {
    Dog() {
        super();
        System.out.println("dog is created");
    }
}

輸出結果:
animal is created
dog is created
注意:如果沒有使用super()或this(),則super()在每個類構造函數中由編譯器自動添加。

super與this關鍵字的比較

super關鍵字:我們可以通過super關鍵字來實現對父類成員的訪問,用來引用當前對象的父類。
this關鍵字:指向自己的引用。
范例:

public class TestAnimalDogDemo {
    public static void main(String[] args) {
        Animal a = new Animal();
        a.eat();
        Dog d = new Dog();
        d.eatTest();
    }
}

class Animal {
    void eat() {
        System.out.println("animal : eat");
    }
}

class Dog extends Animal {
    void eat() {
        System.out.println("dog : eat");
    }

    void eatTest() {
        this.eat(); // this 調用自己的方法
        super.eat(); // super 調用父類方法
    }
}

輸出結果:
animal : eat
dog : eat
animal : eat

上表對 thissuper 的差別進行了比較,從上表中不難發現,用 super或this 調用構造方法時都需要放在首行,所以super 與 this 調用構造方法的操作是不能同時出現的。

編程要求

comment: <> (“編程要求”部分介紹本關任務的具體要求,如實現步驟,規則等,最好能給出效果圖)
根據提示,在右側編輯器Begin-End處補充代碼:
聲明一個名為Person的類,里面有nameage兩個屬性,並聲明一個含有兩個參數的構造方法;
聲明一個名為Student的類,此類繼承自Person類,添加一個屬性school,在子類的構造方法中調用父類中有兩個參數的構造方法;
實例化一個Student類的對象``s,為Student對象s中的school賦值,打印輸出姓名:
張三,年齡:18,學校:哈佛大學。

測試說明

測試輸入:無
預期輸出:
姓名:張三,年齡:18,學校:哈佛大學

實現代碼:

package case3;

public class superTest {
	public static void main(String[] args) {
		// 實例化一個Student類的對象s,為Student對象s中的school賦值,打印輸出信息
		/********* begin *********/
        Student s = new Student();
        //s.name = "張三";      //不為s.name和s.age賦值,父類構造方法中有兩個參數,
        //s.age = 18;           //在子類構造方法調用父類構造方法時,name和age作為實參傳遞
        s.school = "哈佛大學";
        System.out.println("姓名:"+s.name+",年齡:"+s.age+",學校:"+s.school);
		/********* end *********/
	}
}

class Person {
	/********* begin *********/
    String name;
    int age;
    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }
	/********* end *********/
}

class Student extends Person {
	/********* begin *********/
    String school;

    public Student(){               //子類構造方法。
        super("張三",18);         //調用父類構造方法,在此處傳遞姓名和年齡。
    }
	/********* end *********/
}

實驗分析

  1. 子類中調用父類構造方法
    如果沒有使用super()或this(),則super()在每個類構造函數中由編譯器自動添加。
  2. 子類中調用父類普通方法
    AnimalDog兩個類都有eat()方法,如果要調用Dog類中的eat()方法,它將默認調用Dog類的eat()方法,因為當前類的優先級比父類的高。所以要調用父類方法,需要使用super關鍵字指定。
  3. name和age不直接賦值,而是以參數傳值的方式傳遞給父類構造方法。
  4. 子類構造方法中,必須要調用父類構造方法
    參考鏈接
    參考鏈接
    舉例:
public Student(){               //子類構造方法。
        super("張三",18);         //調用父類構造方法,在此處傳遞姓名和年齡。
    }

第4關:方法的重寫與重載

本關任務:掌握方法的重寫與重載。

相關知識

為了完成本關任務,你需要掌握:
1.方法的重寫(override);
2.方法的重載(overload);
3.重寫與重載之間的區別。

方法的重寫(override
  1. 方法的重寫
    子類從父類中繼承方法,有時,子類需要修改父類中定義的方法的實現,這稱做方法的重寫(method overriding)。“重寫”的概念與“重載”相似,它們均是Java“多態”的技術之一,所謂“重載”,即是方法名稱相同,但卻可在不同的場合做不同的事。當一個子類繼承一父類,而子類中的方法與父類中的方法的名稱、參數個數和類型都完全一致時,就稱子類中的這個方法重寫了父類中的方法。“重寫”又稱為“復寫”、“覆蓋”。
  2. 如何使用重寫
class Super {
 訪問權限 方法返回值類型 方法1(參數1) {
     ...
 }
}
class Sub extends Super{
 訪問權限 方法返回值類型 方法1(參數1) —————>復寫父類中的方法
 {
     ...
 }
}

注意:方法重寫時必須遵循兩個原則,否則編譯器會指出程序出錯。

  • 重寫的方法不能比被重寫的方法有更嚴格的訪問權限;
  • 重寫的方法不能比被重寫的方法產生更多的異常(關於異常,在后面會介紹)。
    編譯器加上這兩個限定,是為了與Java語言的多態性(關於方法重寫引起的運行時多態,在后面會詳細講述)特點一致而做出的。這樣限定是出於對程序健壯性的考慮,為了避免程序執行過程中產生訪問權限沖突或有應該捕獲而未捕獲的異常產生。
方法的重載(overload
  1. 方法的重載
    首先回顧一下前面所講的方法的重載,方法重載是指多個方法可以享有相同的名字,但是參數的數量或類型不能完全相同。
    調用方法時,編譯器根據參數的個數和類型來決定當前所使用的方法。方法重載為程序的編寫帶來方便,是OOP多態性的具體變現。在Java系統的類庫中,對許多重要的方法進行重載,為用戶使用這些方法提供了方便。
  2. 重載的規則
  • 被重載的方法必須改變參數列表(參數個數或類型不一樣);
  • 被重載的方法可以改變返回類型;
  • 被重載的方法可以改變訪問修飾符;
  • 被重載的方法可以聲明新的或更廣的檢查異常;
  • 方法能夠在同一個類中或者在一個子類中被重載。
  • 無法以返回值類型作為重載函數的區分標准。
重寫與重載之間的區別

方法的重寫和重載是Java多態性的不同表現,重寫是父類與子類之間多態性的一種表現,重載可以理解成多態的具體表現形式。

  • 方法重載是一個類中定義了多個方法名相同,而他們的參數的數量不同或數量相同而類型和次序不同,則稱為方法的重載;
  • 方法重寫是在子類存在方法與父類的方法的名字相同而且參數的個數與類型一樣,返回值也一樣的方法,就稱為方法的重寫;
  • 方法重載是一個類的多態性表現,而方法重寫是子類與父類的一種多態性表現。

編程要求

根據提示,在右側編輯器補充代碼。

  • 聲明一個名為Person的類,里面聲明name與age兩個屬性,定義talk()方法返回姓名和年齡信息;
  • 聲明一個名為Student的類,此類繼承自Person類,添加school屬性,聲明帶三個參數的構造方法,復寫talk()方法,在該方法中調用父類的talk()方法,返回姓名、年齡和學校信息;
  • 實例化子類對象s,調用talk()方法打印我是:張三,今年:18歲,我在哈佛大學上學。

測試說明

測試輸入: 無
預期輸出:
我是:張三,今年:18歲,我在哈佛大學上學

實現代碼

package case4;

public class overridingTest {
	public static void main(String[] args) {
		// 實例化子類對象s,調用talk()方法打印信息
		/********* begin *********/
        Student s = new Student("張三",18,"哈佛大學");
        System.out.println(s.talk());
		/********* end *********/
		
	}
}

class Person {
	/********* begin *********/
    String name;
    int age;
    public String talk(){
        return "我是:"+this.name+",今年:"+this.age+"歲";
    }
	/********* end *********/
}

class Student extends Person {
	/********* begin *********/
    String school;

    public Student(String name,int age,String school){
        this.name = name;
        this.age = age;
        this.school = school;
    }

    public String talk(){
        return super.talk()+",我在"+this.school+"上學";
    }
	/********* end *********/
}

實驗分析

  1. 構造方法相當於給屬性賦值,本題中直接在子類構造方法中賦值。
  2. talk()方法同時返回姓名和年齡信息時,可以有這樣的語句
    return "我是:"+this.name+",今年:"+this.age+"歲";
    return super.talk()+",我在"+this.school+"上學";


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM