1. 程式人生 > >6、類與物件

6、類與物件

面向物件簡介

面向過程:指的是針對某一個問題單獨提出解決方案和程式碼開發。
面向物件:以元件化的形式進行程式碼設計,優點是程式碼可重用。
面嚮物件語言的特徵:
(1)封裝性:內部操作對外部不可見,保護內部結構安全性。
(2)繼承性:在已有的程式結構上擴充新的功能。
(3)多型性:相同的操作或函式、過程可作用於多種型別的物件上並獲得不同的結果
面向物件開發步驟:OOA(面向物件分析),OOD(面向物件設計),OOP(面向物件程式設計)。

類與物件

1 類與物件是面向物件中最基礎的組成單元。類是共性的集合,物件是某一個性的產物。
2 類用於描述物件的結構, 例如每個人的名字、年齡等一系列特徵。類除了包含特徵(屬性)

外,還包括許多行為(方法)。根據這個類產生的物件都具有相同的行為。
3 物件所擁有的行為由類決定,無法執行超出類定義範疇的操作。
4 綜上所述,類是物件的模版,但類無法直接使用,必須通過例項物件來使用。物件是由類產生的。

類與物件的定義及使用

1.定義類使用class class_name {}語句。類的組成:
(1)field(屬性,成員,變數):一堆變數的集合;
(2)method(方法,行為):由物件呼叫。
範例:定義類

