如果你问你在日常开发中使用最多的Java类是什么,我打赌肯定是String.class。说到String,大家都知道String是一个不可变类;虽然用的很多,但是你有没有想过如何创建自己的不可变类呢?在这篇文章中,阿芬将带你实践并创建你自己的不可变类。特性在手动编写代码之前,我们先来了解一下不可变类有哪些特性。定义类时,需要使用final关键字修饰:之所以使用final修饰,是因为可以避免被其他类继承。子类继承会破坏父类的不变性机制;成员变量需要用final关键字修饰,并且需要private:防止属性被外部修改;成员变量不能提供setter方法,只能提供getter方法:避免外部修改,避免返回成员变量本身;为所有字段提供构造函数;实际操作了解了不可变类的一些基本特性之后,我们就来实际写代码来操作,我们来验证一下,如果不按照上面的要求写,会出现什么样的问题。这里我们定义一个Teacher类来测试它。根据我们上面提到的几点,我们将最终代码添加到类和属性的定义中,如下所示。packagecom.example.demo.immutable;importjava.util.List;importjava.util.Map;publicfinalclassTeacher{privatefinalStringname;privatefinalList学生;私人最终地址地址;privatefinalMap元数据;publicTeacher(Stringname,Liststudents,Addressaddress,Mapmetadata){this.name=name;this.students=学生;this.address=地址;this.metadata=元数据;}publicStringgetName(){返回名称;}publicListgetStudents(){返回学生;}publicAddressgetAddress(){返回地址;}publicMapgetMetadata(){返回元数据;}}packagecom.example.demo.immutable;publicclassAddress{privateStringcountry;私有字符串城市;publicStringgetCountry(){返回国家;}publicvoidsetCountry(Stringcountry){this.country=country;}公共字符串getCity(){返回城市;}publicvoidsetCity(Stringcity){this.city=city;}}我们思考一下上面的代码是不是真的不可变,好吧,我们思考三秒,默数到三来回答这个问题,我们来看下面的测试代码。包com.example.demo;导入com.example.demo.immutable.Address;导入com.example.demo.immutable.Teacher;导入java.util.ArrayList;导入java.util.HashMap;导入java.util.List;importjava.util.Map;/***
*功能:
*作者:@authorSilence
*日期:2022-11-2221:17
*Desc:无
*/publicclassImmutableDemo{publicstaticvoidmain(String[]args){Liststudents=新数组列表<>();students.add("鸭血粉丝1");students.add("鸭血粉丝2");students.add("鸭血粉丝3");地址address=newAddress();address.setCountry("中国");address.setCity("深圳");Mapmetadata=newHashMap<>();metadata.put("爱好","篮球");metadata.put("年龄","29");Teacherteacher=newTeacher("Java极客技术",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("体重","120");teacher.getAddress().setCity("广州");System.out.println(teacher.getStudents().size());系统.out.println(teacher.getMetadata().size());System.out.println(teacher.getAddress().getCity());}}运行结果如下图所示。通过测试,我们可以发现,很简单,只加final关键字,并不能解决不可变性。我们当前的teacher实例已经被外层修改,去掉了成员变量。为了解决这个问题,我们还需要修改我们的Teacher类。首先我们可以想到students和metadata这两个成员变量是必须的,不能直接返回给外层,否则外层的修改会直接影响到我们的不可变类,那么我们可以修改getter方法,复制成员变量并返回,而不是直接返回,修改代码如下publicListgetStudents(){returnnewArrayList<>(students);//返回学生;}publicMapgetMetadata(){returnnewHashMap<>(metadata);//返回元数据;}我们再次运行上面的测试代码,可以看到返回的数据如下,这次我们的students和metadata成员变量没有被外层修改。但是我们的address成员变量还是有问题,没关系,我们往下看。自然,为了解决地址的问题,我们也想到了做一个拷贝,然后在调用getter方法的时候返回一个拷贝对象,而不是直接返回成员变量。然后我们需要改造Address类,将其变成Cloneable。我们实现接口,然后覆盖克隆方法。代码如下packagecom.example.demo.immutable;publicclassAddressimplementsCloneable{...//省略@OverridepublicAddressclone(){try{return(Address)super.clone();}catch(CloneNotSupportedExceptione){thrownewAssertionError();}}}然后修改Teacher的getAddress方法publicAddressgetAddress(){//returnaddress;返回地址.clone();}接下来,我们再次运行测试代码,结果如下。可以看到这次我们的teacher实例的成员变量没有被修改,至此我们完成了一次不可变对象的创建!在String的实现中,我们查看了不可变类自定义实现的操作。接下来,我们简单看一下String类是如何实现不可变的。通过源码我们可以看到String也使用了final关键字来避免被子类继承,对应的存储具体值的成员变量也使用了final关键字。而对外提供的方法substring也是以拷贝的形式对外提供的一个新的String对象。注意这里的JDK版本是19,所以大家的版本可能不一致,具体实现也不一样,但是本质上是一样的。