final修飾符
final關鍵字可用于修飾類、變量和方法,用于表示它修飾的類、方法和變量不可改變。
final修飾變量時,表示該變量一旦獲得初始值就不可改變。final既可以修飾成員變量(包括類變量和執行個體變量),也可以修飾局部變量、形參。
final修飾變量
final成員變量
Java文法規定:final修飾的成員變量必須由程式員顯示指定初始值。
成員變量是随類初始化或對象初始化而初始化的。當類初始化時,系統會為該類的類變量配置設定記憶體,并配置設定預設值;當建立對象時,系統會為該對象的執行個體變量配置設定記憶體,并配置設定預設值。也就是說,當執行靜态初始化塊時可以對類變量賦初始值;當執行普通初始化塊、構造器時可對執行個體變量賦初始值。是以,成員變量的初始值可以在定義該變量時指定預設值,也可以在初始化塊、構造器中指定初始值。
對于final修飾的成員變量而言,一旦有了初始值,就不能被重新指派,如果既沒有在定義成員變量時指定初始值,也沒有在初始化塊、構造器中為成員變量指定初始值,那麼這些成員變量的值将一直是系統預設配置設定的0、’\u0000’、false或null,這些成員變量也就失去存在的意義。
歸納:
- 類變量:必須在靜态初始化塊中指定初始值或聲明該類變量時指定初始值,而且隻能在兩個地方的其中之一指定。
- 執行個體變量:必須在非靜态初始化塊、聲明該執行個體變量或構造器中指定初始值,而且隻能在三個地方的其中之一指定。
final局部變量
系統不會對局部變量進行初始化,局部變量必須由程式員顯示初始化。是以使用final修飾局部變量時,既可以在定義時指定預設值,也可以不指定預設值。如果final修飾的局部變量在定義時沒有指定預設值,則可以在後面代碼中對該final變量賦初始值,隻能一次。也可以在定義時直接指定預設值。
使用final修飾形參時,形參将由系統在調用該方法時根據傳入的參數來完成初始化,是以使用final修飾的形參不能被指派。
final修飾基本變量和引用類型變量的差別
當使用final修飾基本類型變量時,不能對基本類型變量重新指派,是以基本類型變量不能被改變。但對于引用類型變量而言,它儲存的僅僅是引用,final隻保證這個引用類型變量所引用的位址不會改變,即一直引用同一個對象,但這個對象可以改變。
可執行宏替換的final變量
對于一個final變量來說,不管它是類變量、執行個體變量,還是局部變量,當定義final變量時就指定初始值,且該初始值在編譯時就可确定下來,那麼這個變量本質上就是一個宏變量,編譯器會把程式中所有用到該變量的地方直接替換成該變量的值。除了賦直接量外,如果被賦表達式隻是基本的算術表達式或字元串連接配接運算,沒有通路普通變量,調用方法,Java編譯器同樣會将這種final變量當成宏變量處理。
// 4個final宏變量
final int a = 5 + 2;
final double b = 1.2/3;
final String str = "瘋狂" + "Java";
final String book = "瘋狂Java講義:" + 99.0;
// book2變量的值因為調用了方法,是以無法在編譯時确定
final String book2 = "瘋狂Java講義:" + String.valueOf(99.0);
System.out.println(book == "瘋狂Java講義:99.0"); // true
System.out.println(book2 == "瘋狂Java講義:99.0"); // false
final修飾方法
final方法不可被重寫,比如Object類就提供的getClass方法(Java不希望任何類重寫這個方法,使用final把這個方法密封)。final方法依舊可以重載。
對于一個private方法,子類無法通路該方法,是以子類無法重寫該方法。如果子類定義一個與父類private方法相同方法名、相同形參清單、相同傳回值類型的方法,也不是重寫,隻是在子類中定義了一個新方法而已。是以即使使用final修飾private通路權限的方法,子類依舊可以定義相同方法名、相同形參清單、相同傳回值類型的方法。
final修飾類
final類不可有子類,可以保證某個類不可被繼承。
不可變類
不可變類是指建立該類實體後,該執行個體的執行個體變量不可改變。比如包裝類和java.lang.String類,當建立執行個體後,其執行個體變量不可改變。
Double d = new Double(6.5);
String str = new String("Hello");
建立不可變類可以遵循下面規則:
- 使用private和final修飾符來修飾該類的成員
- 提供帶參數構造器,用于根據傳入參數來初始化類裡的成員變量
- 僅為該類成員變量提供getter方法,不提供setter,因為普通方法無法修改final修飾的成員變量
-
重寫Object類的hashCode和equals方法。equals方法根據關鍵成員變量來作為兩個對象是否相等的标準。除此之外,還需保證兩個用equals方法判斷為相等的對象的hashCode也相等。
示例:
public class Address
{
private final String detail;
private final String postCode;
public Address(){
this.detail = "";
this.postCode = "";
}
public Address(String detail, String postCode){
this.detail = detail;
this.postCode = postCode;
}
public String getDetail(){ return this.detail; }
public String getPostCode(){ return this.postCode; }
// 重寫equals方法
public boolean equals(Object obj)
{
if(this == obj) return true;
if(obj != null && obj.getClass() == Address.class)
{
Address ad = (Address)obj;
if(this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode()))
return true;
}
return false;
}
public int hashCoe()
{
return detail.hashCode() + postCode.hashCode()*31;
}
}
如果成員變量是引用類型,想讓這個類變為不可變類,但是該成員變量引用的類型是可變的,如果盡量少的改動實作該需求。
class Name
{
private String firstName;
private String lastName;
public Name() {}
public Name(String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
//這裡有getter和setter方法
}
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 n = new Name("悟空", "孫");
Person p = new Person(n);
n.setFirstName("八戒"); // 由于留有對成員變量的引用,就可以修改可變類
}
}
// 修改後
public class Person
{
private final Name name;
public Person(Name name){
// 設定name執行個體變量為臨時建立Name對象,不将Name變量的引用暴露在外
this.name = new Name(name.getFirstName(), name.getLastName());
}
public Name getName(){
// 傳回一個匿名對象,不将Name變量的引用暴露在外
return new Name(name.getFirstName(), name.getLastName());
}
}
不可變類應用
class CacheImmutale{
private static int MAX_SIZE = 10;
// 對象共享池,由于使用動态初始化,且CacheImmutale是類,是以數組元素初始化為null不涉及到構造器
private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE];
private static int pos = 0;
private final String name;
// 隐藏構造器
private CacheImmutable(String name) { this.name = name; }
public String getName() ( return this.name; )
// 類方法,操作對象池方法,沒有就建立對象并共享,有就直接共享
public static CacheImmutable valueOf(String name)
{
for(int i = 0; i < MAX_SIZE; i++){
if(cache[i] != null && cache[i].getName().equals(name))
return cache[i];
}
// 沒有就建立
if(pos == MAX_SZIE){ // 拐角處理
cache[0] = new CacheImmutable(name);
pos = 1;
}else{
cache[pos++] = new CacheImmutable(name);
}
return cache[pos-1];
}
public boolean equals(Object obj){
if(this == obj) return true;
if(obj != null && obj.getClass() == CacheImmutable.class)
{
CacheImmutable ci = (CacheImmutable)obj;
return name.equals(ci.getName());
}
return false;
}
public boolean hashCode(){ return name.hashCode(); }
}
public class CacheImmutaleTest{
public static void main(String[] args){
CacheImmutable c1 = CacheImmutable.valueOf("hello");
CacheImmutable c2 = CacheImmutable.valueOf("hello");
System.out.println(c1 == c2); // true
}
}
public class IntegerTest{
Interger in1 = new Integer(6);
Interger in2 = Integer.valueOf(6);
Interger in3 = Integer.valueOf(6);
System.out.println(in1 == in2); // false
System.out.println(in2 == in3); // true
}