JAVA的可變類與不可變類


可變類和不可變類(Mutable and Immutable Objects)

1. 可變類和不可變類(Mutable and Immutable Objects)的初步定義:
可變類:當你獲得這個類的一個實例引用時,你可以改變這個實例的內容。
不可變類:當你獲得這個類的一個實例引用時,你不可以改變這個實例的內容。不可變類的實例一但創建,其內在成員變量的值就不能被修改。 
舉個例子:String和StringBuilder,String是immutable的,每次對於String對象的修改都將產生一個新的String對象,而原來的對象保持不變,而StringBuilder是mutable,因為每次對於它的對象的修改都作用於該對象本身,並沒有產生新的對象。

2. 如何創建一個自己的不可變類:
.所有成員都是private final
.不提供對成員的改變方法,例如:setXXXX 提供帶參數的構造器,用於根據傳入的參數來初始化屬性。
.確保所有的方法不會被重載。手段有兩種:使用final Class(強不可變類),或者將所有類方法加上final(弱不可變類)。
.如果某一個類成員不是原始變量(primitive)或者不可變類,必須通過在成員初始化(in)或者get方法(out)時通過深度clone方法,來確保類的不可變。
.如果有必要,重寫hashCode和equals方法,同時應保證兩個用equals方法判斷為相等的對象,其hashCode也應相等。
示例:
  1. public class Address {  
  2.     private final String detail;  
  3.   
  4.     public Address() {  
  5.         this.detail = "";  
  6.     }  
  7.   
  8.     public Address(String detail) {  
  9.         this.detail = detail;  
  10.     }  
  11.   
  12.     public String getDetail() {  
  13.         return detail;  
  14.     }  
  15.   
  16.     @Override  
  17.     public int hashCode() {  
  18.         return detail.hashCode();  
  19.     }  
  20.   
  21.     @Override  
  22.     public boolean equals(Object obj) {  
  23.         if (obj instanceof Address) {  
  24.             Address address = (Address) obj;  
  25.             if (this.getDetail().equals(address.getDetail())) {  
  26.                 return true;  
  27.             }  
  28.         }  
  29.         return false;  
  30.     }  
  31.   

要寫出這樣的類,需要遵循以下幾個原則:

1)immutable對象的狀態在創建之后就不能發生改變,任何對它的改變都應該產生一個新的對象。

2)Immutable類的所有的屬性都應該是final的。

3)對象必須被正確的創建,比如:對象引用在對象創建過程中不能泄露(leak)。

4)對象應該是final的,以此來限制子類繼承父類,以避免子類改變了父類的immutable特性。

5)如果類中包含mutable類對象,那么返回給客戶端的時候,返回該對象的一個拷貝,而不是該對象本身(該條可以歸為第一條中的一個特例)


有時候你要實現的immutable類中可能包含mutable的類,比如java.util.Date,盡管你將其設置成了final的,但是它的值還是可以被修改的,為了避免這個問題,我們建議返回給用戶該對象的一個拷貝,這也是Java的最佳實踐之一。下面是一個創建包含mutable類對象的immutable類的例子:

public final class ImmutableReminder{ private final Date remindingDate; public ImmutableReminder (Date remindingDate) { if(remindingDate.getTime() < System.currentTimeMillis()){ throw new IllegalArgumentException("Can not set reminder” + “ for past time: " + remindingDate);  } this.remindingDate = new Date(remindingDate.getTime()); } public Date getRemindingDate() { return (Date) remindingDate.clone(); } }

上面的getRemindingDate()方法可以看到,返回給用戶的是類中的remindingDate屬性的一個拷貝,這樣的話如果別人通過getRemindingDate()方法獲得了一個Date對象,然后修改了這個Date對象的值,那么這個值的修改將不會導致ImmutableReminder類對象中remindingDate值的修改。

使用Immutable類的好處:
1)Immutable對象是線程安全的,可以不用被synchronize就在並發環境中共享

2)Immutable對象簡化了程序開發,因為它無需使用額外的鎖機制就可以在線程間共享

3)Immutable對象提高了程序的性能,因為它減少了synchroinzed的使用

4)Immutable對象是可以被重復使用的,你可以將它們緩存起來重復使用,就像字符串字面量和整型數字一樣。你可以使用靜態工廠方法來提供類似於valueOf()這樣的方法,它可以從緩存中返回一個已經存在的Immutable對象,而不是重新創建一個。

immutable也有一個缺點就是會制造大量垃圾,由於他們不能被重用而且對於它們的使用就是”用“然后”扔“,字符串就是一個典型的例子,它會創造很多的垃圾,給垃圾收集帶來很大的麻煩。當然這只是個極端的例子,合理的使用immutable對象會創造很大的價值。

 

Java的string類為什么是不可變的

什么是不可變對象(immutable object),不可變對象有什么好處,在什么情況下應該用,或者更具體一些,Java的String類為什么要設成immutable類型?
不可變對象,顧名思義就是創建后不可以改變的對象,典型的例子就是Java中的String類。

[java] view plain copy
  1. String s = "ABC";    
  2. s.toLowerCase();  
