【Java】類的結構

類與對象

類中主要包括五種結構,下面進行對這五種結構進行詳細的介紹。

1. 面向對象與面向過程

  • 面向過程:強調的是功能行爲,以函數爲最小單位,考慮怎麼做。
  • 面向對象:強調具備了功能的對象,以類/對象爲最小單位,考慮誰來做。--唯物主義(物質決定意識)

2. 類與對象的關係

  • 類:對一類事物的描述,是抽象的、概念上的定義
  • 對象:是實際存在的該類事物的每個個體,因而也稱爲實例(instance)

面向對象程序設計的重點是類的設計,設計類就是設計類的成員。
二者的關係:對象,是由類new出來的,派生出來的。

3. 面向對象思想實現的規則

  1. 創建類,設計類的成員
  2. 創建類的對象
  3. 通過對象.屬性 或 對象.方法調用對象的結構

補充:幾個概念的使用說明

  • 屬性 = 成員變量 = field = 域、字段
  • 方法 = 成員方法 = 函數 = method
  • 創建類的對象 = 類的實例化 = 實例化類

4. 對象的創建與對象的內存解析

典型代碼:

Person p1 = new Person();
Person p2 = new Person();
Person p3 = p1;//沒有新創建一個對象,共用一個堆空間中的對象實體。
/*說明:
*如果創建了一個類的多個對象,則每個對象都獨立的擁有一套類的屬性。(非static的)
*意味着:如果我們修改一個對象的屬性a,則不影響另外一個對象屬性a的值。
*/

內存解析:

5. JVM內存結構

編譯完源程序以後,生成一個或多個字節碼文件。我們使用JVM中的類的加載器和解釋器對生成的字節碼文件進行解釋運行。意味着,需要將字節碼文件對應的類加載到內存中,涉及到內存解析。

虛擬機棧:即爲平時提到的棧結構。我們將局部變量存儲在棧結構中

虛擬機堆:我們將new出來的結構(比如:數組、對象)加載在對空間中。

補充:對象的屬性(非static的)加載在堆空間中。

方法區:類的加載信息、常量池、靜態域

6. 匿名對象:

我們創建的對象,沒顯式的賦給一個變量名。即爲匿名對象

特點:匿名對象只能調用一次。

new Phone().sendEmail();
new Phone().playGame();
        
new Phone().price = 1999;
new Phone().showPrice();//0.0

應用場景:

PhoneMall mall = new PhoneMall();

//匿名對象的使用
mall.show(new Phone());
其中,
class PhoneMall{
    public void show(Phone phone){
        phone.sendEmail();
        phone.playGame();
    }
}

7. "萬事萬物皆對象"

在Java語言範疇中,我們都將功能、結構等封裝到類中,通過類的實例化,來調用具體的功能結構

  • Scanner,String等
  • 文件:File
  • 網絡資源:URL

涉及到Java語言與前端HTML、後端的數據庫交互時,前後端的結構在Java層面交互時,都體現爲類、對象。

類的結構之一:屬性

1. 屬性 vs 局部變量

1.1 相同點:

  • 定義變量的格式:數據類型 變量名 = 變量值

  • 先聲明,後使用

  • 變量都其對應的作用域

1.2 不同點:

1.2.1 在類中聲明的位置的不同

  • 屬性:直接定義在類的一對{}內
  • 局部變量:聲明在方法內、方法形參、代碼塊內、構造器形參、構造器內部的變量。

1.2.2 關於權限修飾符的不同

  • 屬性:可以在聲明屬性時,指明其權限,使用權限修飾符。
  • 常用的權限修飾符:private、public、缺省、protected --->封裝性
  • 目前,聲明屬性時,使用缺省就可以。
  • 局部變量:不可以使用權限修飾符。

