写点什么

深入理解 Java 中的不可变对象 (1),面试加分项

发布于: 10 小时前
    return new ImmutableRGB(red, green, blue, name);
}


public int getRGB() {
return ((red << 16) | (green << 8) | blue);
}


public String getName() {
return name;
}
复制代码


}





由于set方法并没有改变原来的对象,而是新创建了一个对象,所以无论线程1或者线程2怎么调用set方法,都不会出现并发访问导致的数据不一致的问题。


### [](
)2)消除副作用


很多时候一些很严重的bug是由于一个很小的副作用引起的,并且由于副作用通常不容易被察觉,所以很难在编写代码以及代码review过程中发现,并且即使发现了也可能会花费很大的精力才能定位出来。


举个简单的例子:
复制代码


class Person {


private int age;   // 年龄
private String identityCardID; // 身份证号码


public int getAge() {
return age;
}


public void setAge(int age) {
this.age = age;
}


public String getIdentityCardID() {
return identityCardID;
}


public void setIdentityCardID(String identityCardID) {
this.identityCardID = identityCardID;
}
复制代码


}


public class Test {


public static void main(String[] args) {
Person jack = new Person();
jack.setAge(101);
jack.setIdentityCardID("42118220090315234X");


System.out.println(validAge(jack));
复制代码


// 后续使用可能没有察觉到 jack 的 age 被修改了


// 为后续埋下了不容易察觉的问题


}


public static boolean validAge(Person person) {
if (person.getAge() >= 100) {
person.setAge(100); // 此处产生了副作用
return false;
}
return true;
}
复制代码


}





validAge函数本身只是对age大小进行判断,但是在这个函数里面有一个副作用,就是对参数person指向的对象进行了修改,导致在外部的jack指向的对象也发生了变化。


如果Person对象是不可变的,在validAge函数中是无法对参数person进行修改的,从而避免了validAge出现副作用,减少了出错的概率。


### [](
)3)减少容器使用过程出错的概率


我们在使用HashSet时,如果HashSet中元素对象的状态可变,就会出现元素丢失的情况,比如下面这个例子:
复制代码


class Person {


private int age;   // 年龄
private String identityCardID; // 身份证号码


public int getAge() {
return age;
}


public void setAge(int age) {
this.age = age;
}


public String getIdentityCardID() {
return identityCardID;
}


public void setIdentityCardID(String identityCardID) {
this.identityCardID = identityCardID;
}


@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}


if (!(obj instanceof Person)) {
return false;
}
Person personObj = (Person) obj;
return this.age == personObj.getAge() && this.identityCardID.equals(personObj.getIdentityCardID());
}


@Override
public int hashCode() {
return age * 37 + identityCardID.hashCode();
}
复制代码


}


public class Test {


public static void main(String[] args) {
Person jack = new Person();
jack.setAge(10);
jack.setIdentityCardID("42118220090315234X");


Set<Person> personSet = new HashSet<Person>();
personSet.add(jack);


jack.setAge(11);


System.out.println(personSet.contains(jack));


}
复制代码


}





输出结果:


![](https://static001.geekbang.org/infoq/7b/7b11e0d8407c6e060feae7527f9de02a.png)


所以在Java中,对于String、包装器这些类,我们经常会用他们来作为HashMap的key,试想一下如果这些类是可变的,将会发生什么?后果不可预知,这将会大大增加Java代码编写的难度。


[](
)三.如何创建不可变对象
==========================================================================


通常来说,创建不可变类原则有以下几条:


**1)所有成员变量必须是private**


**2)最好同时用final修饰(非必须)**


**3)不提供能够修改原有对象状态的方法**


* 最常见的方式是不提供setter方法

* 如果提供修改方法,需要新创建一个对象,并在新创建的对象上进行修改



**4)通过构造器初始化所有成员变量,引用类型的成员变量必须进行深拷贝(deep copy)**


**5)getter方法不能对外泄露this引用以及成员变量的引用**


**6)最好不允许类被继承(非必须)**


JDK中提供了一系列方法方便我们创建不可变集合,如:
复制代码


Collections.unmodifiableList(List<? extends T> list)





另外,在Google的Guava包中也提供了一系列方法来创建不可变集合,如:
复制代码


ImmutableList.copyOf(list)





这2种方式虽然都能创建不可变list,但是两者是有区别的,JDK自带提供的方式实际上创建出来的不是真正意义上的不可变集合,看unmodifiableList方法的实现就知道了:
![](https://static001.geekbang.org/infoq/25/255d64930757e8833c5ab0cdb8765cad.png)
![](https://static001.geekbang.org/infoq/e3/e32099179cd660e7b808f22d24d3bd98.png)
可以看出,实际上UnmodifiableList是将入参list的引用复制了一份,同时将所有的修改方法抛出UnsupportedOperationException。因此如果在外部修改了入参list,实际上会影响到UnmodifiableList,而Guava包提供的ImmutableList是真正意义上的不可变集合,它实际上是对入参list进行了深拷贝。看下面这段测试代码的结果便一目了然:
复制代码


public class Test {


public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
System.out.println(list);


List unmodifiableList = Collections.unmodifiableList(list);
ImmutableList immutableList = ImmutableList.copyOf(list);


list.add(2);
System.out.println(unmodifiableList);
System.out.println(immutableList);


}
复制代码


}





输出结果:

### 最后
看完上述知识点如果你深感Java基础不够扎实,或者刷题刷的不够、知识不全面
小编专门为你量身定制了一套<Java一线大厂高岗面试题解析合集:JAVA基础-中级-高级面试+SSM框架+分布式+性能调优+微服务+并发编程+网络+设计模式+数据结构与算法>
![image](https://static001.geekbang.org/infoq/8c/8ced4f6172c616c37c3b34a064262917.png)
针对知识面不够,也莫慌!还有一整套的<Java核心进阶手册>,可以瞬间查漏补缺
![image](https://static001.geekbang.org/infoq/1d/1d7d5a32c0f0eb64c7a1e01f97bb3ba1.png)
> 全都是一丢一丢的收集整理纯手打出来的>> **[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】](https://ali1024.coding.net/public/P7/Java/git)**

更有纯手绘的各大知识体系大纲,可供梳理:Java筑基、MySQL、Redis、并发编程、Spring、分布式高性能架构知识、微服务架构知识、开源框架知识点等等的xmind手绘图~
![image](https://static001.geekbang.org/infoq/cf/cf6fb24fd2758cf99d9e7c77efbad8cf.png)
![image](https://static001.geekbang.org/infoq/e6/e606988436daccb4ec2ce210e752967c.png)
复制代码


用户头像

VX:vip204888 领取资料 2021.07.29 加入

还未添加个人简介

评论

发布
暂无评论
深入理解Java中的不可变对象(1),面试加分项