如上s.toLowerCase()並沒有改變“ABC“的值,而是創建了一個新的String類“abc”,然后將新的實例的指向變量s。
字符串常量池(String pool, String intern pool, String保留池) 是Java堆內存中一個特殊的存儲區域, 當創建一個String對象時,假如此字符串值已經存在於常量池中,則不會創建一個新的對象,而是引用已經存在的對象。
如下面的代碼所示,將會在堆內存中只創建一個實際String對象.
[java] view plain copy
  1. String s1 = "abcd";    
  2. String s2 = "abcd";    

相對於可變對象,不可變對象有很多優勢:
1).不可變對象可以提高String Pool的效率和安全性。如果你知道一個對象是不可變的,那么需要拷貝這個對象的內容時,就不用復制它的本身而只是復制它的地址,復制地址(通常一個指針的大小)需要很小的內存效率也很高。對於同時引用這個“ABC”的其他變量也不會造成影響。
Java中String對象的哈希碼被頻繁地使用, 比如在hashMap 等容器中。
字符串不變性保證了hash碼的唯一性,因此可以放心地進行緩存.這也是一種性能優化手段,意味着不必每次都去計算新的哈希碼. 在String類的定義中有如下代碼:
[java] view plain copy
  1. private int hash;//用來緩存HashCode    


2).不可變對象對於多線程是安全的,因為在多線程同時進行的情況下,一個可變對象的值很可能被其他進程改變,這樣會造成不可預期的結果,而使用不可變對象就可以避免這種情況。

3)安全性
如果字符串是可變的,那么會引起很嚴重的安全問題。譬如,數據庫的用戶名、密碼都是以字符串的形式傳入來獲得數據庫的連接,或者在socket編程中,主機名和端口都是以字符串的形式傳入。因為字符串是不可變的,所以它的值是不可改變的,否則黑客們可以鑽到空子,改變字符串指向的對象的值,造成安全漏洞。
假如有如下的代碼:

[plain] view plain copy
  1. boolean connect(String s) {  
  2.     if (!isSecure(s)) {  
  3.         throw new SecurityException();  
  4.     }  
  5.     causeProblem(s);  
  6. }  
如果在其他地方可以修改String,那么此處就會引起各種預料不到的問題/錯誤 
當然也有其他方面原因,但是 Java把String設成immutable最大的原因應該是效率和安全。


密碼應該存放在字符數組中而不是String中
但有的時候String的immutable特性也會引起安全問題,這就是密碼應該存放在字符數組中而不是String中的原因!
1) Since Strings are immutable in Java if you store password as plain text it will be available in memory until Garbage collector clears it and since String are used in String pool for reusability there is pretty high chance that it will be remain in memory for long duration, which pose a security threat. Since any one who has access to memory dump can find the password in clear text and that's another reason you should always used an encrypted password than plain text. Since Strings are immutable there is no way contents of Strings can be changed because any change will produce new String, while if you char[] you can still set all his element as blank or zero. So Storing password in character array clearly mitigates security risk of stealing password.

1)由於String在Java中是不可變的,如果你將密碼以明文的形式保存成字符串,那么它將一直留在內存中,直到垃圾收集器把它清除。而由於字符串被放在字符串緩沖池中以方便重復使用,所以它就可能在內存中被保留很長時間,而這將導致安全隱患,因為任何能夠訪問內存(memory dump內存轉儲)的人都能清晰的看到文本中的密碼,這也是為什么你應該總是使用加密的形式而不是明文來保存密碼。由於字符串是不可變的,所以沒有任何方式可以修改字符串的值,因為每次修改都將產生新的字符串,然而如果你使用char[]來保存密碼,你仍然可以將其中所有的元素都設置為空或者零。所以將密碼保存到字符數組中很明顯的降低了密碼被竊取的風險。


2) Java itself recommends using getPassword() method of JPasswordField which returns a char[] and deprecated getText() method which returns password in clear text stating security reason. Its good to follow advice from Java team and adhering to standard rather than going against it.

2)Java本身也推薦使用JPasswordField組件的getPassword()方法,該方法將返回一個字符數組,而放棄了原來的getText()方法,這個方法把密碼以明文的形式返回而可能會引起安全問題。所以,最好能聽從來自Java團隊的建議並且堅持標准,而不是去反對它。


3) With String there is always a risk of printing plain text in log file or console but if use Array you won't print contents of array instead its memory location get printed. though not a real reason but still make sense.

3)使用字符串,在將文本輸出到日志文件或者控制台的時候會存在風險。但是使用數組你不會把數組的內容打印出來,相反,打印出來的是數組在內存中的位置。盡管這算不上一個真正的原因,但這仍然很有意義。

That's all on why character array is better choice than String for storing passwords in Java. Though using char[] is not just enough you need to erase content to be more secure. I also suggest working with hash'd or encrypted password instead of plain text and clearing it from memory as soon as authentication is completed.

這就是全部的關於為什么使用字符數組存儲密碼比字符串更好。只使用字符數組也是不夠的,為了更安全你需要將數組內容進行轉化。我也建議使用哈希的或者是加密過的密碼而不是明文,然后一旦完成驗證,就將它從內存中清除掉。

轉載文檔

1) http://shixm.iteye.com/blog/297906

2)http://www.jb51.NET/article/49092.htm

3)http://my.oschina.Net/jasonultimate/blog/166968

4)http://www.cnblogs.com/gdjdsjh/p/5111083.html


免責聲明!

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



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