簡單聊聊java中的final關鍵字


 

簡單聊聊java中的final關鍵字
日常代碼中,final關鍵字也算常用的。其主要應用在三個方面:

1)修飾類(暫時見過,但是還沒用過);
2)修飾方法(見過,沒寫過);
3)修飾數據

那么,我們主要也是從一下幾個方面探討一下,主要是第三點。

一、final修飾類和方法
final修飾的類不可被繼承(例如: String, Integer, Double, ....);
final修飾的方法不可被重寫(例如: AtomicInteger中的大部分方法)

二、final修飾數據
1. final修飾變量
   分為局部變量和全局變量而言.如果是全局變量,不管你用不用,都必須而且只能賦值一次;
  例如:

public class FinalTest {
  private final int i;
}

  如果不賦值,就會報編譯錯誤: Variable "xxx" might not have been initialized。

  如果是局部變量,如果不使用,可以不賦值。(當然,你得保證你的項目經理不會打死你)。

 

  1.1 final修飾的基本數據類型和不變對象(例如: String, 包裝類以及jdk8新的日期時間類庫)那就是真的什么都不能該變了(引用與對象之間的引用關系,對象的內容(基本類型就是值)的值都不能改變). 例如:

1   public class FinalTest {
2     
3         public static void main(String[] args) {
4     
5             final int a = 10;
6             final LocalDate localDate = LocalDate.now();
7         }
8     }

 

  如果再對a或者localDate進行賦值:就會報編譯錯誤: cannot assign a value to final variable 'xxx'.

  可以說,final對不可變對象的引用的修飾和對基礎類型的引用的修飾含義幾乎是一樣的。

  1.2 對於可變對象而言(常用的StringBuilder, 各種常見(List, Set, Map)的集合實現類),final僅僅只能保證引用和對象之前的引用關系不變,無法確保對象的內容(例如字段的值,容器內元素的個數)不變.

 

 public class FinalTest {
    
    public static void main(String[] args) {
       final StringBuilder sb = new StringBuilder("sb ");
       System.out.println("sb: " + sb);
sb.append("changed"); System.out.println("sb: " + sb); } }

  

-------------------------------------------------------------------------------
輸出:
sb: sb
sb: sb changed


  3. final修飾的值類型(8種基本數據類型 + String)將會優化為編譯期常量

  此處我們利用String的特性來測驗一下:

public class FinalTest {

    public static void main(String[] args) {
        String a = "a";
        String ab = a + "b";
        System.out.println(ab == "ab");


        final String finalA = "a";
        String finalAB = finalA + "b";
        System.out.println(finalAB == "ab");
    }
} 

-------------------------------------------------------------------------------
輸出:
false
true

  此處,我們可以對反編譯FinalTest.class文件:

1 public class FinalTest {
2    public FinalTest() {
3   }
4
5    public static void main(String[] args) {
6        String a = "a";
7        String ab = a + "b";
8        System.out.println(ab == "ab");
9        String finalA = "a";
10       String finalAB = "ab";
11       System.out.println(finalAB == "ab");
12    }
13 }

對比第7行和第10行發現,第7行變量ab的值在編譯期還是未知的(實際上在運行期,第7行的代碼是這樣執行的:

  String ab = new StringBuilder("").append(a).append("b").toString;

而在StringBuilder#toString()方法內,new了一個新的String實例,因此ab 和 "ab"不是一個實例,所以第8行輸出false.),而第10行變量finalAB的值在編譯期就已知了,由於String常量池的緩存特性,使得finalAB和"ab"是同一個實例,所以第11行輸出true. 

這一點,有時候會帶來一些問題。例如下面的例子:

 1 class A{
 2     public static final int A = 10; 3 } 4 5 6 class B{ 7 public B(){ 8  System.out.println(A.A); 9  } 10 }

 

從源代碼中,可以看到class B和class A有些關系。但是實際上,編譯之后的class字節碼,類A 和類B沒有任何關系。編譯之后,第8行A.A已經被替換為10了。如果這個時候,修改了這個A中常量的值,然后僅僅對A重新編譯,就會導致類B的class文件中依然是10.這可能給程序運行代碼一些問題。


final修飾方法參數
  `1這個在jdk中少見,但是在框架代碼中常常見到.基本作用也就是防止方法調用者對參數在做賦值(其實這也是一種約定吧). 例如這樣的場景: 現在有一組任務需要執行(任務可以並行),在這組任務全部執行完成之后,需要做一次清理緩存的操作; 可能的代碼是這樣的

    List<Task> tasks = ...;
        CountDownLatch countDoenLatch = new CountDownLatch(tasks.size());
        for(Task task : tasks){
            //線程池異步執行
            WORKER.submit(task);
        }
        cleanCache(countDownLatch);
-----------------------------------------------------------------------------------------------------        
        public void cleanCache(final CountDownLatch countDownLatch){
           countDownLatch.await();
           ...
        } 


此處cleanCache()方法中的參數CountDownLatch就需要使用final修飾。(如果對countDownLatch重新賦值,后續調用countDownLatch.await()會導致無限期等待)

小結:
final在修飾引用的時候,僅僅只能確保引用能且只能和某個對象建立引用關系(基本類型的值), 至於引用所指向的對象的內容是否可以改變,和這里的final沒有任何關系,而是和這個對象是否是不變對象有關。 (通俗的將,使用final修飾某個引用的時候,這個final能夠管得着的僅僅只是這個引用, 至於這個引起所指向的對象可不可變,它管不着。)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM