1. 程式人生 > >Java面向對象進階篇(包裝類,不可變類)

Java面向對象進階篇(包裝類,不可變類)

public 不存在 內存空間 test 都是 style system 覆蓋 位置

一. Java 8的包裝類

Java中的8種基本數據類型不支持面向對象的變成機制,也不具備對象的特性:沒有成員變量,方法可以調用。為此,Java為這8 種基本數據類型分別提供了對應的

包裝類(Byte,Short,Integer,Long,Double,Float,Charater,Boolean)。

從jdk 1.5開始,Java提供了自動裝箱和自動拆箱的功能。自動裝箱就是可以把一個基本類型變量賦給對應的包裝類變量。自動拆箱與之相反。

包裝類提供了基本類型變量和字符串之間的轉換的方法。有兩種方式把字符串類型值轉換成基本類型的值

a)利用包裝類提供的parseXxx(String s)靜態方法(除了Character之外的所有包裝類都提供了該方法。)

b)利用包裝類提供的Xxx(String s)構造器

String類提供了多個重載valueOf()方法,用於將基本類型變量轉換成字符串。

兩個128自動裝箱後,比較它們的大小並不相等,因此Java 7增強了包裝類的功能,為所有的包裝類提供了一個靜態的compare(xxx val1,xxx val2)方法,來比較兩個基本類型值得大小。

Java 8再次增強包裝類的功能,可以支持無符號運算。

二.處理對象

2.1 打印對象和toString方法

System.out的println()方法和print()方法只能在控制臺輸出字符串。

toString()方法是Object類裏的一個實例方法,它是一個自我描述方法,所有的Java類都是Object類的子類,因此所有的Java類都有toString()方法。

當程序員直接打印一個對象時,系統會輸出該對象的“自我描述”信息,以告訴外界該對象的所有狀態信息。當我們使用println()和print()方法打印一個對象時,會自動調用Object類的toString()方 法。因此,下面兩行代碼的效果完全一樣:

System.out.println(person);
System.out.println(person.toString());

Object類提供的toString()方法總是返回該對象實現類的“類名+@+hashCode”值,這個值不能真正實現“自我描述”功能,因此如果用戶需要實現自我描述功能,就必須重寫Object類的toString()方法。

package com.company;

class Apple{
    private String color;
    private double weight;
    public Apple(String color,double weight)
    {
        this.color = color;
        this.weight = weight;

    }

    @Override
    public String toString() {
        return "一個蘋果,它的顏色是"+color+",重量是"+weight;
    }
}


public class ToStringTest {
    public static void main(String[] args){
        Apple apple = new Apple("紅色",5.68);
        System.out.println(apple);
    }

}

技術分享圖片

2.2 ==和equals方法

== 和 equals 都可以用來測試兩個變量是否相等。== 用來判斷基本數據類型時,當且僅當變量的數據類型和變量的值都一致時才返回true。== 用來判斷引用類型變量時,只有當它們指向同一個對象時才返回true。不可用於比較類型上沒有父子關系的兩個對象。

可以使用String對象的equals方法判斷兩個字符串變量的引用字符串的字符序列是否相等,相等就返回true。

下面程序示範了JVM使用常量池管理字符串直接量的情形

package com.company;

public class StringCompareTest {
    public static void main(String[] args){
        //s1直接引用常量池中的“瘋狂Java”
        String s1 = "瘋狂Java";
        String s2 = "瘋狂";
        String s3 = "Java";
        String s4 = "瘋狂"+"Java";//s4後面的字符串值在編譯時確定下來,直接引用常量池中的“瘋狂Java”
        String s5 = s2+s3;//s5後面的字符串值在編譯時確定下來,不能直接引用常量池中的“瘋狂Java”
        //JVM會使用常量池來管理“瘋狂Java”,在調用構造器創建一個新的String對象,一共產生了兩個字符串對象
        String s6 = new String("瘋狂Java");//s6調用構造器創建一個新的String對象,s6引用堆內存中的String對象

        System.out.println(s1 == s4);//輸出true
        System.out.println(s1 == s5);//輸出false
        System.out.println(s1 == s6);//輸出false
        System.out.println(s4 == s6);//輸出false
    }
}

equals方法是Object類提供的一個實例方法,因此所有引用變量都可調用該方法來判斷是否等於引用變量,但使用這個

方法與==運算符沒有區別,同樣要求兩個引用變量指向同一個對象才返回true。如果希望采用自定義的相等標準,則可

采用重寫equals方法實現。

package com.company;

class Person
{
    private String name;
    private String id;

    public Person(String name, String id) {
        this.name = name;
        this.id = id;
    }

