JAVA-第二階段

一、類變量和類方法

 1、類變量

類變量也叫靜態變量/靜態屬性,是該類的所有對象共享的變量,任何一個該類的對象去訪問它時,取到的都是相同的值,同樣任何一個該類的對象去修改它時,修改的也是同一個變量。

類變量定義方式

訪問修飾符 static 數據類型 變量名;  【推薦】
static 訪問修飾符 數據類型 變量名;

類變量內存佈局

package com.study;
public class run {
    public static void main(String[] args) {
        money money1 = new money();
        money money2 = new money();
        money1.payfee(50);
        money1.payfee(100);
        System.out.println(money.getFee());
    }
}
class money{
    private int salary = 5000;
    private static double fee = 0;
    public void payfee(double fee) {
        money.fee += fee;
    }
    public static double getFee(){
        return fee;
    }
}

類變量訪問方式

類名.類變量名
//或者
對象名.類變量名 //靜態變量訪問修飾符的訪問權限和範圍與普通屬性是一樣的
//推薦使用:類名.類變量名;

類變量使用注意事項和細節討論

1> 什麼時候需要用類變量

當我們需要讓某個類的所有對象都共享一個變量時,就可以考慮使用類變量(靜態變量)

2> 類變量與實例變量(普通屬性)區別

類變量是該類的所有對象共享的,而實例變量是每個對象獨享的

3> 加上static稱爲類變量或靜態變量,否則稱爲實例變量/普通變量/非靜態變量

4> 類變量可以通過 類名.類變量名 或者 對象名.類變量名 來訪問,但java設計者推薦我們使用類名.類變量名方式訪問【前提是滿足訪問修飾符的訪問權限和範圍】

5> 實例變量不能通過類名.類變量名方式訪問

6> 類變量是在類加載時就初始化了,也就是說,即使你沒有創建對象,只要類加載了,就可以使用類變量了

7> 類變量的生命週期是隨類的加載開始,隨着類消亡而銷燬

2、類方法

類方法也叫靜態方法,形式如下:

訪問修飾符 static 數據返回類型 方法名() { }【推薦】
static 訪問修飾符 數據返回類型 方法名() { }

類方法的調用

類名.類方法名
//或者
對象名.類方法名  //前提是滿足訪問修飾符的訪問權限和範圍

 類方法應用案例

package com.study;
public class run {
    public static void main(String[] args) {
        money.payfee(50);
        money.payfee(100);
        System.out.println(money.getFee());  //輸出結果:150
    }
}
class money{
    private int salary = 5000;
    private static double fee = 0;
    public static void payfee(double fee) {
        money.fee += fee;
    }
    public static double getFee(){
        return fee;
    }
}

類方法經典的使用場景

當方法中不涉及到任何和對象相關的成員,則可以將方法設計成靜態方法,提高開發效率

比如:工具類中的方法utils、Math類、Arrays類、Collections 集合類

在程序員實際開發,往往會將一些通用的方法,設計成靜態方法,這樣我們不需要創建對象就可以使用了,比如:打印一維數組,冒泡排序,完成某個計算任務等

package com.study;
public class run {
    public static void main(String[] args) {
        //調用方法都沒有創建對象
        myTools.getSum(10,50);
        myTools.getAbs(-50);
    }
}
class myTools{
    public static void getSum(int n1 , int n2){
        System.out.println(n1 + n2);
    }
    public static void getAbs(int n1){
        System.out.println(Math.abs(n1));  //沒有創建Math對象即可調用abs方法,因爲abs方位是static
    }
}

類方法使用注意事項和細節討論

1> 類方法和普通方法都是隨着類的加載而加載,將結構信息存儲在方法區∶

  類方法中無this的參數

  普通方法中隱含着this的參數

2> 類方法可以通過類名調用,也可以通過對象名調用

package com.study;
public class run {
    public static void main(String[] args) {
        myTools.getSum(10,50);  //通過類名調用
        myTools myTools1 = new myTools();
        myTools1.getSum(10,20);  //通過對象調用
    }
}
class myTools{
    public static void getSum(int n1 , int n2){
        System.out.println(n1 + n2);
    }
    public static void getAbs(int n1){
        System.out.println(Math.abs(n1));
    }
}

3> 普通方法和對象有關,需要通過對象名調用,比如 對象名.方法名(參數),不能通過類名調用

4> 類方法中不允許使用和對象有關的關鍵字,比如this和super;普通方法(成員方法)可以

class myTools{
    private int n1 = 0;
    private static int n2 = 10;
    public static void setN(int n){
        this.n1 = n;  //這樣是錯誤的,類方法中不允許使用和對象有關的關鍵字
    }
    public void setM(int m){
        this.n1 = m;  //普通方法(成員方法)允許使用和對象有關的關鍵字
    }
}

5> 類方法(靜態方法)中只能訪問靜態變量或靜態方法;普通成員方法,既可以訪問非靜態成員,也可以訪問靜態成員

class myTools{
    private int n1 = 0;
    private static int n2 = 10;
    public static void f1(){
        System.out.println(n1);  //這樣是錯誤的,靜態方法中不允許訪問非靜態成員
        System.out.println(n2);  //靜態方法中只能訪問靜態變量或靜態方法
    }
}

小結:靜態方法,只能訪問靜態的成員,非靜態的方法,可以訪問靜態成員和非靜態成員(必須遵守訪問權限)

3、課堂練習

package com.study;
public class run{
    static int count = 9;
    public void count(){
        System.out.println("count=" +(count++));
    }
    public static void main(String args[]) {
        new run().count();    //count=9
        new run().count();    //count=10
        System.out.println(run.count);    //11
    }
}

二、理解main方法

1、main方法是java虛擬機調用

2、java虛擬機需要調用類的main()方法時,和main()方法不在同一個類中,所以main()方法的訪問權限必須是public

3、java虛擬機在執行main()方法時不必創建對象,所以該方法必須是static

4、該方法接收String類型的數組參數,該數組中保存運行java命令時傳遞給所運行類的參數

特別提示

1、在main()方法中,我們可以直接調用main方法所在類的靜態方法或靜態屬性

2、在main()方法中,不能直接訪問該類中的非靜態成員,必須創建該類的一個實例對象後,才能通過這個對象去訪問類中的非靜態成員

package com.study;
public class run{
    private static int num1 = 100;  //定義靜態屬性
    private int num2 = 200;         //定義非靜態屬性
    public static void f1(){        //定義靜態方法
        System.out.println("static的f1方法");
    }
    public void f2(){               //定義非靜態方法
        System.out.println("f2方法");
    }
    public static void main(String[] args){
        //以下調用是正確的
        System.out.println(num1);
        f1();
        //以下調用是錯誤的
        System.out.println(num2);
        f2();
        //需要創建對象之後纔可以訪問非靜態成員
        run run = new run();
        System.out.println(run.num2);
        run.f2();
    }
}

三、代碼塊

1、代碼塊概述

代碼化塊又稱爲初始化塊,屬於類中的成員[即是類的一部分],類似於方法,將邏輯語句封裝在方法體中,通過{ }包圍起來

但和方法不同,沒有方法名,沒有返回,沒有參數,只有方法體,而且不用通過對象或類顯式調用,而是加載類時,或創建對象時隱式調用

[修飾符]{
        代碼塊
    };

說明注意

1> 修飾符可選,要寫的話,也只能寫static

2> 代碼塊分爲兩類,使用static修飾的叫靜態代碼塊,沒有static修飾的叫普通代碼塊/非靜態代碼塊

3> 邏輯語句可以爲任何邏輯語句(輸入、輸出、方法調用、循環、判斷等)

4> " ; "號可以寫上,也可以省略。

代碼塊的好處

1> 相當於另外一種形式的構造器(對構造器的補充機制),可以做初始化的操作

2> 場景:如果多個構造器中都有重複的語句,可以抽取到初始化塊中,提高代碼的重用性

//未使用代碼塊
package com.study;
public class block{
    private String name;
    private int age;
    private double salary;
    //構造器
    public block(String name) {
        System.out.println("程序開始執行...");
        System.out.println("程序執行50%...");
        System.out.println("程序執行完畢...");
        this.name = name;
    }
    public block(String name, int age) {
        System.out.println("程序開始執行...");
        System.out.println("程序執行50%...");
        System.out.println("程序執行完畢...");
        this.name = name;
        this.age = age;
    }
    public block(String name, int age, double salary) {
        System.out.println("程序開始執行...");
        System.out.println("程序執行50%...");
        System.out.println("程序執行完畢...");
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
}
//使用代碼塊
package com.study;
public class block1 {
    private String name;
    private int age;
    private double salary;
    //代碼塊
    {
        System.out.println("程序開始執行...");
        System.out.println("程序執行50%...");
        System.out.println("程序執行完畢...");
    }
    //構造器
    public block1(String name) {
        this.name = name;
        System.out.println("block1(String name)被調用");
    }

    public block1(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("block1(String name, int age)被調用");
    }

    public block1(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
        System.out.println("block1(String name, int age, double salary)被調用");
    }
}

不管我們調用哪個構造器,創建對象,都會先調用代碼塊的內容,代碼塊調用的順序優先於構造器

2、代碼塊使用注意事項和細節討論

1> static代碼塊也叫靜態代碼塊,作用就是對類進行初始化,而且它隨着類的加載而執行,並且只會執行一次;如果是普通代碼塊,每創建一個對象,就執行一次

package com.study;
public class block{
    public static void main(String[] args) {
        //普通代碼塊,每創建一個對象,就執行一次
        AA aa1 = new AA();
        AA aa2 = new AA();
        //輸出結果:
        //AA類的普通代碼塊被執行
        //AA類的普通代碼塊被執行

        //static代碼塊隨着類的加載而執行,並且只會執行一次
        BB bb1 = new BB();
        BB bb2 = new BB();
        //輸出結果:
        //BB類的static代碼塊被執行
    }
}
class AA{
    {
        System.out.println("AA類的普通代碼塊被執行");
    }
}
class BB{
    static {
        System.out.println("BB類的static代碼塊被執行");
    }
}

2> 類什麼時候被加載[重要背!]

① 創建對象實例時(new)

② 創建子類對象實例,父類也會被加載

③ 使用類的靜態成員時(靜態屬性,靜態方法)

package com.study;
public class block{
    public static void main(String[] args) {
        //①創建對象實例時(new)
        AA aa = new AA();
        //輸出結果:AA類的普通代碼塊被執行

        //②創建子類對象實例,父類也會被加載
        BB bb = new BB();
        //輸出結果:
        //AA類的普通代碼塊被執行
        //BB類的普通代碼塊被執行

        //③使用類的靜態成員時(靜態屬性,靜態方法)
        //如果是隻使用類的靜態成員,普通代碼塊並不會執行,static代碼塊在類加載時會執行一次
        System.out.println(CC.salary);
        //輸出結果:
        //CC類的static代碼塊被執行
        //5000
    }
}
class AA{
    {
        System.out.println("AA類的普通代碼塊被執行");
    }
}
class BB extends AA{  //BB類繼承AA類
    {
        System.out.println("BB類的普通代碼塊被執行");
    }
}
class CC{
    public static int salary = 5000;
    static {
        System.out.println("CC類的static代碼塊被執行");
    }
}

3> 普通的代碼塊,在創建對象實例時,會被隱式的調用,對象被創建一次,普通的代碼塊就會調用一次

如果只使用類的靜態成員,普通代碼塊並不會執行。

package com.study;
public class block{
    public static void main(String[] args) {
        System.out.println(AA.salary);
        //輸出結果:
        //AA類的static代碼塊被執行
        //5000
    }
}
class AA{
    public static int salary = 5000;
    {
        System.out.println("AA類的普通代碼塊被執行");
    }
    static {
        System.out.println("AA類的static代碼塊被執行");
    }
}

小結:

① static代碼塊是類加載時執行,且只會執行一次

② 普通代碼塊是在創建對象時調用的,創建一次調用一次

③ 類加載的3種情況,需要記住

4> 創建一個對象時,在一個類裏調用順序(重點,難點)

① 調用靜態代碼塊和靜態屬性初始化 (注意:靜態代碼塊和靜態屬性初始化調用的優先級一樣,如果存在多個靜態代碼塊和多個靜態變量初始化,則按他們定義的順序調用)

② 調用普通代碼塊和普通屬性的初始化 (注意:普通代碼塊和普通屬性初始化調用的優先級一樣,如果有多個普通代碼塊和多個普通屬性初始化,則按定義順序調用)

③ 調用構造方法

package com.study;
public class block{
    public static void main(String[] args) {
        AA aa = new AA();
    }
}
class AA{
    public static int salary = getSalary();
    public int height = getHeight();
    {
        System.out.println("AA類的普通代碼塊被執行");
    }
    static {
        System.out.println("AA類的static代碼塊被執行");
    }
    public int getHeight(){
        System.out.println("getHeight()方法被調用");
        return 180;
    }
    public static int getSalary(){
        System.out.println("getSalary()方法被調用");
        return 10000;
    }
    AA(){
        System.out.println("AA()的無參構造器被調用");
    }
}
//打印結果:
//getSalary()方法被調用
//AA類的static代碼塊被執行
//getHeight()方法被調用
//AA類的普通代碼塊被執行
//AA()的無參構造器被調用

5> 構造器的最前面其實隱含了super()和調用普通代碼塊;靜態相關的代碼塊和屬性初始化在類加載時,就執行完畢了,因此是優先於構造器和普通代碼塊執行的

package com.study;
public class block{
    public static void main(String[] args) {
        BB bb = new BB();
    }
}
class AA{
    {//普通代碼塊
        System.out.println("AA類的普通代碼塊");

    }
    AA(){
        //隱藏代碼1、super();
        //隱藏代碼2、調用本類普通代碼塊
        System.out.println("AA()無參構造器被調用");
    }
}
class BB extends AA{
    {  //普通代碼塊
        System.out.println("BB類的普通代碼塊");
    }
    BB(){
        //隱藏代碼1、super();
        //隱藏代碼2、調用本類普通代碼塊
        System.out.println("BB()無參構造器被調用");
    }
}
//打印結果:
//AA類的普通代碼塊
//AA()無參構造器被調用
//BB類的普通代碼塊
//BB()無參構造器被調用

6> 創建一個子類時(存在繼承關係),他們的靜態代碼塊,靜態屬性初始化,普通代碼塊,普通屬性初始化,構造方法的調用順序如下:

① 父類的靜態代碼塊和靜態屬性(優先級一樣,按定義順序執行)

② 子類的靜態代碼塊和靜態屬性(優先級一樣,按定義順序執行)

③ 父類的普通代碼塊和普通屬性初始化(優先級一樣,按定義順序執行)

④ 父類的構造方法

⑤ 子類的普通代碼塊和普通屬性初始化(優先級一樣,按定義順序執行)

⑥ 子類的構造方法

public class block {
    public static void main(String[] args) {
        BB bb = new BB();
    }
}
class AA{
    {//AA類的普通代碼塊
        System.out.println("AA類的普通代碼塊");
    }
    static {//AA類的static代碼塊
        System.out.println("AA類的static代碼塊");
    }
    private static int age = getAge();
    private int height = getHeight();