package com.java.entity;
public class Book { // 類名首字母大寫
   // 定義屬性
   public String title;
public double price; // 定義方法 public void getInfo(){ System.out.println("書名:" + title + ",價格:" + price); } }

2.要使用類,必須要有物件,物件定義的語法有如下兩種:
(1)宣告並例項化物件:class_name object_name = new class_name()
(2)分步完成:
第一步-宣告物件:class_name object_name = null;
第二步-例項化物件:object_name =new class_name()

;
引用資料型別與基本資料型別最大區別是需要記憶體的開闢及使用,所以關鍵字new的主要功能是開闢記憶體空間。
3.當一個物件例項化後,利用如下方式操作類:
(1)object_name.field:操作類中的屬性;
(2)object_name.method():呼叫類中的方法。
範例:使用類

public class Demo {
    public static void main(String[] args) {
        Book book = new Book(); // 宣告並例項化物件
        book.title = "Java開發"; // 定義屬性
        book.price = 66.6;
        book.getInfo(); // 呼叫方法
    }
}

4.堆記憶體和棧記憶體的概念:

堆記憶體 棧記憶體
儲存物件的屬性內容,使用關鍵字new開闢 棧記憶體:儲存堆記憶體的地址。可以理解為棧記憶體儲存物件的名字

在這裡插入圖片描述
範例:分步使用例項化物件

public class Demo {
    public static void main(String[] args) {
        Book book = null; // 宣告物件
        book = new Book(); // 例項化物件
        book.title = "Java開發";
        book.price = 66.6;
        book.getInfo();
    }
}

記憶體分析:使用關鍵字new開闢了新的的堆記憶體,堆記憶體中儲存類定義的屬性,此時所有的屬性值都為預設值
在這裡插入圖片描述
使用未例項化的物件,程式執行時會出現NullPointerException(空指向異常)

物件引用分析

1.在引用分析中,每次使用new關鍵字便會開闢新的堆記憶體。假設聲明瞭兩個物件,且都用new分別進行了例項化,那麼兩個物件佔據的是不同的堆記憶體,因此不會互相影響。
範例:宣告兩個物件

public class Demo {
   public static void main(String[] args) {
       Book bkA = new Book();
       Book bkB = new Book();
       bkA.title = "Java開發";
       bkA.price = 66.6;
       bkA.getInfo();
       bkB.title = "C++開發";
       bkB.price = 22.6;
       bkB.getInfo();
   }
}

在這裡插入圖片描述
2. 範例:物件引用傳遞

public class Demo {
    public static void main(String[] args) {
        Book bkA = new Book();
        Book bkB = null;
        bkA.title = "Java開發";
        bkA.price = 66.6;
        bkA.getInfo(); // 66.6
        bkB = bkA; // 引用傳遞
        bkB.price = 90.5;
        bkA.getInfo(); // 90.5
        bkB.getInfo(); // 90.5
    }
}

在這裡插入圖片描述
由於兩個物件指向同一塊堆記憶體,所以任意一個物件修改了堆記憶體的屬性值後,都會影響到其他物件的值。在引用中,一塊堆記憶體可以被多個棧記憶體指向,但一個棧記憶體只能儲存一個堆記憶體的地址。
3. 範例:引用傳遞

public class Demo {
    public static void main(String[] args) {
        Book bkA = new Book();
        Book bkB = new Book();
        bkA.title = "Java開發";
        bkA.price = 66.6;
        bkB.title = "C++開發";
        bkB.price = 90.5;
        bkB = bkA; // 引用傳遞
        bkB.price = 100;
        bkA.getInfo(); // 100
        bkB.getInfo(); // 100
    }
}

在這裡插入圖片描述
通過記憶體分析可知,在引用時,一塊沒有棧記憶體指向的堆記憶體將成為垃圾,最後被垃圾收集器(GC)回收,回收後,釋放其所佔空間。

封裝性

public class Demo {
    public static void main(String[] args) {
        Book bkA = new Book();
        bkA.title = "Java開發";
        bkA.price = -66.6;
        bkA.getInfo();
    }
}

上述程式碼沒有語法錯誤,但存在業務邏輯錯誤,因為書的價格不能為負數。造成該問題是因為物件可以在類的外部直接訪問屬性。
1.因此應將Book類的屬性設為對外不可見(只允許本類訪問),可以使用private關鍵字定義屬性。

public class Book {
    private String title;
    private double price;
    public void getInfo(){
        System.out.println("書名:" + title + ",價格:" + price);
    }
}

public class Demo {
    public static void main(String[] args) {
        Book bkA = new Book();
        bkA.title = "Java開發"; // 報錯,無法訪問私有屬性
    }
}

此時外部物件無法直接呼叫類中的屬性,但程式要正常執行,必須讓物件可以操作類中的屬性。因此在開發中,對於屬性有該要求:類中的屬性要使用private宣告,如果屬性需被外部使用,按照要求定義對應的setter()/getter().
2. 以Book類中的title屬性為例,定義setter()/getter():

setter() getter()
作用 設定屬性值 取得屬性值
語法 public void setTitle(String t) public void getTitle()
是否含參 有參 無參
package com.java.entity;
public class Book {
    private String title;
    private double price;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public void getInfo(){
        System.out.println("書名:" + title + ",價格:" + price);
    }
}

如果要有價格不能為負數的功能,可以在setter()中新增:

public void setPrice(double price) {
    if (price > 0.0){
        this.price = price; 
    }
}

實際開發中,對於資料的驗證,應由其他輔助程式碼完成,setter()只是簡單地設定資料,getter()只用於返回資料。

構造方法與匿名物件

定義物件的語法:類名稱 物件名稱 = new 類名稱();
①類名稱:定義物件的型別;
②物件名稱:識別符號,要使用物件,需要有一個物件名;
③new:用於開闢堆記憶體空間,例項化物件;
④類名稱():一個方法名和類名稱一樣的方法,這就是構造方法。
通過上述分析,我們發現了構造方法的存在,但我們並沒有定義構造方法。之所以能使用這個構造方法,是因為Java預設在沒有自定義構造方法的情況下,程式編譯時自動在類中新增一個無參無返回值的構造方法。

1.構造方法的定義原則:方法名稱與類名稱相同,沒有返回值,也不能使用void關鍵字

class Book {
    public Book() { // 系統自動生成的構造方法
    }
}

2.構造方法在物件使用new例項化時呼叫。
範例:證明構造方法被呼叫

public class Book {
   public Book() {
       System.out.println("構造方法被呼叫");
   }
}

public class Demo {
   public static void main(String[] args) {
       Book book = null ; // 宣告物件
       book = new Book(); // 例項化物件時呼叫構造方法
       //結果:構造方法被呼叫
   }
}

構造方法與普通方法的最大區別:構造方法只在例項化物件時呼叫一次。普通方法在物件例項化後可以多次呼叫。
3.範例:自定義構造方法

class Book {
    private String title;
    private double price;
    // 自定義構造方法後,系統不再自動生成無參無返回值的構造方法
    public Book(String t, double p) {
        title = t;
        price = p;
    }

