天天看點

【小家java】String類為什麼要設計成final?不可變有什麼優點?

相關閱讀

【小家java】java5新特性(簡述十大新特性) 重要一躍

【小家java】java6新特性(簡述十大新特性) 雞肋更新

【小家java】java7新特性(簡述八大新特性) 不溫不火

【小家java】java8新特性(簡述十大新特性) 飽受贊譽

【小家java】java9新特性(簡述十大新特性) 褒貶不一

【小家java】java10新特性(簡述十大新特性) 小步疊代

【小家java】java11新特性(簡述八大新特性) 首個重磅LTS版本

源碼解釋:

先貼一下String類的申明代碼:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {}
           

它最大的一個特點是被final修飾了。我們先看看官方怎麼解釋:

Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared.

翻譯如下:

字元串是恒定的,建立之後它們的值不能被改變。StringBuffer是可變的strings.字元串對象不可變讓它們可以被共享。

先認識final類

要了解為什麼,首先我們得分析一下final修飾類,這個類有什麼特點呢?

從安全上講:

  • final的出現就是為了為了不想改變
  • final 修飾的類是不被能繼承的,是以 final 修飾的類是不能被篡改的(因為不可能有子類了嘛)

從效率上講:

  • 設計成final,JVM才不用對相關方法在虛函數表中查詢,而直接定位到String類的相關方法上,提高了執行效率 這點對提高效率特别重要
  • Java設計者認為共享帶來的效率更高(比如常量池、線程池都是這個概念)

設計者為什麼讓String被final修飾

首先我們有個共識,String類絕對是被我們使用得最多的一個類,沒有之一。是以它是Java非常底層的一個類,一個資料結構。由于使用得實在太多,是以在設計上做了安全性和效率性的考慮。(Java9在底層存儲結構上都進行了優化,旨在提高效率)

要了解這個問題,需要先了解以下幾點:

  • immutable ——不可改變
    • 不可改變類——是指類的狀态不變,一旦建立,狀态就是固定不變的
  • 字元串池——String pool(常量池,實際上分為兩種形态:靜态常量池和運作時常量池)
public static void main(String[] args) {
        String a = "HELLO";
        String b = "HELLO";
        String c = new String("HELLO");
        String d = new String("HELLO");
        System.out.println(a == b); //true 這裡true,字元池的效果展現出來了
        System.out.println(b == c); //false
        System.out.println(c == d); //false
        System.out.println(a.equals(b)); //true equals都會傳回true

        //intern()方法試用一把
        c = c.intern();
        System.out.println(b == c); //true 這裡直接也傳回true了
    }

           

String pools是為了提高JAVA記憶體使用率而采用的措施,當遇到String a = “HELLO”時,JAVA會先在字元串池中查找是否存在“HELLO”這個字元串,如果沒有,則新建立一個對象,然後變量a指向這個位址,然後再遇到String b = “HELLO”時,由于字元串池中以及有了“HELLO”這個對象,是以直接将變量b的位址指向“HELLO”,省去了重新配置設定的麻煩,如圖:

【小家java】String類為什麼要設計成final?不可變有什麼優點?
在JAVA中,“==”對于兩個基本類型,判斷内容是否相等,對于對象判斷兩個對象的位址值是否相等

那麼String c = new String(“Hello”)又如何處理呢?

如果是這種寫法,則不會去通路字元串池,而是先為變量 c 開辟空間,然後将值寫入空間。是以b == c傳回false,c == d同樣傳回false。至于String的equals方法,因為它比較的不是對象的位址,而是對象的值,是以都傳回true就不奇怪了。

Java虛拟機有一個字元串池,儲存着幾乎所有的字元串對象。字元串表達式總是指向字元串池中的一個對象。使用new操作建立的字元串對象不指向字元串池中的對象

最後一句我們看到,當我們使用intern()方法後,會和池子的對象一樣的效果了。

簡單介紹下intern()方法的原理:如果池中已經有相同的 字元串。有則直接傳回池中的字元串,否則先将字元串添加到池中,再傳回。這步操作相當于手動向常量池裡扔東西

另外,因為String是底層的類,且是使用最為廣泛的類。是以用final修飾,自然而然的方法也會被final修飾。是以在調用String的任何方法的時候,都采用JVM的内嵌機制,效率會有較大的提升

闡述設計成final類的優點

隻有當字元串是不可變的,字元串池才有可能實作

字元串池的實作可以在運作時節約很多heap空間,因為不同的字元串變量都指向池中的同一個字元串。但如果字元串是可變的,那麼String interning将不能實作(注:String interning是指對不同的字元串僅僅隻儲存一個,即不會儲存多個相同的字元串。),因為這樣的話,如果變量改變了它的值,那麼其它指向這個值的變量的值也會一起改變。

如果字元串是可變的,那麼會引起很嚴重的安全問題

譬如,資料庫的使用者名、密碼都是以字元串的形式傳入來獲得資料庫的連接配接,或者在socket程式設計中,主機名和端口都是以字元串的形式傳入。因為字元串是不可變的,是以它的值是不可改變的,否則黑客們可以鑽到空子,改變字元串指向的對象的值,造成安全漏洞。

因為字元串是不可變的,是以是多線程安全的

同一個字元串執行個體可以被多個線程共享。這樣便不用因為線程安全問題而使用同步。字元串自己便是線程安全的。

類加載器要用到字元串,不可變性提供了安全性,以便正确的類被加載

譬如你想加載java.sql.Connection類,而這個值被改成了myhacked.Connection,那麼會對你的資料庫造成不可知的破壞。

作為Map的key,提高了通路效率

繼續閱讀