1.2.3 默認初始化值的情況:

  • 屬性:類的屬性,根據其類型,都默認初始化值。
  • 整型(byte、short、int、long:0)
  • 浮點型(float、double:0.0)
  • 字符型(char:0 (或'\u0000'))
  • 布爾型(boolean:false)
  • 引用數據類型(類、數組、接口:null)
  • 局部變量:沒默認初始化值。

    意味着,我們在調用局部變量之前,一定要顯式賦值。
    特別地:形參在調用時,我們賦值即可。

1.2.4 在內存中加載的位置:

  • 屬性:加載到堆空間中 (非static)
  • 局部變量:加載到棧空間

2. 變量的分類:

方式一:按照數據類型:

方式二:按照在類中聲明的位置:

類的結構之二:方法

定義:描述類應該具的功能。

1. 方法舉例:

1.1 JDK中的方法:

  • Math類:sqrt() random() ...
  • Scanner類:nextXxx() ...
  • Arrays類:sort() binarySearch() toString() equals() ...

1.2 自定義的方法:

public void eat(){
    System.out.printly("我要喫飯!!!");
}

public String getNation(String nation){
    System.out.printly("當前地點爲" + nation);
}

1.3 方法的聲明:

權限修飾符 返回值類型  方法名(形參列表){

      方法體

}

注意:static、final、abstract 來修飾的方法,後面再聊。

2. 方法的說明:

2.1 關於權限修飾符:

Java規定的4種權限修飾符:private、public、缺省、protected

詳細內容請查看文章《細說面向對象三大特徵》

2.2 返回值類型:

2.2.1 返回值 vs 沒返回值

  • 如果方法有返回值,則必須在方法聲明時,指定返回值的類型。同時,方法中,需要使用return關鍵字來返回指定類型的變量或常量:return 數據。
  • 如果方法沒返回值,則方法聲明時,使用void來表示。通常,沒返回值的方法中,就不需要使用return。如果使用的話,只能使用 return; 表示結束此方法的意思。

2.2.2 方法該不該定義返回值?

  1. 題目要求

  2. 具體問題具體分析

2.3 方法命名規範

  • 屬於標識符,遵循標識符的規則和規範,“見名知意”
  • 方法名應遵循小駝峯命名 aaaBbbCcc
  • 方法名最好使用英文單詞,不要使用拼音或者縮寫
  • 更多規範要求請參考《Java開發手冊》

2.4 關於形參列表

方法可以聲明0個,1個,或多個形參。

也可以使用可變形參,但可變形參必須放到最後,詳細說明請查看本章第五部分。

格式:數據類型1 形參1, 數據類型2 形參2 .....

定義方法時,該不該定義形參?

  1. 題目要求

  2. 具體問題具體分析

2.5 方法體

是方法中功能的體現,通過循環分支、條件判斷等語句完成複雜的邏輯關係。

方法中可以調用其他方法,同類中可以直接調用,不同類中通過類的實例化對象調用。

注意:方法中不可以定義新的方法

3. 方法的使用

  • 同類中的方法可以直接調用當前類的屬性或方法,不同類中通過類的實例化對象調用。
  • 特殊的:方法A中又調用了自身---遞歸方法。(自身調用)

關於遞歸方法的使用請查看本章第七部分。

4. 方法的重載

4.1 重載的概念

在同一個類中,允許存在一個以上的同名方法,只要它們的參數個數或者參數類型不同即可。

"兩同一不同":

  • 相同:同一個類、相同方法名
  • 參數列表不同:參數個數不同,參數類型不同

4.2 構成重載的實例:

// 舉例一:Arrays類中重載的sort() / binarySearch();PrintStream中的println()
// 舉例二:
// 如下的4個方法構成了重載
public void getSum(int i,int j){
    System.out.println("1");
}

public void getSum(double d1,double d2){
    System.out.println("2");
}

public void getSum(String s ,int i){
    System.out.println("3");
}

public void getSum(int i,String s){
    System.out.println("4");
}

不構成重載的實例:

// 如下的3個方法不能與上述4個方法構成重載
public int getSum(int i,int j){
    return 0;
}
    
public void getSum(int m,int n){
        
}
    
private void getSum(int i,int j){
    
}

4.3 重載方法判斷

如何判斷是否構成方法重載?
嚴格按照定義判斷:兩同一不同。跟方法的權限修飾符、返回值類型、形參變量名、方法體都沒關係!

如何確定類中某一個方法的調用:
①方法名 ---> ②參數列表

5. 可變個數形參方法

5.1 使用說明

  • JDK 5.0新增的內容

  • JDK 5.0以前:採用數組形參來定義方法,傳入多個同一類型變量
    public static void test(int a, String[] books);

  • JDK 5.0以後:採用可變個數形參來定義方法,傳入多個同一類型變量
    public static void test(int a, String ... books);

具體使用:

  • 可變個數形參的格式:數據類型... 變量名

  • 當調用可變個數形參的方法時,傳入的參數個數可以是:0個,1個,2個,.....

  • 可變個數形參的方法與本類中方法名相同,形參不同的方法之間構成重載

  • 可變個數形參的方法與本類中方法名相同,形參類型也相同的數組之間不構成重載。換句話說,二者不能共存。

  • 可變個數形參在方法的形參中,必須聲明在末尾

  • 可變個數形參在方法的形參中,最多隻能聲明一個可變形參

5.2 舉例說明

public void show(int i){

}

public void show(String s){
    System.out.println("show(String)");
}

public void show(String ... strs){
    System.out.println("show(String ... strs)");

    for(int i = 0;i < strs.length;i++){
        System.out.println(strs[i]);
    }
}
// 不能與上一個方法同時存在
//  public void show(String[] strs){
//      
//  }
// 調用時:可變形參與數組類似
test.show("hello");
test.show("hello","world");
test.show();

test.show(new String[]{"AA","BB","CC"});

6. Java的值傳遞機制

6.1 針對方法內變量的賦值舉例:

System.out.println("***********基本數據類型:****************");
int m = 10;
int n = m;

System.out.println("m = " + m + ", n = " + n);

n = 20;

System.out.println("m = " + m + ", n = " + n);

System.out.println("***********引用數據類型:****************");

Order o1 = new Order();
o1.orderId = 1001;

Order o2 = o1;//賦值以後,o1和o2的地址值相同,都指向了堆空間中同一個對象實體。

System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);

o2.orderId = 1002;

System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);

規則: 如果變量是基本數據類型,此時賦值的是變量所保存的數據值。 如果變量是引用數據類型,此時賦值的是變量所保存的數據的地址值。

6.2 針對於方法的參數概念

形參:方法定義時,聲明的小括號內的參數 實參:方法調用時,實際傳遞給形參的數據

6.3 Java中參數傳遞機制:值傳遞機制

規則:

  • 如果參數是基本數據類型,此時實參賦給形參的是實參真實存儲的數據值。
  • 如果參數是引用數據類型,此時實參賦給形參的是實參存儲數據的地址值。

推廣:

  • 如果變量是基本數據類型,此時賦值的是變量所保存的數據值。
  • 如果變量是引用數據類型,此時賦值的是變量所保存的數據的地址值。

6.4 內存解析:

內存解析畫法要點:

  1. 內存結構:棧(局部變量)、堆(new出來的結構:對象(非static成員變量)、數組
  2. 變量:成員變量 vs 局部變量(方法內、方法形參、構造器內、構造器形參、代碼塊內)

舉例一

舉例二

7. 遞歸方法

遞歸方法:一個方法體內調用它自身。

方法遞歸包含了一種隱式的循環,它會重複執行某段代碼,但這種重複執行無須循環控制。 遞歸一定要向已知方向遞歸,否則這種遞歸就變成了無窮遞歸,類似於死循環。

遞歸方法舉例:

// 例1:計算1-n之間所自然數的和
public int getSum(int n) {// 3

    if (n == 1) {
        return 1;
    } else {
        return n + getSum(n - 1);
    }

}

// 例2:計算1-n之間所自然數的乘積:n!
public int getSum1(int n) {

    if (n == 1) {
        return 1;
    } else {
        return n * getSum1(n - 1);
    }

}

//例3:已知一個數列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
//其中n是大於0的整數,求f(10)的值。
public int f(int n){
    if(n == 0){
        return 1;
    }else if(n == 1){
        return 4;
    }else{
        //          return f(n + 2) - 2 * f(n + 1);
        return 2*f(n - 1) + f(n - 2);
    }
}

//例4:斐波那契數列

//例5:漢諾塔問題

//例6:快排

8. 方法的重寫

8.1 什麼是方法的重寫(override 或 overwrite)?

子類繼承父類以後,可以對父類中同名同參數的方法,進行覆蓋操作.

8.2 重寫的應用:

重寫以後,當創建子類對象以後,通過子類對象調用子父類中的同名同參數的方法時,實際執行的是子類重寫父類的方法。

8.3 重寫舉例:

// 父類
class Circle{
public double findArea(){}//求面積
}
// 子類
class Cylinder extends Circle{
public double findArea(){}//求表面積
}
**********************************************
// 父類
class Account{
public boolean withdraw(double amt){}
}
// 子類
class CheckAccount extends Account{
public boolean withdraw(double amt){}
}

8.4 重寫的規則:

方法的聲明:

權限修飾符  返回值類型  方法名(形參列表) throws 異常的類型{
    //方法體
}

約定俗成:子類中的叫重寫的方法,父類中的叫被重寫的方法

  1. 子類重寫的方法的方法名和形參列表與父類被重寫的方法的方法名和形參列表相同
  2. 子類重寫的方法的權限修飾符不小於父類被重寫的方法的權限修飾符

    特殊情況:子類不能重寫父類中聲明爲private權限的方法

  3. 返回值類型:
    • 父類被重寫的方法的返回值類型是void,則子類重寫的方法的返回值類型只能是void
    • 父類被重寫的方法的返回值類型是A類型,則子類重寫的方法的返回值類型可以是A類或A類的子類
    • 父類被重寫的方法的返回值類型是基本數據類型(比如:double),則子類重寫的方法的返回值類型必須是相同的基本數據類型(必須也是double)
  4. 子類重寫的方法拋出的異常類型不大於父類被重寫的方法拋出的異常類型(具體放到異常處理時候講)

    子類和父類中的同名同參數的方法要麼都聲明爲非static的(考慮重寫,要麼都聲明爲static的(不是重寫)。

    開發中一般保持子父類一致

8.5 面試題:

區分方法的重寫和重載?

二者的概念:

  • 方法的重寫:子類繼承父類以後,可以對父類中同名同參數的方法,進行覆蓋操作.
  • 方法的重載:在同一個類中,允許存在一個以上的同名方法,只要它們的參數個數或者參數類型不同即可。

重載和重寫的具體規則:

  • 重載:兩同一不同,
  • 重寫:全相同,一模一樣

多態性:

  • 重載:不表現爲多態性。
  • 重寫:表現爲多態性。

從編譯和運行的角度看:

  • 重載,是指允許存在多個同名方法,而這些方法的參數不同。編譯器根據方法不同的參數表,對同名方法的名稱做修飾。對於編譯器而言,這些同名方法就成了不同的方法。它們的調用地址在編譯期就綁定了。Java的重載是可以包括父類和子類的,即子類可以重載父類的同名不同參數的方法。
  • 所以對於重載而言,在方法調用之前,編譯器就已經確定了所要調用的方法,這稱爲“早綁定”或“靜態綁定”;
  • 而對於多態,只等到方法調用的那一刻,解釋運行器纔會確定所要調用的具體方法,這稱爲“晚綁定”或“動態綁定”。

    引用一句Bruce Eckel的話:“不要犯傻,如果它不是晚綁定,它就不是多態。”

類的結構之三:構造器

1. 構造器(或構造方法):Constructor

構造器的作用:(只要造對象就得用構造器)

  1. 創建對象

  2. 初始化對象的信息

2. 使用說明:

  • 如果沒顯式的定義類的構造器的話,則系統默認提供一個空參的構造器
  • 定義構造器的格式:權限修飾符 類名(形參列表){ }
  • 一個類中定義的多個構造器,彼此構成重載
  • 一旦我們顯式的定義了類的構造器之後,系統就不再提供默認的空參構造器
  • 一個類中,至少會有一個構造器

3. 構造器舉例:

//構造器不等於方法
public Person(){
    System.out.println("Person().....");
}

public Person(String n){
    name = n;

}

public Person(String n,int a){
    name = n;
    age = a;
}

構造器默認權限和類的權限一致

4. 屬性賦值順序

總結:屬性賦值的先後順序

  1. ① 默認初始化

  2. ② 顯式初始化

  3. ③ 構造器中初始化

  4. ④ 通過"對象.方法" 或 "對象.屬性"的方式,賦值

以上操作的先後順序:① - ② - ③ - ④

5. JavaBean的概念

所謂JavaBean,是指符合如下標準的Java類:

  • 類是公共的
  • 一個無參的公共的構造器
  • 屬性,且對應的get、set方法

類的結構之四:代碼塊

代碼塊(初始化塊)(重要性較屬性、方法、構造器差一些)

1. 代碼塊的作用:

用來初始化類、對象的信息

2. 分類:

代碼塊要是使用修飾符,只能使用static 分類:靜態代碼塊 vs 非靜態代碼塊

3. 靜態代碼塊 vs 非靜態代碼塊區別

靜態代碼塊:

  • 內部可以輸出語句
  • 隨着類的加載而執行,而且只執行一次
  • 作用:初始化類的信息
  • 如果一個類中定義了多個靜態代碼塊,則按照聲明的先後順序執行
  • 靜態代碼塊的執行要優先於非靜態代碼塊的執行
  • 靜態代碼塊內只能調用靜態的屬性、靜態的方法,不能調用非靜態的結構

非靜態代碼塊:

  • 內部可以輸出語句
  • 隨着對象的創建而執行
  • 每創建一個對象,就執行一次非靜態代碼塊
  • 作用:可以在創建對象時,對對象的屬性等進行初始化
  • 如果一個類中定義了多個非靜態代碼塊,則按照聲明的先後順序執行
  • 非靜態代碼塊內可以調用靜態的屬性、靜態的方法,或非靜態的屬性、非靜態的方法

    注意:實例化子類對象時,涉及到父類、子類中靜態代碼塊、非靜態代碼塊、構造器的加載順序:由父及子,靜態先行。

舉例一

class Root{
    static{
        System.out.println("Root的靜態初始化塊");
    }
    {
        System.out.println("Root的普通初始化塊");
    }
    public Root(){
        System.out.println("Root的無參數的構造器");
    }
}
class Mid extends Root{
    static{
        System.out.println("Mid的靜態初始化塊");
    }
    {
        System.out.println("Mid的普通初始化塊");
    }
    public Mid(){
        System.out.println("Mid的無參數的構造器");
    }
    public Mid(String msg){
        //通過this調用同一類中重載的構造器
        this();
        System.out.println("Mid的帶參數構造器,其參數值:"
                           + msg);
    }
}
class Leaf extends Mid{
    static{
        System.out.println("Leaf的靜態初始化塊");
    }
    {
        System.out.println("Leaf的普通初始化塊");
    }   
    public Leaf(){
        //通過super調用父類中有一個字符串參數的構造器
        super("調用父類構造器");
        System.out.println("Leaf的構造器");
    }
}
public class LeafTest{
    public static void main(String[] args){
        new Leaf(); 
        //new Leaf();
    }
}

舉例二

class Father {
    static {
        System.out.println("11111111111");
    }
    {
        System.out.println("22222222222");
    }

    public Father() {
        System.out.println("33333333333");
    }
}

public class Son extends Father {
    static {
        System.out.println("44444444444");
    }
    {
        System.out.println("55555555555");
    }
    public Son() {
        System.out.println("66666666666");
    }

    public static void main(String[] args) { // 由父及子 靜態先行
        System.out.println("77777777777");
        System.out.println("************************");
        new Son();
        System.out.println("************************");

        new Son();
        System.out.println("************************");
        new Father();
    }
}

4. 屬性的賦值順序

  1. ① 默認初始化

  2. ② 顯式初始化 / ⑤在代碼塊中賦值

  3. ③ 構造器中初始化

  4. ④ 有了對象以後,可以通過"對象.屬性"或"對象.方法"的方式,進行賦值

執行的先後順序:① - ② / ⑤ - ③ - ④

類的結構之五:內部類

內部類:類的第五個成員

1. 內部類的定義:

Java中允許將一個類A聲明在另一個類B中,則類A就是內部類,類B稱爲外部類。

2. 內部類的分類:

成員內部類(靜態、非靜態 ) 局部內部類(方法內、代碼塊內、構造器內)

3. 成員內部類的理解:

一方面,作爲外部類的成員:

  • 調用外部類的結構
  • 可以被static修飾
  • 可以被4種不同的權限修飾

另一方面,作爲一個類:

  • 類內可以定義屬性、方法、構造器等
  • 可以被final修飾,表示此類不能被繼承。言外之意,不使用final,就可以被繼承
  • 可以被abstract修飾

4. 成員內部類:

4.1 如何創建成員內部類的對象?(靜態的,非靜態的)

// 創建靜態的Dog內部類的實例(靜態的成員內部類):
Person.Dog dog = new Person.Dog();

// 創建非靜態的Bird內部類的實例(非靜態的成員內部類):
// Person.Bird bird = new Person.Bird();//錯誤的
Person p = new Person();
Person.Bird bird = p.new Bird();

4.2 如何在成員內部類中調用外部類的結構?

class Person{
    String name = "小明";
    public void eat(){
    }
    //非靜態成員內部類
    class Bird{
        String name = "杜鵑";
        public void display(String name){
            System.out.println(name);//方法的形參
            System.out.println(this.name);//內部類的屬性
            System.out.println(Person.this.name);//外部類的屬性
            //Person.this.eat();
        }
    }
}

5.局部內部類的使用:

//返回一個實現了Comparable接口的類的對象
public Comparable getComparable(){

    //創建一個實現了Comparable接口的類:局部內部類
    //方式一:
    //      class MyComparable implements Comparable{
    //
    //          @Override
    //          public int compareTo(Object o) {
    //              return 0;
    //          }
    //          
    //      }
    //      
    //      return new MyComparable();

    //方式二:
    return new Comparable(){

        @Override
        public int compareTo(Object o) {
            return 0;
        }
    };
}

注意點:

在局部內部類的方法中(比如:show如果調用局部內部類所聲明的方法(比如:method)中的局部變量(比如:num)的話,要求此局部變量聲明爲final的。

原因:局部內部類也會生成字節碼文件,在調用所屬類的局部變量時,因爲是兩個類,所以不能修改所屬類的屬性,因此所屬類將屬性設置爲final的爲內部類調用提供一個副本,而內部類不能進行修改。

  • jdk 7及之前版本:要求此局部變量顯式的聲明爲final的
  • jdk 8及之後的版本:可以省略final的聲明

總結:成員內部類和局部內部類,在編譯以後,都會生成字節碼文件。
字節碼文件名格式:
成員內部類:外部類$內部類名.class
局部內部類:外部類$數字 內部類名.class

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