    public void getInfo() {
        System.out.println("書名:" + title + ",價格:" + price);
    }
}

public class Demo {
    public static void main(String[] args) {
        Book book = new Book("Java開發", 66.6);
        book.getInfo();
    }
}

由上述程式碼可知構造方法的作用:在類物件例項化時設定屬性的初始值,即構造方法用於屬性初始化
4.構造方法也屬於方法,因此可以進行過載。
範例:構造方法過載

class Book {
    public Book() {
        System.out.println("系統自動生成的構造方法");
    }
    // 進行方法過載的構造方法
    public Book(String t, double p) {
        System.out.println("方法過載後的構造方法");
    }
}

public class Demo {
    public static void main(String[] args) {
        Book bookA = new Book(); // 系統自動生成的構造方法
        Book bookB = new Book("Java開發", 66.6); // 方法過載後的構造方法
    }
}

過載方法時要求:按照引數個數,對方法進行升序或者降序排列。
5.在定義類時可為屬性設定預設值。但該預設值只有在物件例項化後才進行設定。而物件例項化是物件構造的最後一步。物件例項化過程的步驟:類的載入,記憶體的分配,預設值的設定,構造方法

class Book {
    private String title = "Java開發"; // 設定預設值
    private double price;

    public Book() {
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        if (price > 0.0){
            this.price = price;
        }
    }

    public void getInfo() {
        System.out.println("書名:" + title + ",價格:" + price);
    }
}

public class Demo {
    public static void main(String[] args) {
        Book bkA = new Book();
        bkA.getInfo(); // 書名:Java開發,價格:0.0
    }
}

本程式中,只有在物件例項化後,才會將Java開發設定給title屬性。在沒有例項化前,title的值仍為其資料型別的預設值,即null真實的物件資訊都儲存在堆記憶體中。
6.匿名物件:沒有棧記憶體指向的物件,即沒有識別符號的物件

public class Demo {
    public static void main(String[] args) {
        new Book("Java開發",6.6).getInfo();
    }
}

由於匿名物件沒有識別符號,也沒有其他物件對其引用,因此只能使用一次,之後該物件空間變為垃圾,等待回收。
何時使用匿名物件:但一個物件僅需要被使用一次時就使用匿名物件。當一個物件要被反覆呼叫時,就使用非匿名物件。

簡單Java類實踐

題目要求:定義一個僱員類,類中有僱員編號、姓名、職位、基本工資、佣金等屬性。
提示:這種類被稱為簡單java類,因為這種類不包含過於複雜的程式邏輯。

對於簡單Java類而言,它的要求如下:
·類名必須有意義;
·類中所有屬性必須private封裝,封裝後的屬性必須提供setter和getter方法;
·類中可以有多個構造方法,但必須保留無參構造方法;
·類中不允許出現輸出語句,資訊輸出必須交給呼叫處。
·類中需要提供有一個取得物件完整資訊的方法,暫定為getInfo(),返回值型別為String型。

第一步:定義類

public class Emp {
    private int eId; // 編號
    private String eName; // 姓名
    private String job; // 職位
    private double sal; // 工資
    private double comm; // 佣金
    // 定義構造方法
    public Emp() {
    }
    
    public Emp(int eId, String eName, String job, double sal, double comm) {
        this.eId = eId;
        this.eName = eName;
        this.job = job;
        this.sal = sal;
        this.comm = comm;
    }
    // 定義setter和getter方法
    public int geteId() {
        return eId;
    }

    public void seteId(int eId) {
        this.eId = eId;
    }

    public String geteName() {
        return eName;
    }

    public void seteName(String eName) {
        this.eName = eName;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    public double getComm() {
        return comm;
    }

    public void setComm(double comm) {
        this.comm = comm;
    }
    // 定義普通方法
    public String getInfo() {
        return "編  號" + this.eId + "\n" +
                "姓 名" + this.eName + "\n" +
                "職 位" + this.job + "\n" +
                "工 資" + this.sal + "\n" +
                "傭 金" + this.comm;
    }
}

第二步:測試

public class TEmp {
    public static void main(String[] args) {
        Emp e = new Emp(1, "Tom", "保安", 800.0, 10.0);
        System.out.println(e.getInfo()); // 獲取全部資訊
        System.out.println(e.geteId()); // 通過getter()獲取單一資訊
    }
}

類中的setter()/getter()必須寫。setter()除了有設定屬性內容功能外,還有修改屬性值的功能。