    @Override
    public boolean equals(Object obj)
    {
       if(this == obj)
           return true;
       if(null != obj && obj.getClass() == Person.class)
       {
           Person person = (Person)obj;
           if(person.id == this.id)
           {
               return true;
           }
       }
        return false;
    }
}
public class OverrideEqualsRight {
    public static void main(String[] args)
    {
      Person p1 = new Person("孫悟空","234");
      Person p2 = new Person("孫行者","234");
      Person p3 = new Person("孫悟飯","567");
      System.out.println("p1和p2是否相等?"+p1.equals(p2));
      System.out.println("p1和p3是否相等?"+p1.equals(p3));

    }
}

技術分享圖片

三. 類成員

3.1 理解類成員

static關鍵字修飾的成員就是類成員。類成員可以有4種,包括類變量,類方法,靜態初始代碼塊,內部類等。static不等修飾構造器。類成員只屬於類不屬於實例。

類變量屬於整個類,當系統第一次準備使用該類時,系統會為該類變量分配內存空間,類變量開始生效,直到該類被卸載,該類的類變量所占有的內存才會被系統的垃圾回收機制回收。當類初始化完成後,類變量也初始化完成。

類變量可以通過類來訪問,也可以通過類的對象來訪問。當通過對象來訪問類變量時,系統會在底層轉換為通過該類訪問變量。

註意類成員不能訪問實例成員。因為類成員是屬於類的,作用域比實例成員的作用域大。完全可能出現類成員初始化完成,但實例成員還不曾初始化的情況。

3.2 單例(Singleton)類

在一些特殊場景下,不允許自由創建該類的對象,而只允許為該類創建一個對象。為了避免其他類自由創建該類的實例,應該把類的構造器使用private修飾。根據良好的封裝原則,一旦把類的構造器隱藏起來,則需要提供一個public方法作為該類的訪問點,用於創建該類的對象,且該方法必須使用static修飾(因為調用該方法之前還不存在對象,因此調用該方法的不可能是對象,只能是類)。除此之外,該類還必須緩存已經創建的對象,否則該類無法知道是否曾經創建過對象。為此該類需要使用一個成員變量來保存曾經創建的對象,因為該成員變量需要上面的靜態方法訪問,故該成員變量必須使用static修飾。

如果一個類始終只能創建一個實例,則這個類被稱為單例類。

基於上面介紹,下面程序創建了一個實例類。

package com.company;
class Singleton
{
    private static Singleton instance;
    private Singleton(){}
    public static Singleton getSingleton()
    {
        if(null == instance)
        {
            instance = new Singleton();
        }

       return instance; 
    }

}

public class SingletonTest {
    public static void main(String[] args)
    {
        Singleton s1 = Singleton.getSingleton();
        Singleton s2 = Singleton.getSingleton();
        System.out.println(s1 == s2);
    }

}

輸出:true

四. final修飾符

final關鍵字可用於修飾類、變量和方法。用於表示它修飾的類,方法和變量不可改變。

4.1 final成員變量

final修飾的成員變量必須由程序員顯式地指定初始值,歸納如下:

類變量:必須在聲明類變量時或靜態初始塊中指定初始值。而且只能在兩個地方的其中之一指定。

實例變量:必須在聲明實例變量時,非靜態初始化塊或構造器中指定初始值,而且只能在三個地方的其中之一指定。

如果打算在構造器、初始化塊中對final成員變量進行初始化,則不要在初始化之前就訪問成員變量的值。例如下面程序

引發錯誤。

public class FinalErrorTest {
    final int age;
    {
        System.out.println(age);//訪問引發錯誤
        age = 6;
        System.out.println(age);
    }
}

4.2 final局部變量

局部變量必須由程序員顯示指定默認值。因此使用final修飾局部變量時,既可以在定義時指定默認值,也可以不指定

默認值。例如:

public void setDemo()
{
final int a; a=3;
System.out.println(a); }

final修飾基本數據類型變量時,保證變量的基本數值不會改變,當final修飾引用類型變量時,final只保證變量引用的

地址不會改變,即一直引用一個對象。

4.3 可執行宏替換的final變量

對於一個final變量來說,不論是類變量,實例變量,還是局部變量,只要滿足三個條件,這個final變量不再是一個變量,而是相當於一個直接量。

a)使用final修飾符修飾

b)在定義該final變量時指定了初始值

c)該初始值在編譯時確定了下來

4.4 final方法

final修飾的方法不可以被重寫

4.5 final類

final修飾的類不可以有子類,不可被繼承,例如java.lang.math就是一個final類

4.6 不可變類

不可變類的意思是創建該類的實例後,該實例的實例變量是不可改變的。Java提供的8個包裝類和java.lang.String類

都是不可變類,當創建它們的實例後,其實例的實例變量不可改變

創建自定義的不可變類,需滿足以下規則:

1.使用private和final修飾符來修飾該類的成員變量

2.提供帶參數的構造器,用於根據傳入參數初始化類裏的成員變量

3.僅為類的成員變量提供getter方法,不提供setter方法

