Java類和對象詳解,以及相關知識點

瞭解類和對象前,簡單提及面向對象程序設計。面向對象程序設計就是通過對象來進行程序設計,對象表示一個可以明確標識的實體。例如:一個人、一本書、一個學校或一臺電腦等等。每個對象都有自己獨特的標識、狀態和行爲。

對象的狀態(特徵或屬性,即實例變量),由該對象的數據域來表示。 例如:一個人可以具有名字、年齡、身高、體重、家庭地址等等屬性,這些就是“人這個對象的數據域”。

對象的行爲(對象執行的動作,即功能),由方法來定義。例如:定義getName()來獲取姓名, getHeight()獲取身高,setAddress(String addr)修改地址。

類和對象的關係

類是一種抽象的概念集合,是最基礎的組織單位,作爲對象的模板、合約或藍圖。

類是對象的類型,使用一個通用類可以定義同一類型的對象,類中定義對象的數據域是什麼以及方法是做什麼的。 對象是類的實例,一個類可以擁有多個實例,創建實例的過程叫做實例化。實例也稱爲對象,兩者說法一致。

用幾個案例來加深理解類和對象。

上面的幾個類定義了該類對象的數據域和方法, 但這些類中都沒有main()方法,所以無法運行。注意:擁有main()方法的類稱爲主類,是執行程序的入口。使用類中定義的數據域和方法需要創建該類的實例,然後用實例來調用。

public class TestCreateInstance {
    public static void main(String[] args) {
        //創建Fruit類的實例
        Fruit f = new Fruit("香蕉", "甜味"); 
        System.out.println(f.toString()); //顯示f實例中的水果信息
        
        //創建Book類的實例
        Book b = new Book("Java案例學習", "IT", 25.5, "A0001");
        System.out.println(b.toString()); //顯示b實例中的書籍信息
        
        //創建Person類的實例
        Person p = new Person("張三", 1, 18, "中國北京");
        System.out.println(p.toString());
        
        //result
//        水果名:香蕉, 口味:甜味
//        書名:Java案例學習, 書的種類:IT, 單價:25.5, 書籍編號:A0001
//        名字:張三, 性別:1,年齡:18, 出生地:中國北京
    }
}

 這裏定義了帶有main()方法的TestCreateInstance類,用於測試其他三個類。在main方法中,一共創建了三個對象, 創建對象使用new操作符, new Class(parameter)表示調用該類中相應參數的構造方法。 

例如 new Fruit("香蕉", "甜味") 會調用 Fruit(String name, String state) 這個構造方法來創建對象。

構造方法

構造方法在使用new操作符創建對象時被調用,作用就是用於初始化對象數據域。

 構造方法相比於普通方法比較特殊的地方: 構造方法名和所在類的類名一致;無返回值(即void也沒有);只有創建對象時纔會被調用。  構造方法和普通方法一樣,也可以重載,根據不同的初始參數,來構造對象。

//只初始化名字和性別
    public Person(String name, int sex) {
        this.name = name;
        this.sex = sex;
    }
  
    public Person(String name, int sex, int age, String birthplace) {
        this(name, sex); //使用this()來調用類中的構造器
        this.age = age;
        this.birthplace = birthplace;
    }

除了定義有參構造方法,也可以定義無參構造方法: public Person(){}。若一個類中沒有定義任何構造方法,那麼在類中會隱式存在一個方法體爲空的無參構造方法,也叫默認構造方法。如果顯式的定義構造方法,則默認構造方法失效。

爲什麼要有默認構造方法呢?這就要提到構造方法中的調用流程了,即“構造方法鏈”。  這裏涉及到繼承的相關知識,這裏不給出。

引用變量訪問對象和調用數據域和方法

對象是通過對象類型變量來訪問,該變量包含了對對象的引用。對象類型變量使用操作符(.)來 訪問對象數據和方法。 

創建的對象會在內存中分配空間, 然後通過引用變量來訪問。 包含一個引用地址的變量就是引用變量, 即引用類型。 Java中,除了基本類型外,就是引用類型(對象)。

聲明對象類型變量的兩種形式

Fruit f ; //只聲明,未指向一個引用地址
Fruit f = null; //f指向一個空地址
//兩者基本無區別,null是引用類型的默認值

本質上來看,類是一種自定義類型,是一種引用類型,所以該類類型的變量可以引用該類的一個實例。 

Fruit f = new Fruit("西瓜", "“甜味”)

//表示創建一個Fruit對象,並返回該對象的引用,賦給Fruit類型的f變量。  變量f包含了一個Fruit對象的引用地址。 但通常情況下,直接稱變量f爲Fruit對象。

 

引用類型變量和基本類型變量的區別

每一個變量都代表一個存儲值的內存位置。 聲明變量時,就是告知編譯器該變量可以存儲什麼類型的值。對基本類型變量來說,對應內存所存儲的值就是基本類型值。而對於引用類型變量來說,對應內存所存儲的值是一個引用,指向對象在內存中的位置。

除了基本類型,就是引用類型,引用類型包含對象引用,可以將引用類型看作對象。

        int a = 6;
        int b = a; //將a的實際值賦給b        
        TestReferance tr = new TestReferance();
        TestReferance t2 = tr; //將tr的引用賦給t2 , tr和t2指向同一對象
        t2.a = 10; 
        System.out.println(tr.a); // 10

所以,將引用類型變量賦值給另一個同類型引用變量,兩者會指向同一個對象,而不是獨立的對象。 如果想要指向一個具有同樣內容,但不是同一個對象,可以使用clone()方法。

【技巧】:如果不再需要某個對象時,也就是不引用該對象,可以將引用類型變量賦值null,表示引用爲空。 若創建的對象沒有被任何變量所引用,JVM會自動回收它所佔的空間。

        TestReferance t1 = new TestReferance(); 
        t1 = new TestReferance(); //t1指向一個新的對象。 t1原來指向的對象會被回收
        //創建一個匿名對象,執行完構造方法後,就會被回收。
        new TestReferance();

PS:關於NullPointerException異常,一般都是因爲操作的引用類型變量指向null,所以對引用類型變量操作時,最好先判斷一下是否爲null。

 

靜態與實例的區別

上面的類中定義的都是實例變量和實例方法,這些數據域和方法屬於類的某個特定實例,只有創建該類的實例後,纔可以訪問對象的數據域和方法。

靜態變量和方法屬於類本身,靜態變量被類中的所有對象共享,在靜態方法中不能直接訪問實例變量和調用實例方法。

 

public class Test {
    public int a = 5;
    public static int staB = 10; 
    public Test(int a) { this.a = a; }
    public static void main(String[] args) {
        Test t1 = new Test(6);
        Test t2 = new Test(8);
        System.out.println(t1.a + " " + t2.a); // 6  8, 兩個對象互不相關 
        t1.staB = 15; //t1對象修改靜態變量 staB
        System.out.println(t2.staB); //影響t2對象
    }
}
public class Test {
    public int a = 5; //實例變量
    public static int staB = 10; //靜態變量
    public Test(int a) { this.a = a; }
    
    public static void staMethod() {
        System.out.println(a); //error, 不允許直接訪問實例變量
        insMethod(); //不允許直接調用實例方法
        
        //通過對象來調用
        System.out.println(new Test(5).a);
        new Test(5).insMethod();
        
    }
    //實例方法中,可直接訪問靜態變量和調用靜態方法
    public void insMethod() {
        staMethod();
        System.out.println(staB);
    }
}

 

因爲靜態變量將變量值存儲在一個公共地址,被該類的所有對象共享,當某個對象對其修改時,會影響到其他對象。而實例的實例變量則是存儲在不同的內存位置中,不會相互影響。

訪問靜態變量和靜態方法時,可以不用創建對象,通過“類名.靜態變量/靜態方法”來訪問調用。 雖然能通過對象來訪問靜態變量和方法,但爲了可讀性,方便分辨靜態變量,應該通過類名來調用。

【技巧】:若想要某個數據被所有對象共享,就可以使用static修飾,例如常量,修飾常量使用public static final。

【技巧】:如果某個變量或方法依賴於類的某個實例,則應該定義成實例變量或實例方法。若某個變量或方法不依賴於類的某個實例,則應該定義成靜態變量和靜態方法。 例如Math類,只有靜態方法和靜態變量,禁止創建Math對象。

 

不可變對象和類

 通過定不可變類來產生不可變對象,不可變對象的內容不能被改變。就像文件中的“只讀”概念。

通常情況下,創建一個對象後,該對象的內容可以允許之後修改。但有時候我們需要一個一旦創建,其內容就不能改變的對象。

定義不可變類的要素:

類中所有數據域都是私有的,並且沒有提供任何一個數據域的setXxx()方法。若數據域是可變的引用類型,不提供返回該引用類型變量的getXxx()方法。

 

public class Test {
    private int a = 5;
    private String str; //String是不可變對象,它的方法都是返回一個新的String對象
    private Date date;
    public Test(int a, String str, Date date) {
        super();
        this.a = a;
        this.str = str;
        this.date = date;
    }
    public static void main(String[] args) {
        Test t = new Test(5, "ABC", new Date());
        System.out.println(t.toString());
        //獲取引用類型的數據域,其中Date是可變的
        String s = t.getStr();
        Date d = t.getDate();
        s.toLowerCase();
        //可變數據域
        d.setDate(541313);
        System.out.println(t.toString());
    }
    public int getA() {
        return a;
    }

    public String getStr() {
        return str;
    }

 

從以上案例看出,如果數據域是一個可變的引用類型,那麼不要返回該數據域,不然對返回的引用變量進行操作,會導致對象內容改變。

 

this關鍵字的使用

關鍵字this表示當前對象,引用對象自身。 可以用於訪問實例的數據域, 尤其是實例變量和局部變量同名時,進行分辨。除此之外,可以在構造方法內部調用同一個類的其他構造方法。

以構造方法爲例

 

    public int a;
    public String str;
    //不使用this來初始化構造方法
    public TestThis(int a, String str) {
        //隱式存在於每個構造方法的第一行
        super(); 
        a = a;   //指向同名局部變量形參a
        str = str; //指向同名局部變量形參str
    }

 

形參名和全局變量同名,但形參是局部變量,所以在方法中優先使用局部變量,這裏的賦值是賦值給形參了。解決這個問題,可以修改形參名,但形參名和要初始化的變量名不相等容易引起歧義。

所以使用this來引用實例變量,一舉兩得。

 

    public int a;
    public int b;
    public int c;
    //構造方法1
    public TestThis(int a, int b) {
        this.a = a;  
        this.b = b; 
    }
    
    //構造方法2
    public TestThis(int a, int b, int c) {
        this(a, b); //調用構造方法1來簡化初始化操作
        this.c = c;
    }

 

 

引用參數傳遞給方法

方法是一種功能集合,表明可以做什麼,封裝了實現功能的代碼,要實現某個功能只需要調用相關方法即可,無需關注功能實現細節。大部分方法都需要傳遞參數來實現相關功能,但是傳遞基本類型和傳遞引用類型有什麼區別呢?

給方法傳遞一個對象參數,實際上是將對象的引用傳遞給方法。

 

public class TestThis {
    public static void main(String[] args) {
        int[] arr = {1, 2};
        swap(arr[0], arr[1]);
        System.out.println(Arrays.toString(arr)); //[1, 2]
        swap(arr);
        System.out.println(Arrays.toString(arr)); //[2, 1]
        
    }
    public static void swap(int a, int b) {
        int temp = a;
        a = b;
        b = temp;
    }
    public static void swap(int[] a) {
        int temp = a[0];
        a[0] = a[1];
        a[1] = temp;
    }
}

 

第一個swap()方法,接收基本類型參數,所以通過訪問下標獲取數組元素傳遞給形參,相當於賦一個實際值給形參,所以對形參交換,不會影響實際參數。

第二個swap()接收一個int類型數組, 數組也是一個對象, 所以此時形參a包含一個該數組的引用,在方法內部對a數組操作,會影響實際參數 arr。 

 

上面方法中使用到了方法重載,方法重載就是使用同樣的名字,但根據方法簽名來定義多個方法。

方法重載只與方法簽名有關,和修飾符以及返回類型無關。被重載的方法必須有不同的參數列表或者參數類型不同。

參數列表不同必然重載,若參數列表相同,但類型相近時,會引發匹配歧義,因爲類型不明確。

     //因爲double類型可以匹配int類型,所以會導致編譯器匹配歧義
     public static void sum(int a, double b) { }
     public static void sum(double a, int b) { }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章