为什么需要不可变集合
- 在并发程序中,使用不可变集合保证线程安全。尤其当一个对象是值对象时,更应该考虑采用Immutable方式;
- 被不可信的类库使用时会很安全;
- 对象不支持修改操作,将会节省空间和时间的开销。所有不可变的集合实现都比可变集合更加有效地利用内存;
- 可以当作一个常量来对待,并且这个对象在以后也不会被改变。
- 使用Immutable是一个防御性编程技术。
1. Collections.unmodifiableList(…):不是真正的不可变集合
在JDK类库中很多集合(List、Set、Map等)都可以调用Collections类提供的静态方法unmodifiableXXX(…)来得到一个不可修改的视图 ,但是JDK实现的不是真正的不可变集合,当原始集合被修改后,不可变集合里面的元素也是跟着发生变化。
2. Guava提供的Immutable:真正的不可变集合
从下面的例子了解 1 和 2 差别:
public class Student {
private String name;
/** 课程编号 */
private List<String> courses;
public Student(String name, List<String> courses) {
this.name = name;
this.courses = courses;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getCourses() {
// JDK不可变集合
return Collections.unmodifiableList(courses);
}
public void addCourses(String course) {
this.courses.add(course);
}
public String removeCourses(String course) {
boolean removed = this.courses.remove(course);
if (removed) {
return course;
}
return null;
}
}
---------- 测试场景①:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("001");
list.add("002");
Student student = new Student("Tom", list);
// 修改集合前的打印
System.out.println("----- Tom's course list-----");
list.forEach(System.out::println);
List<String> courseList = student.getCourses();
courseList.add("003");
}
执行结果:
----- Tom's course list-----
001
002
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableCollection.add(Collections.java:1055)
at com.dnl.learning.test.demo.unmodifiable.TestList.main(TestList.java:19)
list集合不可变,向集合添加元素会抛出异常。
---------- 测试场景②:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("001");
list.add("002");
Student student = new Student("Tom", list);
// 修改集合前的打印
System.out.println("----- 修改集合前的打印 list-----");
list.forEach(System.out::println);
List<String> courseList = student.getCourses();
System.out.println("----- 修改集合前的打印 courseList-----");
courseList.forEach(System.out::println);
// courseList.add("003");
// TODO 修改list本身
list.add("004");
// 修改集合后的打印
System.out.println("----- 修改集合后的打印 list-----");
list.forEach(System.out::println);
System.out.println("----- 修改集合后的打印 courseList-----");
courseList.forEach(System.out::println);
}
执行结果:
----- 修改集合前的打印 list-----
001
002
----- 修改集合前的打印 courseList-----
001
002
----- 修改集合后的打印 list-----
001
002
004
----- 修改集合后的打印 courseList-----
001
002
004
通过改变原集合,Java不可变Immutable集合被修改。
修改实体类的 getCourses() 方法:
public List<String> getCourses() {
// return Collections.unmodifiableList(courses);
return ImmutableList.copyOf(courses);
}
测试场景2再执行一遍,courseList集合里面的元素个数没有改变。
----- 修改集合前的打印 list-----
001
002
----- 修改集合前的打印 courseList-----
001
002
----- 修改集合后的打印 list-----
001
002
004
----- 修改集合后的打印 courseList-----
001
002