天天看點

Java Review (十二、面向對象----final 修飾符)final變量final方法final類

文章目錄

final關鍵宇可用于修飾類、方法和變量,被它修飾的類、方法和變量不可改變。

成員變量是随類初始化或對象初始化而初始化的 。

  • 當類初始化時,系統會為該類的類變量配置設定記憶體,并配置設定預設值 ;
  • 當建立對象時,系統會為該對象的執行個體變量配置設定記憶體,并配置設定預設值。

對于 final 修飾的成員變量而言,一旦有了初始值,就不能被重新指派,如果既沒有在定義成員變量時指定初始值,也沒有在初始化塊、構造器中為成員變量指定初始值,那麼這些成員變量的值将一直是系統預設配置設定的0、’\u000’、false或 null ,這些成員變也就完全失去了存在的意義。 是以:

Java文法規定 final 修飾的成員變量必須由程式員顯式地指定初始值。

歸納起來, final 修飾的類變量、執行個體變量能指定初始值的地方如下:

  • 類變量 : 必須在靜态初始化塊中指定初始值或聲明該類變量時指定初始值,而且隻能在兩個地方的其中之一指定 。
  • 執行個體變量:必須在非靜态初始化塊、聲明該執行個體變量或構造器中指定初始值 , 而且隻能在三個地方的其中之一指定 。

final成員變量執行個體

public class FinalVariableTest{
    //定義成員變量時指定預設值,合法
   final int a = 6;
   //下面變量将在構造器或初始化塊中配置設定初始值
    final String str;
   final int c;
   final static double d ;
   //既沒有指定預設值 ,又沒有在初始化塊、構造器中指定初始值
    //下面定義的 ch 執行個體變量是不合法的
    // final char ch;
    //初始化塊 ,可對沒有指定預設值的執行個體變量指定初始值
   //;在初始化塊中為執行個體變囊指定初始值,合法
   str = "He110";
  //定義 a 執行個體變量時已經指定了預設值
  //不能為 a 重新指派,是以下面指派語句非法
   // a = 9;
   
   //靜态初始化塊,可對沒有指定預設值的類變量指定初始值
   static{
       //在靜态初始化塊中為類變量指定初始值,合法
       d = 5 . 6;
    }
    
     //構造器,可對既沒有指定預設值, 又沒有在初始化塊中
     //指定初始值的執行個體變量指定初始值
  public FinalVariableTest (){
     //如果在初始化塊中已經對 str 指定了初始值
    //那麼在構造器中不能對 final 變量重新指派,下面指派語句非法
    // str = "java";
    c = 5;
    }
    
   public void changeFinal( ){
     //普通方法不能為 final 修飾的成員變量指派
     //d= 1. 2;
    //不能在普通方法中為 final 成員變量指定初始值
   // ch = ' a ';
   }
   
publ i c static void main(String[] args){
    FinalVariableTest ft = new FinalVariableTest();
    System.out . println(ft . a);
    System . out.println(ft.c) ;
    System . out.println(ft.d);
    }

}      

系統不會對局部變量進行初始化,局部變量必須由程式員顯式初始化 。 是以:

使用 final 修飾局部變量時 , 既可以在定義時指定預設值,也可以不指定預設值 。

  • 如果 final 修飾的局部變量在定義時沒有指定預設值,則可以在後面代碼中對該 final 變量賦初始值,但隻能一次,不能重複指派 ;
  • 如果 final 修飾的局部變量在定義時己經指定預設值,則後面代碼中不能再對該變量指派 。

final修飾局部變量、形參執行個體

pub1ic c1ass Fina1Loca1Variab1eTest{
  pub1ic void test( final int a){
   // 不能對 fina1 修飾的形參指派,下面語句非法
   // a = 5;
   }
   
 pub1ic static void main(String[] args){
   //定義 fina1 局部變量時指定預設值,則 str 變量無法重新指派
   final String str = "hello";
   //下面指派語句非法
   // str = " Java";
   // 定義 fina1 局部變量時沒有指定預設值,則 d 變量可被指派一 次
  final double d;
  // 第一次賦初始值,成功
  d = 5.6;
  // 對 fina1 變量重複指派 , 下面語句非法
  // d = 3.4;
  }
  
}      

  • 當使用 final 修飾基本類型變量時,不能對基本類型變量重新指派,是以基本類型變量不能被改變 。
  • 但對于引用類型變量而言 ,它儲存的僅僅是一個引用, final 隻保證這個引用類型變量所引用的位址不會改變,即 一直引用同一個對象,但這個對象完全可以發生改變 。

final 修飾基本類型變量和引用類型變量的差別執行個體

