cod*_*tes 2 java collections immutability deep-copy shallow-copy
我尝试实现不可变类,并且看到一条规则说明“在 getter 方法中执行对象克隆以返回副本而不是返回实际对象引用”。
我知道当我们使用不可变对象时,从 getter 返回的复制/克隆集合不会发生变化。当我们使用自定义类时,从 getter 返回的克隆(浅复制)集合也可以看到原始集合的变化。
在下面的代码中,我无法理解这种情况:
我创建了两种方法,一种用于将原始集合作为 courseList 返回,另一种用于课程列表的浅拷贝。
我为本地引用 clist1 和 clist2 分配了两个版本。
然后我更改了原始列表中的项目。当我通过学生对象到达它们时,我也可以看到更改的原始列表和复制的列表。但是,通过我之前指向克隆课程列表的参考,无法看到更改!我认为它也应该受到变化的影响。为什么我看不到以前复制的版本的变化? 这是参考,我认为它应该指向相同的内存区域,我还通过下面的另一个示例再次检查结果。我创建了一个包含 StringBuilder 的列表。我将新的 strs 应用到 stringbuilder 中,然后我可以看到更改后的先前复制的列表版本。
那么,主要问题是,我必须始终在不可变类中使用深拷贝吗?这是错误的用法吗?在不可变类中使用集合的安全方法是什么?
提前致谢。
不可变学生.java
public final class ImmutableStudent {
public ImmutableStudent(String _name, Long _id, String _uni, ArrayList<String> _courseList){
name = _name;
id = _id;
uni = _uni;
courseList = new ArrayList<>();
_courseList.forEach( course -> courseList.add(course));
}
private final String name;
private final Long id;
private final String uni;
private final ArrayList<String> courseList;
public String getName() {
return name;
}
public Long getId() {
return id;
}
public String getUni() {
return uni;
}
public List<String> getCourseList() {
return courseList;
}
public List<String> getCourseListClone() {
return (ArrayList<String>)courseList.clone();
}
}
Run Code Online (Sandbox Code Playgroud)
ImmutableHelper.java
public class ImmutableHelper {
public static void run(){
ArrayList<String> courseList = new ArrayList<>();
courseList.add("Literature");
courseList.add("Math");
String name = "Emma";
Long id = 123456L;
String uni = "MIT";
ImmutableStudent student = new ImmutableStudent(name, id, uni, courseList);
System.out.println(name == student.getName());
System.out.println(id.equals(student.getId()));
System.out.println(courseList == student.getCourseList());
System.out.println("Course List :" + student.getCourseList());
System.out.println("Course List Clone :" + student.getCourseListClone());
List<String> clist1 = student.getCourseList();
List<String> clist2 = student.getCourseListClone();
student.getCourseList().set(1, "Art");
System.out.println("Course List :" + student.getCourseList());
System.out.println("Course List Clone :" + student.getCourseListClone());
System.out.println("Previous Course List :" + clist1);
System.out.println("Previous Course List Clone :" + clist2);
// Check shallow copy using collections.clone()
ArrayList<StringBuilder> bList = new ArrayList<>();
StringBuilder a = new StringBuilder();
a.append("1").append("2").append("3");
StringBuilder b = new StringBuilder();
b.append("5").append("6").append("7");
bList.add(a);
bList.add(b);
ArrayList<StringBuilder> bListCp = (ArrayList<StringBuilder>)bList.clone();
System.out.println("Blist : " + bList);
System.out.println("BlistCp :" + bListCp);
a.append(4);
System.out.println("Blist : " + bList);
System.out.println("BlistCp :" + bListCp);
}
}
Run Code Online (Sandbox Code Playgroud)
结果
Course List :[Literature, Math]
Course List Clone :[Literature, Math]
Course List :[Literature, Math, Art]
Course List Clone :[Literature, Math, Art]
Previous Course List :[Literature, Math, Art]
Previous Course List Clone :[Literature, Math]
Blist : [123, 567]
BlistCp :[123, 567]
Blist : [1234, 567]
BlistCp :[1234, 567]
Run Code Online (Sandbox Code Playgroud)
小智 6
来自clone()Javadoc:
返回此 ArrayList 实例的浅表副本。(元素本身不会被复制。)
这意味着该clone方法返回的引用实际上是对ArrayList包含与原始列表完全相同元素的新实例的引用。在一个例子中:
// Original list is reference L1 and contains three elements A, B and C
L1 = [ A, B, C ]
// By doing L1.clone you get a reference to a new list L2
L2 = [ A, B, C ]
// When you add a new element to L1 you do not see the change in L2 because
// it is effectively a different list
L1 = [ A, B, C, D ]
L2 = [ A, B, C ]
// However, if you change one of the A, B or C's internal state then that
// will be seen when accessing that object through L2, since the reference
// kept in the lists are the same
L1 = [ A, B', C, D ]
L2 = [ A, B', C ]
Run Code Online (Sandbox Code Playgroud)
对于您的问题:
那么,主要问题是,我必须始终在不可变类中使用深拷贝吗?这是错误的用法吗?在不可变类中使用集合的安全方法是什么?
这取决于您想要实现的目标。有两种情况:
场景 A:您希望类的用户接收列表的不可变视图(意味着没有用户可以修改列表),但您希望原始列表发生的任何更改都通过不可变视图传播。
场景 B:您希望列表的所有版本都是不可变的,即使是内部保存的版本也是如此。
这两种情况都可以通过使用 来回答Collections.unmodifiableList,它在 Javadoc 中说明:
返回指定列表的不可修改视图。对返回列表的查询操作“通读”到指定列表,并尝试修改返回的列表,无论是直接还是通过其迭代器,都会导致 UnsupportedOperationException。
不同之处在于你在哪里使用它。因为Scenario A您将调用 getter 中的方法,以便每个调用者都会收到列表的不可修改视图,但该类仍会在内部保留可修改视图。因为Scenario B您将unmodifiableList在类中存储对内部调用结果的引用。请注意,在较新版本的 Java 上,您还可以使用List.copyOf创建不可变列表,您可以将其用于Scenario B.
根据您的描述,我相信您要实现的目标是scenario A.