4.如果有必要,重寫Object類的equals()方法和hashCode()方法。equals()方法根據關鍵成員變量來作為兩個對象是否相 等的標準,除此之外,還應該保證兩個用equals()方法判斷為相等的對象的hashCode()也相等。

下面定義一個不可變的Address類。

package com.company;

public class Address {
    private final String detail;
    private final String postCode;
    
    public Address()
    {
        this.postCode = "";
        this.detail = "";
    }
    
    public Address(String detail,String postCode)
    {
        this.detail = detail;
        this.postCode = postCode;
    }

    public String getDetail() 
    {
        return this.detail;
    }

    public String getPostCode() 
    {
        return this.postCode;
    }

    @Override
    public int hashCode() {
        return detail.hashCode() + postCode.hashCode()*31;
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj)
            return true;
        if(null != obj && obj.getClass() == Address.class)
        {
            Address ad = (Address)obj;
            if(this.getDetail().equals(ad.getDetail()) 
                    && this.getPostCode().equals(ad.getPostCode()))
            return true;
        }
        return false;
    }
    
    
    
}

如果需要設計一個不可變類,尤其要註意其引用類型的成員變量。如果引用類型的成員變量的類是可變的,就必須采取必要的措施來保護該成員變量所引用的對象不會被修改,這樣才能創建真正的不可變類。

下面程序試圖創建一個不可變的Person類,但因為Person類的一個成員變量是可變類,所以導致Person類也是個可變類。

package com.company2;

class Name
        {
          private String firstName;
          private String lastName;

            public Name(String firstName, String lastName) {
                this.firstName = firstName;
                this.lastName = lastName;
            }
            public Name(){}

            public String getFirstName() {
                return firstName;
            }

            public String getLastName() {
                return lastName;
            }

            public void setFirstName(String firstName) {
                this.firstName = firstName;
            }

            public void setLastName(String lastName) {
                this.lastName = lastName;
            }


        }
public class Person {

    private final Name name;

    public Person(Name name)
    {
        this.name = name;
    }

    public Name getName() {
        return name;
    }



    public static void main(String[] args)
    {
        Name name = new Name("悟空","孫");
        Person person = new Person(name);

        System.out.println(person.getName().getFirstName());

        name.setFirstName("八戒");

        System.out.println(person.getName().getFirstName());

    }
}

為了保持Person對象的不可變性,必須保護好Person對象的成員變量:name,讓程序無法訪問到Person對象的name

成員變量,也無法利用name成員變量的可變性來改變Person對象了。

因此將Person類改為如下:

public class Person {

    private final Name name;
    public Person(Name name)
    {
        this.name = new Name(name.getFirstName(),name.getLastName());
    }

    public Name getName() {
        return name;
    }

技術分享圖片

4.7 緩存實例的不可變類

不可變類的實例狀態不可改變,可以很方便的被多個對象所共享。如果程序經常需要使用相同的不可變類的實例,則

應該考慮緩存這種不可變類的實例,畢竟創建重復的對象沒有太大的意義,而且加大系統開銷。如果可能,應該將不可變類的實例進行緩存。

package com.company2;

class CacheImmutale
{
    private static int MAX_SIZE = 10;
    //使用數組來緩存已有的實例
    private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE];
    //記錄緩存實例在緩存中的位置,cache[pos-1]是最新的實例
    private static int pos = 0;
    private final String name;

    private CacheImmutale(String name)
    {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    /**
     * 緩存的操作方法
     * @param name
     * @return 緩存的實例
     */
    public static CacheImmutale valueOf(String name){
        //遍歷已緩存的對象
        for(int i = 0 ; i < MAX_SIZE ; i++)
        {
            //如果已有相同實例,則直接返回該緩存的實例
            if(null != cache[i] && cache[i].getName().equals(name))
            {
                return cache[i];
            }

        }
        //如果緩存池已滿,
        if(pos == MAX_SIZE)
        {
            //把緩存的第一個對象覆蓋,即把剛剛生成的對象放在緩存池的最開始位置
            cache[0] = new CacheImmutale(name);
            pos = 1;
        }
        else
        {
            //把新的對象緩存起來,pos加1
            cache[pos++] = new CacheImmutale(name);
        }
        return cache[pos-1];
    }

    @Override
    public boolean equals(Object obj) {
      if(this == obj)
        return true;
      if(null != obj && obj.getClass() == CacheImmutale.class)
      {
          CacheImmutale cache = (CacheImmutale) obj;
          return cache.getName().equals(name);
      }
        return false;
    }
    public int hashCode()
    {
        return name.hashCode();
    }
}
public class CachelmmutaleTest {
    public static void main(String[] args)
    {
        CacheImmutale c1 = CacheImmutale.valueOf("Hello");
        CacheImmutale c2 = CacheImmutale.valueOf("Hello");

        System.out.println(c1 == c2);
    }
}

Java面向對象進階篇(包裝類,不可變類)