c1ass Person{
   private int age;
   pub1ic Person() {
   }
  // 有參數的構造器
  pub1ic Person(int age){
   this.age = age;
   //省略 age 的 setter 和 getter 方法
   //age 的 setter 和 getter 方法
  }
}  
 
pub1ic c1ass Fina1ReferenceTest{
  pub1ic static void main(String[] args){
    //fina1 修飾數組變量, iArr 是一個引用變量
   fina1 int[] iArr = (5 , 6, 12, 9) ;
   System.out.print1n(Arrays.toString(iArr));
   //對數組元素進行排序,合法
   Arrays.sort(iArr);
   System.out.println(Arrays.toString(iArr));
  //對數組元素指派,合法
  iArr[2] = - 8 ;
  System.out.println(Arrays.toString (iArr) );
  // 下面語句對 iArr 重新指派,非法
  // iArr = null;
  // final 修飾 Person 變量 , p 是一個引用變量
  final Persoηp =new Person(45) ;
  // 改變 Person 對象的 age 執行個體變量 ,合法
  p.setAge(23) ;
  System.out.println(p.getAge()) ;
  //下面語句對 p 重新指派 ,非法
 // p = null;
}

}
       

之是以要使用final 方法,可能是出于對兩方面理由的考慮。

  • 第一個是為方法“上鎖”,防止任何子類改變它的本來含義。用final修飾的方法的行為在繼承期間保持不變,而且不可被重寫。
  • 采用final 方法的第二個理由是程式執行的效率——将一個方法設成 final 後,編譯器就可以把對那個方法的所有調用都置入“嵌入”調用裡。隻要編譯器發現一個final 方法調用,就會(編譯器判斷)忽略為執行方法調用機制而采取的正常代碼插入方法(将變量壓入堆棧;跳至方法代碼并執行它;跳回來; 清除堆棧變量; 最後對傳回值進行處理)。相反,它會用方法主體内實際代碼的一個副本來替換方法調用。這樣做可避免方法調用時的系統開銷。
public class Test{
    public final void changeName(){
       // 方法體
    }
}
      

對于一個 private 方法 , 因為它僅在目前類中可見, 其子類無法通路該方法 , 是以子類無法重寫該方法一一如果子類中定義一個與父類 private 方法有相同方法名、相同形參清單、相同傳回值類型 的方法 ,也不是方法重寫,隻是重新定義了 一個新方法。是以, 即使使用 final 修飾一個 private 通路權限的方法,依然可 以在其子類中定義與該方法具有相同方法名 、 相同形參清單、相同傳回值類型的方法。

public class PrivateFinalMethodTest{
  private final void test(){}
}  
class Sub extends PrivateFinalMethodTest{
  //下面的方法定義不會出現 問題
  public void test(){}
}      
final 修飾的方法不能被重寫 , 但是可以被重載 。

final 修飾的類不可被繼承。

public final class Test {
   // 類體
}      

不可變( immutable ) 類的意思是建立該類的執行個體後,該執行個體 的執行個體變量是不可改變的。 Java 提供的 8 個包裝類和 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()也相等。

定義一個不可變的 Address 類,程式把 Address 類的 detail 和 postCode 成員變量都使用 private隐藏起來,并使用 final 修飾這兩個成員變量 , 不允許其他方法修改這兩個成員變量的值。

public class Address{
   private final String detail ;
   private final String postCode ;
   // 在構造器裡初始化兩個執行個體變量
   public Address(){
    this .detail = "";
    this.postCode = "";
   }
   
  pub1ic Address(String detai1 , String postCode){
   this.deta = deta ;
   this.postCode = postCode ;
  }
   
  //僅為兩個執行個體變量提供 getter 方法
  pub1ic String getDetai1(){
   return this.detai1 ;
  }
   
  pub1ic String getPostCode(){
   return this.postCode;
  }
   
  //重寫 equa1s ()方法,判斷兩個對象是否相等
  pub1ic boo1ean equa1s(Object obj){
    if (this == obj ){
     return true ;
   }
   
   if(obj != nu11 && obj.getC1ass() == Address.c1ass{
    Address ad = (Address)obj;
    //當 detai1 和 postCode 相等時 , 可認為兩個 Address 對象相等
    if(this . getDeta 工 1() .equa1s (ad . getDetai1())&& this.getPostCode (} . equa1s(ad.getPostCode())){
        return true ;
    }
   } 
  return fa1se ;
 } 
 
 pub1ic int hashCode(){
   return detai1.hashCode() + postCode.hashCode()*31;
 }

}        

參考:

【1】:《瘋狂Java講義》

【2】:

https://www.runoob.com/java/java-modifier-types.html

【3】:《Java程式設計思想》