天天看點

Java學習筆記一面向對象final修飾符

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
}