Java面向對象進階篇(包裝類,不可變類)
一. 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面向對象進階篇(包裝類,不可變類)