詳解Java如何實(shí)現(xiàn)一個(gè)像String一樣不可變的類
如果問你在日常開發(fā)中用到的最多的一個(gè) Java 類是什么,阿粉敢打賭絕對(duì)是 String.class。說到 String 大家都知道 String 是一個(gè)不可變的類;雖然用的很多,那不知道小伙伴們有沒有想過怎么樣創(chuàng)建一個(gè)自己的不可變的類呢?這篇文章阿粉就帶大家來實(shí)踐一下,創(chuàng)建一個(gè)自己的不可變的類。
特性
在手動(dòng)編寫代碼之前,我們先了解一下不可變類都有哪些特性,
- 定義類的時(shí)候需要使用
final關(guān)鍵字進(jìn)行修飾:之所以使用final進(jìn)行修飾是因?yàn)檫@樣可以避免被其他類繼承,一旦有了子類繼承就會(huì)破壞父類的不可變性機(jī)制; - 成員變量需要使用
final 關(guān)鍵詞修飾,并且需要是private的:避免屬性被外部修改; - 成員變量不可提供
setter方法,只能提供getter方法:避免被外部修改,并且避免返回成員變量本身; - 提供所有字段的構(gòu)造函數(shù);
實(shí)操
知道了不可變類的一些基本特性之后,我們來實(shí)際寫代碼操作一下,以及我們會(huì)驗(yàn)證一下,如果不按照上面的要求來編寫的話,會(huì)出現(xiàn)什么樣的問題。
這里我們定義一個(gè) Teacher 類來測(cè)試一下,按照我們上面提到的幾點(diǎn),我們給類和屬性的定義都加上 final 代碼如下所示。
package?com.example.demo.immutable;
import?java.util.List;
import?java.util.Map;
public?final?class?Teacher?{
??private?final?String?name;
??private?final?List<String>?students;
??private?final?Address?address;
??private?final?Map<String,?String>?metadata;
??public?Teacher(String?name,?List<String>?students,?Address?address,?Map<String,?String>?metadata)?{
????this.name?=?name;
????this.students?=?students;
????this.address?=?address;
????this.metadata?=?metadata;
??}
??public?String?getName()?{
????return?name;
??}
??public?List<String>?getStudents()?{
????return?students;
??}
??public?Address?getAddress()?{
????return?address;
??}
??public?Map<String,?String>?getMetadata()?{
????return?metadata;
??}
}package?com.example.demo.immutable;
public?class?Address?{
??private?String?country;
??private?String?city;
??public?String?getCountry()?{
????return?country;
??}
??public?void?setCountry(String?country)?{
????this.country?=?country;
??}
??public?String?getCity()?{
????return?city;
??}
??public?void?setCity(String?city)?{
????this.city?=?city;
??}
}我們思考一下,上面的代碼是否真正的做到了不可變,好,我們思考三秒鐘,心里默默的數(shù)三下。為了回答這個(gè)問題,我們看下下面的測(cè)試代碼。
package?com.example.demo;
import?com.example.demo.immutable.Address;
import?com.example.demo.immutable.Teacher;
import?java.util.ArrayList;
import?java.util.HashMap;
import?java.util.List;
import?java.util.Map;
/**
?*?<br>
?*?<b>Function:</b><br>
?*?<b>Author:</b>@author?Silence<br>
?*?<b>Date:</b>2022-11-22 21:17<br>
?*?<b>Desc:</b>無<br>
?*/
public?class?ImmutableDemo?{
??public?static?void?main(String[]?args)?{
????List<String>?students?=?new?ArrayList<>();
????students.add("鴨血粉絲?1");
????students.add("鴨血粉絲?2");
????students.add("鴨血粉絲?3");
????Address?address?=?new?Address();
????address.setCountry("中國");
????address.setCity("深圳");
????Map<String,?String>?metadata?=?new?HashMap<>();
????metadata.put("hobby",?"籃球");
????metadata.put("age",?"29");
????Teacher?teacher?=?new?Teacher("Java極客技術(shù)",?students,?address,?metadata);
????System.out.println(teacher.getStudents().size());
????System.out.println(teacher.getMetadata().size());
????System.out.println(teacher.getAddress().getCity());
????//?修改屬性
????teacher.getStudents().add("小明");
????teacher.getMetadata().put("weight",?"120");
????teacher.getAddress().setCity("廣州");
????System.out.println(teacher.getStudents().size());
????System.out.println(teacher.getMetadata().size());
????System.out.println(teacher.getAddress().getCity());
??}
}運(yùn)行的結(jié)果如下截圖所示,通過測(cè)試我們可以發(fā)現(xiàn),簡(jiǎn)單的只添加 final 關(guān)鍵字是不能解決不可變性的,我們當(dāng)前的 teacher 實(shí)例已經(jīng)被外層修改掉了成員變量。

為了解決這個(gè)問題,我們還需要對(duì)我們的 Teacher 類進(jìn)行改造,首先我們可以想到的就是需要將 students 和 metadata 兩個(gè)成員變量不能直接返回給外層,否則外層的修改會(huì)直接影響到我們的不可變類,那么我們就可以修改 getter 方法,拷貝一下成員變量進(jìn)行返回,而不是直接返回,修改代碼如下
??public?List<String>?getStudents()?{
????return?new?ArrayList<>(students);
????//return?students;
??}
????public?Map<String,?String>?getMetadata()?{
????return?new?HashMap<>(metadata);
??//return?metadata;
??}
我們?cè)俅芜\(yùn)行上面的測(cè)試代碼,可以看到這次的返回?cái)?shù)據(jù)如下,這次我們的 students 和 metadate 成員變量并沒有被外層修改掉了。但是我們的 address 成員變量還是有問題,沒關(guān)系,我們接著往下看。

很自然的為了解決 address 的問題,我們想到了也是進(jìn)行一個(gè)拷貝,再調(diào)用 getter 方法的時(shí)候返回一個(gè)拷貝對(duì)象,而不是直接返回成員變量。那我們就需要改造 Address 類,將其變成 Cloneable 的即可,我們實(shí)現(xiàn) 接口,然后覆蓋一個(gè) clone 方法,代碼如下
package?com.example.demo.immutable;
public?class?Address?implements?Cloneable{
??...//?省略
??@Override
??public?Address?clone()?{
????try?{
??????return?(Address)?super.clone();
????}?catch?(CloneNotSupportedException?e)?{
??????throw?new?AssertionError();
????}
??}
}再修改 Teacher 的 getAddress 方法
??public?Address?getAddress()?{
??//return?address;
????return?address.clone();
??}
接下來我們?cè)龠\(yùn)行一下測(cè)試代碼,結(jié)果如下,可以看到這次我們的 teacher 實(shí)例的成員變量并沒有被修改掉了,至此我們完成了一個(gè)不可變對(duì)象的創(chuàng)建!

String 的實(shí)現(xiàn)
前面我們看的是自定義實(shí)現(xiàn)不可變類的操作,接下來我們簡(jiǎn)單看一下 String 類是如何實(shí)現(xiàn)不可變的,通過源碼我們可以看到 String 也使用了關(guān)鍵字 final 來避免被子類繼承,以及對(duì)應(yīng)存放具體值的成員變量也使用了 final 關(guān)鍵字。

并且對(duì)外提供的方法 substring 也是通過復(fù)制的形式對(duì)外提供的新的 String 對(duì)象。


到此這篇關(guān)于詳解Java如何實(shí)現(xiàn)一個(gè)像String一樣不可變的類的文章就介紹到這了,更多相關(guān)Java不可變的類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+Kotlin中使用GRPC實(shí)現(xiàn)服務(wù)通信的示例代碼
本文主要介紹了SpringBoot+Kotlin中使用GRPC實(shí)現(xiàn)服務(wù)通信的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
Java實(shí)現(xiàn)Map遍歷key-value的四種方法
本文主要介紹了Java實(shí)現(xiàn)Map遍歷key-value的四種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
關(guān)于Java中try finally return語句的執(zhí)行順序淺析
這篇文章主要介紹了關(guān)于Java中try finally return語句的執(zhí)行順序淺析,需要的朋友可以參考下2017-08-08
解決@PathVariable對(duì)于特殊字符截?cái)嗟膯栴}
這篇文章主要介紹了解決@PathVariable對(duì)于特殊字符截?cái)嗟膯栴},具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02
kafka 重新分配partition和調(diào)整replica的數(shù)量實(shí)現(xiàn)
當(dāng)需要提升Kafka集群的性能和負(fù)載均衡時(shí),可通過kafka-reassign-partitions.sh命令手動(dòng)重新分配Partition,增加節(jié)點(diǎn)后,可以將Topic的Partition的Leader節(jié)點(diǎn)均勻分布,以提高寫入和消費(fèi)速度,感興趣的可以了解一下2022-03-03