    public AA() {
        //隱藏1:super();
        //隱藏2:普通代碼塊和普通屬性初始化
        System.out.println("AA的無參構造器被調用");;
    }
    public static int getAge(){
        System.out.println("static int getAge()被調用");
        return 10;
    }
    public int getHeight(){
        System.out.println("int getHeight()被調用");
        return 175;
    }
}
class BB extends AA{
    {//BB類的普通代碼塊
        System.out.println("BB類的普通代碼塊");
    }
    static {//BB類的static代碼塊
        System.out.println("BB類的static代碼塊");
    }
    private static String name  = getName();
    private int weight = getWeight();

    public BB() {
        //隱藏1:super();
        //隱藏2:普通代碼塊和普通屬性初始化
        System.out.println("BB的無參構造器被調用");;
    }
    public static String getName(){
        System.out.println("static String getName()被調用");
        return "King";
    }
    public  int getWeight(){
        System.out.println("int getWeight()被調用");
        return 140;
    }
}
//打印結果:
//AA類的static代碼塊
//static int getAge()被調用
//BB類的static代碼塊
//static String getName()被調用
//A類的普通代碼塊
//int getHeight()被調用
//AA的無參構造器被調用
//BB類的普通代碼塊
//int getWeight()被調用
//BB的無參構造器被調用

① 靜態屬性初始化和靜態代碼塊是在類加載的時候就會運行的,在創建BB類對象的時候就會要加載BB類,但是BB類繼承了AA類,所以會先加載AA類,所以AA類的靜態方法和靜態代碼塊就會執行

② AA類加載完成之後纔會加載BB類,然後BB類的靜態方法和靜態代碼塊就會執行,執行完畢之後就會走BB類的構造器,但是BB類的構造器隱藏了super();和普通代碼塊

③ 所以就會先執行AA類的構造器,所以會先運行AA類的構造器,AA類的構造器也隱藏了代碼,所以先會執行AA類的普通代碼塊和普通屬性初始化,執行完畢之後執行AA類的構造器

④ 執行完畢之後再返回到BB類的構造器繼續執行BB類隱藏的普通代碼塊和普通屬性初始化,最後再執行BB類的構造器

7> 靜態代碼塊只能直接調用靜態成員 (靜態屬性和靜態方法),普通代碼塊可以調用任意成員

class AA{
    private static int age = 100;
    private int height = 175;
    private static void f1(){
        System.out.println("static void f1()");
    }
    private void f2(){
        System.out.println("void f2()");
    }
    static { //靜態代碼塊
        System.out.println(age);
        System.out.println(height);  //會報錯,用不了
        f1();
        f2();   //會報錯,用不了
    }
    { //普通代碼塊
        System.out.println(age);
        System.out.println(height);
        f1();
        f2(); 
    }
}

四、單例模式

單例(單個的實例),是靜態方法和屬性的經典使用

1> 所謂類的單例設計模式,就是採取一定的方法保證在整個的軟件系統中,對某個類只能存在一個對象實例,並且該類只提供一個取得其對象實例的方法

2> 單例模式有兩種方式:① 餓漢式    ② 懶漢式

餓漢式創建步驟:

1> 將構造器私有化

2> 在類的內部直接創建對象(該對象是static)

3> 提供一個公共的static方法,返回第二步創建的對象

懶漢式創建步驟:

1> 構造器私有化

2> 定義一個 static靜態屬性的對象

3> 提供一個public的static方法,可以返回一個Cat對象

4> 懶漢式,只當用戶使用getCat的時候纔會返回Cat對象,後面再次調用返回的還是第一次創建的cat

public class Singleton {
    public static void main(String[] args) {
        //餓漢式
        GirlFriend gf1 = GirlFriend.getGf();
        System.out.println(gf1);
        GirlFriend gf2 = GirlFriend.getGf();
        System.out.println(gf2);
        System.out.println(gf1 == gf2);
        System.out.println(GirlFriend.age);
        //懶漢式
        System.out.println(Cat.age);
        Cat cat = Cat.getCat();
        System.out.println(cat);
    }
}
//餓漢式
class GirlFriend{
    public static int age = 22;
    private String name;
    //1、將構造器私有化,防止在類外面直接new對象
    private GirlFriend(String name) {
        System.out.println("構造器被調用.....");
        this.name = name;
    }
    //2、在類的內部創建對象(對象需要爲static的)
    private static GirlFriend gf = new GirlFriend("任小可");  //只要類加載了,對象就會被創建
    //3、提供一個public的static方法,用於返回gf對象
    public static GirlFriend getGf(){
        return gf;
    }
    //重寫toString方法
    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}
//懶漢式
class Cat{
    public static int age = 2;
    private String name;
    
    //1、將構造器私有化,防止在類外面直接new對象
    private Cat(String name) {
        System.out.println("構造器被調用.....");
        this.name = name;
    }
    //2、定義一個static對象
    private static Cat cat;
    //3、提供公共的靜態方法用於返回一個對象
    //4.懶漢式,只當用戶使用getCat的時候纔會返回Cat對象,後面再次調用返回的還是第一次創建的cat
    public static Cat getCat(){
        if(cat == null){
            cat = new Cat("小可");
        }
        return cat;
    }
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

1> 二者最主要的區別在於創建對象的時機不同:

  餓漢式是在類加載就創建了對象實例

  懶漢式是在使用時才創建

2> 餓漢式不存在線程安全問題,懶漢式存在線程安全問題

3> 餓漢式存在浪費資源的可能,因爲如果程序員一個對象實例都沒有使用,那麼餓漢式創建的對象就浪費了,懶漢式是使用時才創建,就不存在這個問題

4> 在我們javaSE標準類中,java.lang.Runtime就是經典的單例模式

五、final關鍵字

final 中文意思:最後的、最終的

final 可以修飾類、屬性、方法和局部變量

在某些情況下,程序員可能有以下需求,就會使用到final

1、當不希望類被繼承時,可以用final修飾

2、當不希望父類的某個方法被子類覆蓋/重寫(override)時,可以用final關鍵字修飾

3、當不希望類的某個屬性值被修改,可以用final修飾

4、當不希望某個局部變量被修改,可以使用final修飾

final使用注意事項和細節討論

1、final修飾的屬性又叫常量,一般用XX_XX XX來命名

2、final修飾的屬性在定義時,必須賦初值,並且以後不能再修改,賦值可以在如下位置之一【選擇一個位置賦初值即可】:

①定義時

②在構造器中

③在代碼塊中

class AA{
    //1、定義時
    public final int age1 = 15;
    //2、在構造器中
    public final int age2;
    public AA() {
        age2 = 10;
    }
    //3、在代碼塊中
    public final int age3;
    {
        age3 = 10;
    }
}

3、如果final修飾的屬性是靜態的,則初始化的位置只能是

①定義時

②在靜態代碼塊

不能在構造器中賦值

4、final類不能繼承,但是可以實例化對象

5、如果類不是final類,但是含有final方法,則該方法雖然不能重寫,但是可以被繼承

6、一般來說,如果一個類已經是final類了,就沒有必要再將方法修飾成final方法

7、final不能修飾構造方法(即構造器)

8、final和 static 往往搭配使用,效率更高,不會導致類加載,底層編譯器做了優化處理

public class final_ {
    public static void main(String[] args) {
        System.out.println(AA.age);
    }
}
class AA{
    public final static int age = 15;
    static {
        System.out.println("static代碼塊被加載");
    }
}
//打印結果:15
//只有 15 ,不會打印 static代碼塊被加載

9、包裝類(Integer、Double、Float、Boolean、String)等都是final類

六、抽象類

1、抽象類介紹

當父類的一些方法不能確定但需要聲明時,可以用abstract關鍵字來修飾該方法,這個方法就是抽象方法,用abstract來修飾該類就是抽象類

abstract class Animals{
    private String name;
    private int age;
    //抽象方法就是沒有實現的方法,及沒有方法體
    //一般來說抽象類都會被繼承,在子類中實現抽象方法
    public abstract void f1();
}

1> 用abstract關鍵字來修飾一個類時,這個類就叫抽象類

2> 用abstract關鍵字來修飾一個方法時,這個方法就是抽象方法

3> 抽象類的價值更多作用是在於設計,是設計者設計好後,讓子類繼承並實現抽象類

4> 抽象類是考官比較愛問的知識點,在框架和設計模式使用較多

2、抽象類使用的注意事項和細節討論

1> 抽象類不能被實例化

2> 抽象類不一定要包含abstract方法。也就是說:抽象類可以沒有abstract方法

3> 一旦類包含了abstract方法,則這個類必須聲明爲abstract

4> abstract只能修飾類和方法,不能修飾屬性和其它的

5> 抽象類可以有任意成員【抽象類本質還是類】,比如:非抽象方法、構造器、靜態屬性等等

abstract class Animals{
    private int age;
    private static int height = 100;
    {
        System.out.println("方法體");
    }
    static {
        System.out.println("static方法體");
    }
    public abstract void f1();
    public void f2(){
        System.out.println("f2()方法");
    }
    public static void f3(){
        System.out.println("static void f3()方法");
    }
}

6> 抽象方法不能有主體,即不能實現(沒有方法體就是沒有實現方法;有了方法體即使方法體中沒有內容也看作實現了方法)

7> 如果一個類繼承了抽象類,則它必須實現抽象類的所有抽象方法,除非它自己也聲明爲abstract類

abstract class Animals{
    private int age;
    private static int height = 100;
    public abstract void f1();
    public void f2(){
        System.out.println("f2()方法");
    }
    public abstract void f3();
}
//1、自己聲明abstract類
abstract class Cat extends Animals{

}
//2、實現抽象類的所有抽象方法
class Dog extends Animals{
    @Override
    public void f1() {
        //有了方法體即使方法體中沒有內容也看作實現了方法
    }
    @Override
    public void f3() {
        //有了方法體即使方法體中沒有內容也看作實現了方法
    }
}

8> 抽象方法不能使用private、final和static來修飾,因爲這些關鍵字都是和重寫相違背的

3、抽象類課堂練習

編寫一個Employee類,聲明爲抽象類,包含如下三個屬性:name、id、salary 提供必要的構造器和抽象方法:work()

對於Manager類來說,他既是員工,還具有獎金(bonus)的屬性

請使用繼承的思想,設計CommonEmployee類和Manager類,要求類中提供必要的方法進行屬性訪問,實現work(),提示"經理/普通員工名字工作中...”

請使用OOP的繼承+抽象類完成

public class abstract_ {
    public static void main(String[] args) {
        Mananger king = new Mananger("King", 1001, 8000, 1200);
        CommontEmployee rxk = new CommontEmployee("Rxk", 1002, 5000);
        king.work();
        rxk.work();
    }
}
abstract class Employee{
    private String name;
    private int id;
    private double salary;
    //構造器
    public Employee(String name, int id, double salary) {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }
    //聲明抽象方法work
    abstract public void work();
    //get、set方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
}
class Mananger extends Employee{
    private double bouns;
    public Mananger(String name, int id, double salary, double bouns) {
        super(name, id, salary);
        this.bouns = bouns;
    }
    @Override
    public void work(){
        System.out.println("經理" + getName() + "正在工作中");
    }
}
class CommontEmployee extends Employee{
    public CommontEmployee(String name, int id, double salary) {
        super(name, id, salary);
    }
    @Override
    public void work() {
        System.out.println("員工" + getName() + "正在工作中...");
    }
}

4、抽象類模板設計模式

抽象類體現的就是一種模板模式的設計,抽象類作爲多個子類的通用模板,子類在抽象類的基礎上進行擴展、改造,但子類總體上會保留抽象類的行爲方式

模板設計模式能解決的問題

1、當功能內部一部分實現是確定,一部分實現是不確定的;這時可以把不確定的部分暴露出去,讓子類去實現

2、編寫一個抽象父類,父類提供了多個子類的通用方法,並把一個或多個方法留給其子類實現,就是一種模板模式.

public class abstract_ {
    public static void main(String[] args) {
        Calculate1 calculate1 = new Calculate1();
        calculate1.calTime();
        Calculate2 calculate2 = new Calculate2();
        calculate2.calTime();
    }
}
//有多個類完成不同的任務job,統計各自任務完成的時間
//設計抽象類
abstract class Template{
    public abstract void job();  //定義抽象方法
    public void calTime(){
        long start = System.currentTimeMillis();
        job();  //子類在調用的時候會用到多態綁定機制
        long end = System.currentTimeMillis();
        System.out.println("任務耗時:" + (end - start) + "毫秒");
    }
}
//編寫Calculate1類
class Calculate1 extends Template{
    @Override
    public void job() {  //實現父類的job抽象方法
        int sum = 0;
        for (int i = 1; i <= 800000; i++) {
            sum += i;
        }
    }
}
//編寫Calculate2類
class Calculate2 extends Template{
    @Override
    public void job() { //實現父類的job抽象方法
        int amass = 1;
        for (int i = 1; i <= 800000; i++) {
            amass *= i;
        }
    }
}

上述代碼中計算程序運行時間的代碼是確定的,各個子類中具體job的代碼是不一樣的(不確定的),所以把不確定的部分暴露出去設爲抽象方法,讓子類去實現此方法

上述代碼中父類提供的通用方法就是計算程序運行時間的代碼,然後把job的方法交給了子類

七、接口

1、接口概述

接口就是給出一些沒有實現的方法,封裝到一起,在某個類要使用的時候,再根據具體情況把這些方法寫出來

接口是更加抽象的抽象的類,抽象類裏的方法可以有方法體,接口裏的所有方法都沒有方法體【jdk7.0】

接口體現了程序設計的多態和高內聚低偶合的設計思想

【jdk8.0】後接口類可以有靜態方法、默認方法,也就是說接口中可以有方法的具體實現

//定義接口
package com.study;
public interface DateBase {
    public void connect();
    public void close();
}
//定義類
package com.study;
public class Mysql_ implements DateBase{
    @Override
    public void connect() {
        System.out.println("Mysql數據庫已連接...");
    }
    @Override
    public void close() {
        System.out.println("Mysql數據庫已關閉...");
    }
}
//定義類
package com.study;
public class Orance_ implements DateBase{
    @Override
    public void connect() {
        System.out.println("Orance數據庫已連接...");
    }
    @Override
    public void close() {
        System.out.println("Orance數據庫已關閉...");
    }
}
//主方法測試
package com.study;
public class interface_ {
    public static void main(String[] args) {
        Mysql_ mysql = new Mysql_();
        database(mysql);
        Orance_ orance = new Orance_();
        database(orance);
    }
    public static void database(DateBase db){
        db.connect();
        db.close();
    }
}

2、注意事項和細節

1> 接口不能被實例化

2> 接口中所有的方法是 public方法。接口中抽象方法,可以不用abstract修飾

interface Ainter{
    void aaa();  //實際上是public abstract void aa();
}

3> 一個普通類實現接口,就必須將該接口的所有方法都實現

interface Ainter{
    void aaa();
    void bbb();
}
class testclass implements Ainter{
    @Override
    public void aaa() {
        
    }
    @Override
    public void bbb() {

    }
}

4> 抽象類實現接口,可以不用實現接口的方法

5> 一個類同時可以實現多個接口

interface Ainter{
    void AAA();
}
interface Binter{
    void BBB();
}
class testclass implements Ainter,Binter{
    @Override
    public void AAA() {
    }
    @Override
    public void BBB() {
    }
}

6> 接口中的屬性,只能是final的,而且是 public static final的修飾符

interface Ainter{
    int a = 1;  //實際上是 public static final int a=1;(必須初始化)
}

7> 接口中屬性的訪問形式:接口名.屬性名

8> 接口不能繼承其它的類,但是可以繼承多個別的接口

interface Ainter{
    void AAA();
}
interface Binter{
    void BBB();
}
interface Cinter extends Ainter,Binter{
    void CCC();
}
class testclass implements Cinter{
    //需要實現所有接口的方法,包括Cinter繼承的接口
    @Override
    public void AAA() {
    }
    @Override
    public void BBB() {
    }
    @Override
    public void CCC() {
    }
}

9> 接口的修飾符只能是 public 和 默認,這點和類的修飾符是一樣的

3、實現接口 VS 繼承類

當子類繼承了父類,就自動的擁有父類的功能

如果子類需要擴展功能,可以通過實現接口的方式擴展

可以理解實現接口是對java單繼承機制的一種補充.

public class interface_ {
    public static void main(String[] args) {
        littleMonkey wuKong = new littleMonkey("wuKong");
        wuKong.climb();
        wuKong.swimming();
        wuKong.fly();
    }
}
class Monkey{
    private String name;
    public Monkey(String name) {
        this.name = name;
    }
    public void climb(){
        //悟空只要繼承了這個類,那麼這個類的方法悟空自然就可以使用
        System.out.println(name + "在爬樹...");
    }
    public String getName() {
        return name;
    }
}
interface Fish{
    void swimming();
}
interface Bird{
    void fly();
}
class littleMonkey extends Monkey implements Fish,Bird{
    public littleMonkey(String name) {
        super(name);
    }
    @Override
    public void swimming() {
        //假如悟空想要游泳,但是悟空剛開始沒有游泳這個能力,java單繼承機制又不可能去繼承魚類
        //所以就提供魚接口,讓悟空去實現這個魚接口方法
        System.out.println(getName() + "通過學習會游泳了...");
    }
    @Override
    public void fly() {
        //假如悟空想要飛翔,但是悟空剛開始沒有飛翔這個能力,java單繼承機制又不可能去繼承鳥類
        //所以就提供鳥接口,讓悟空去實現這個鳥接口方法
        System.out.println(getName() + "通過學習會飛翔了...");
    }
}

接口和繼承解決的問題不同

繼承的價值主要在於:解決代碼的複用性和可維護性

接口的價值主要在於:設計,設計好各種規範(方法),讓其它類去實現這些方法,即更加的靈活

接口比繼承更加靈活,繼承是滿足is - a的關係,而接口只需滿足 like - a的關係

猴子 is a 動物,猴子 like(像) a 鳥在飛

接口在一定程度上實現代碼解耦 [即:接口規範性+動態綁定機制]

4、接口的多態性

 1> 多態參數

public class interface_ {
    public static void main(String[] args) {
        //接口多態的體現
        //接口類型的變量 fish1 可以指向 實現了Fish接口的類的對象實例
        Fish fish1 = new Monkey();
        fish1 = new Cat();
        //繼承多態的體現
        //父類類型的變量 aa 可以指向 繼承了父類AA的子類的對象實例
        AA aa = new BB();
        aa = new CC();
    }
}
//接口多態
interface Fish{}
class Monkey implements Fish{}
class Cat implements Fish{}
//繼承多態
class AA{}
class BB extends AA{}
class CC extends AA{}

2> 多態數組

public class interface_ {
    public static void main(String[] args) {
        //*************多態數組的體現***************
        Fish[] fishs = new Fish[2];
        fishs[0] = new Monkey();
        fishs[1] = new Cat();
        for (int i = 0; i < fishs.length; i++) {
            fishs[i].swimming();
            if (fishs[i] instanceof Cat){
                ((Cat) fishs[i]).eat();
            }
        }
        //****************************************
    }
}
//接口多態
interface Fish{
    void swimming();
}
class Monkey implements Fish{
    @Override
    public void swimming() {
        System.out.println("Monkey的swimming()");
    }
}
class Cat implements Fish{
    @Override
    public void swimming() {
        System.out.println("Cat的swimming()");
    }
    public void eat(){
        System.out.println("Cat的eat()");
    }
}

3> 多態傳遞

① 接口類型的變量可以指向實現接口類的父類的子類的實例

public class interface_ {
    public static void main(String[] args) {
        //*************多態傳遞的體現***************
        Monkey littleMonkey = new littleMonkey();
        Fish fish1 = littleMonkey;
        Fish fish2 = new littleMonkey();  //接口類型的變量fish2可以指向實現了Fish類的Monkey類的子類的實例
        Fish fish3 = new Monkey();
        //****************************************
    }
}
//多態傳遞
interface Fish{
    void swimming();
}
class Monkey implements Fish{
    @Override
    public void swimming() {
        System.out.println("Monkey的swimming()");
    }
}
class littleMonkey extends Monkey{

}

② 子接口繼承了父接口,類實現了子接口,相當於類也實現了父接口

public class interface_ {
    public static void main(String[] args) {
        //*************多態傳遞的體現***************
        Cat cat = new Monkey(); 
        // Fish接口繼承了Cat接口,Monkey類實現了Cat接口,相當於Monkey類也實現了Cat接口
        //****************************************
    }
}
//多態傳遞
interface Cat{ }
interface Fish extends Cat{ }
class Monkey implements Fish{ }

5、接口練習

package com.study;
public class interface_ {
    public static void main(String[] args) {
        Testclass testclass = new Testclass();
        System.out.println(testclass.a);
        System.out.println(Testclass.a);  
        //Testclass類實現了Ainter接口,那麼Aniter接口的屬肯定能用
        System.out.println(Ainter.a);
        //輸出結構都是23,沒有任何錯誤
    }
}
interface Ainter{
    int a = 23;  //等價於 public static final int a = 23;
}
class Testclass implements Ainter{

}
interface A{
    int x = 1;
}
class B{
    int x = 2;
}
class C extends B implements A{
    public void returnX(){
        //System.out.println(x); 錯誤的,原因是有兩個X,不知道指向哪個X
        //訪問接口x
        System.out.println(A.x);
        //訪問A類的x
        System.out.println(super.x);
    }
    public static void main(String[] args){
        new C().returnX();
    }
}

八、內部類

1、內部類介紹與分類

一個類的內部又完整的嵌套了另一個類結構

被嵌套的類稱爲內部類(inner class),嵌套其他類的類稱爲外部類(outer class)

外部類是我們類的第五大成員【思考:類的五大成員是哪些?屬性、方法、構造器、代碼塊、內部類】

內部類最大的特點就是可以直接訪問私有屬性,並且可以體現類與類之間的包含關係

注意:內部類是學習的難點,同時也是重點,後面看底層源碼時,有大量的內部類

class Outer{//外部類
    //屬性
    private String name;
    //方法
    public void f1(){
        
    }
    //構造器
    public Outer(String name) {
        this.name = name;
    }
    //代碼塊
    {
        
    }
    //內部類
    class inner{

    }
}
class Other{ //其他類

}

內部類的分類

1> 定義在外部類局部位置上(比如方法內、代碼塊內):

① 局部內部類(有類名)

② 匿名內部類(沒有類名,需要重點掌握!)

2> 定義在外部類的成員位置上

① 成員內部類(沒用static修飾)

② 靜態內部類(使用static修飾)

2、局部內部類的使用

局部內部類是定義在外部類的局部位置,比如方法中、代碼塊中,並且有類名

1> 可以直接訪問外部類的所有成員,包括私有的

class Outer{//外部類
    //屬性
    private String name = "King";
    //方法
    public void f1(){
        class inner{//局部內部類
            public void f2(){
                System.out.println(name);// 可以直接訪問外部類的所有成員,包括私有的
            }
        }
    }
}

2> 不能添加訪問修飾符,因爲它的地位就是一個局部變量,局部變量是不能使用修飾符的。但是可以使用final修飾,因爲局部變量也可以使用final

3> 作用域:僅僅在定義它的方法或代碼塊中

4> 局部內部類訪問外部類的成員的訪問方式:直接訪問

5> 外部類訪問局部內部類成員的訪問方式:創建對象再訪問(注意:必須在作用域內)

public class InnerClass {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.f1();
    }
}
class Outer{//外部類
    //屬性
    private String name = "King";
    //方法
    public void f1(){
        class inner{//局部內部類
            public void f2(){
                System.out.println(name);
            }
        }
        //在f1()方法中(作用域)創建對象再訪問
        inner inner = new inner();
        inner.f2();
    }
}

小結:

(1)局部內部類定義在方法中/代碼塊

(2)作用域在方法體或者代碼塊中

(3)局部內部類本質仍然是一個類

6> 外部其他類不能訪問局部內部類,因爲局部內部類地位是一個局部變量

7> 如果外部類和局部內部類的成員重名時,默認遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問

public class InnerClass {
    public static void main(String[] args) {
        Outer outer1 = new Outer();
        outer1.f1();
        System.out.println(outer1);
    }
}
class Outer{//外部類
    //屬性
    private String name = "King";
    //方法
    public void f1(){
        class inner{
            String name = "Jerry";
            public void f2(){
                System.out.println(name);
                //Outer.this本質就是一個外部類的對象;機那個對象調用了f1(),Outer.this就是哪個對象
                System.out.println(Outer.this.name);// outer1 = Outer.this
            }
        }
        inner inner = new inner();
        inner.f2();
    }
}
//打印結果:
//Jerry
//King
//Outer@2133c8f8
//Outer@2133c8f8

3、匿名內部類

匿名內部類本質是類,是一個內部類,該類沒有名字,同時還是一個對象

說明:匿名內部類是定義在外部類的局部位置,比如方法中、代碼塊中,並且沒有類名

1> 匿名內部類的基本語法

new 類或接口 (參數列表) {
    //類體
};

2> 匿名內部類基本使用

package com.study;
public class innerClass {
    public static void main(String[] args) {
        Method method = new Method();
        method.m1();
    }
}
interface IA{
    void fly();
}
class Father{
    private String name;
    public Father(String name) {
        this.name = name;
        System.out.println("Father類的有參構造器被調用");
    }
    public void m1(){
        System.out.println("Father's name is " + name);
    }
    public void m2(){
        System.out.println("m2()");
    }
}
abstract class Son{
    public void m1(){
        System.out.println("m1方法");
    };
    public abstract void m2();
}
class Method{
    public void m1(){
        //基於接口的匿名內部類
        //ia1的編譯類型=IA,運行類型爲Mnthod$1(即外部類+$1)
        IA ia1 = new IA(){
            @Override
            public void fly() {
                System.out.println("IA接口的fly方法");
            }
        };
        //底層原理爲:
        //class innerClass$1 implements IA{
        //     @Override
        //     public void fly() {
        //     System.out.println("IA接口的fly方法");
        //     }
        // }
        //jdk底層創建匿名內部類Method$1,然後立即創建了Method$1實例,並且把實例地址返回給ia1
        //只不過此匿名內部類使用一次之後就沒有了,但是ia1指向的對象還是在的
        ia1.fly();
        System.out.println("ia1的運行類型=" + ia1.getClass());

        //基於類的匿名內部類
        //father的編譯類型Father、father的運行類型Method$2
        //("King")參數列表會傳遞給Father類的構造器
        Father father = new Father("King"){
            @Override
            public void m2() {
                System.out.println("匿名內部類的m2()");
            }
        };
        System.out.println("father的運行類型=" + father.getClass());
        //m1、m2都可以調用
        father.m1();
        father.m2();
        //底層原理爲:
        //class Method$2 extends Father{
        //    @Override
        //    public void m2() {
        //    System.out.println("匿名內部類的m2()");
        //    }
        //}
        //jdk底層創建匿名內部類Method$2同時繼承Father類,然後立即創建了Method$2實例,並且把實例地址返回給father
        //只不過此匿名內部類使用一次之後就沒有了,但是father指向的對象還是在的
        //如果Father類是一個抽象類,則匿名內部類的內部必須實現抽象類的方法
        //基於抽象類的匿名內部類
        Son son = new Son(){
            @Override
            public void m2() {
                System.out.println("匿名內部類的m2");
            }
        };
        son.m1();
        son.m2();
    }
}

3> 因爲匿名內部類既是一個類的定義,同時它本身也是一個對象,因此從語法上看,它既有定義類的特徵,也有創建對象的特徵,因此有兩種可以調用匿名內部類方法的方式

package com.study;
public class innerClass {
    public static void main(String[] args) {
        Father father = new Father();
        father.f1();
    }
}
class Father{
    private String name = "King";
    public void f1(){
        //第一種調用方法
        AA a = new AA(){
            @Override
            public void m2() {
                System.out.println("匿名內部類重寫了m2");
            }
        };
        a.m1();
        a.m2();  //動態綁定機制====Father$1.m2();
        //第二種調用方法
        new AA(){
            @Override
            public void m2() {
                System.out.println("匿名內部類重寫了m2");
            }
        }.m2();  //可以直接調用,匿名內部類本身也是一個對象
    }
}
class AA{
    public void m1(){
        System.out.println("m1...");
    }
    public void m2(){
        System.out.println("m2...");
    }
}

4> 可以直接訪問外部類的所有成員,包括私有的

5> 不能添加訪問修飾符,因爲它的地位就是一個局部變量

6> 作用域:僅僅在定義它的方法或代碼塊中

7> 匿名內部類訪問外部類成員訪問方式爲直接訪問

8> 外部其他類不能訪問匿名內部類(因爲匿名內部類地位是一個局部變量)

9> 如果外部類和匿名內部類的成員重名時,匿名內部類訪問默認遵循就近原則,如果想訪問外部類的成員則可以使用(外部類名.this.成員)訪問

10> 匿名內部類最佳實踐

匿名內部類可以當作實參進行直接傳遞,簡潔高效

package com.study;
public class innerClass {
    public static void main(String[] args) {
        //第一種
        Cellphone cellphone = new Cellphone();
        cellphone.alarmclock(new Bell(){
            @Override
            public void ring() {
                System.out.println("懶豬起牀了");
            }
        });
        cellphone.alarmclock(new Bell(){
            @Override
            public void ring() {
                System.out.println("小夥伴上課了");
            }
        });
        //第二種
        new Cellphone(){
        }.alarmclock(new Bell(){
            @Override
            public void ring() {
                System.out.println("懶豬起牀了");
            }
        });
        new Cellphone(){
        }.alarmclock(new Bell(){
            @Override
            public void ring() {
                System.out.println("小夥伴上課了");
            }
        });

    }
}
interface Bell{
    void ring();
}
class Cellphone{
    public void alarmclock(Bell bell){
        bell.ring();//動態綁定機制
    }
}

4、成員內部類

成員內部類是定義在外部類的成員位置,並且沒有static修飾

1> 可以直接訪問外部類的所有成員,包括私有的

package com.study;
public class innerClass {
    public static void main(String[] args) {
        new AA().f1();
    }
}
class AA{
    private int age = 10;
    public void m1(){
        System.out.println("m1...");
    }
    class BB{  //成員內部類
        public void m1(){
            System.out.println(age);
        }
    }
    public void f1(){
        new BB().m1();
    }
}

2> 可以添加任意訪問修飾符(public、protected、默認、 private),因爲它的地位就是一個類的成員

3> 作用域和外部類的其他成員一樣,爲整個類體

4> 成員內部類訪問外部類成員的訪問方式:直接訪問

5> 外部類訪問成員內部類的訪問方式:創建對象,再訪問

6> 外部其他類訪問成員內部類的方式

package com.study;
public class innerClass {
    public static void main(String[] args) {
        AA aa = new AA();
        //第一種方式:將成員內部類當作AA類的成員
        AA.BB bb1 = aa.new BB();
        bb1.m1();  //輸出10
        //第二種方式:定義一個方法,返回一個BB類對象
        AA.BB bb2 = aa.getBB();
        bb2.m1();  //輸出10
        //第三種方式:new一個AA類,再在此基礎上newBB類
        AA.BB bb3 = new AA().new BB();
        bb3.m1();  //輸出10
    }
}
class AA{
    private int age = 10;
    public void m1(){
        System.out.println("m1...");
    }
    class BB{  //成員內部類
        public void m1(){
            System.out.println(age);
        }
    }
    public BB getBB(){  //定義一個方法,返回一個BB類對象
        return new BB();
    }
}

7> 如果外部類和內部類的成員重名時,內部類訪問默認遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問

5、靜態內部類

靜態內部類是定義在外部類的成員位置,並且有static修飾

package com.study;
public class innerClass {
    public static void main(String[] args) {
        new AA().m2();
    }
}
class AA{
    private int age = 10;
    private static int height = 175;
    public void m1(){
        System.out.println("m1...");
    }
    static class BB{  //靜態內部類
        public void m1(){
            new AA().m1();
            System.out.println(height);
        }
    }
    public void m2(){
        new BB().m1();
    }
}

1> 可以直接訪問外部類的所有靜態成員,包括私有的,但不能直接訪問非靜態成員

2> 可以添加任意訪問修飾符(public、protected、默認、private),因爲它的地位就是一個成員

3> 作用域與其他的成員一樣,爲整個類體

4> 靜態內部類訪問外部類的方式:直接訪問所有靜態成員

5> 外部類訪問靜態內部類的訪問方式:創建對象,再訪問

6> 外部其他類訪問靜態內部類的方式

package com.study;
public class innerClass {
    public static void main(String[] args) {
        //第一種方式:靜態內部類可以通過類名直接訪問,前提滿足訪問權限
        AA.BB bb1 = new AA.BB();
        bb1.m1();
        //第二種方式:定義一個普通方法,返回一個靜態內部類的實例
        AA aa = new AA();
        AA.BB bb2 = aa.getBB();
        bb2.m1();
        //第三種方式:定義一個static方法,返回一個靜態內部類的實例
        AA.BB bb3 = AA.getBB_();
        bb3.m1();
    }
}
class AA{
    private int age = 10;
    private static int height = 175;
    public void m1(){
        System.out.println("m1...");
    }
    static class BB{  //靜態內部類
        public void m1(){
            new AA().m1();
            System.out.println(height);
        }
    }
    public BB getBB(){ //定義一個普通方法,返回一個靜態內部類的實例
        return new BB();
    }
    public static BB getBB_(){//定義一個static方法,返回一個靜態內部類的實例
        return new BB();
    }
}

7> 如果外部類和靜態內部類的成員重名時,靜態內部類訪問默認遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.成員)去訪問

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章