微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在 Java 中安全地使用不可变类中的集合?

如何解决如何在 Java 中安全地使用不可变类中的集合?

我尝试实现不可变类,我看到一条规则说明“在 getter 方法中执行对象克隆以返回副本而不是返回实际对象引用”。

我知道当我们使用不可变对象时,从 getter 返回的复制/克隆集合不会发生变化。当我们使用自定义类时,也可以看到原始集合的变化,也可以看到从 getter 返回的克隆(浅复制)集合。

在下面的代码中,我无法理解这种情况:

我创建了两种方法,一种用于将原始集合作为 courseList 返回,另一种用于 courselist 的浅拷贝。

我为本地引用 clist1 和 clist2 分配了两个版本。

然后我更改了原始列表中的项目。当我通过学生对象到达它们时,我也可以看到更改的原始列表和复制的列表。但是,通过我之前指向克隆课程列表的参考,无法看到更改!我认为它也应该受到变化的影响。 为什么我看不到以前复制的版本的变化? 这是参考,我认为它应该指向相同的内存区域,我还通过下面的另一个示例再次检查结果。 我创建了一个包含 StringBuilder 的列表。我将新的 strs 应用到 stringbuilder 中,然后我可以看到更改后的先前复制的列表版本。

那么,主要问题是,我必须始终在不可变类中使用深拷贝吗?这是错误用法吗?在不可变类中使用集合的安全方法是什么?

提前致谢。

ImmutableStudent.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();
    }
    
}

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);

    }
}

结果

Course List         :[Literature,Math]

Course List Clone   :[Literature,Math]

Course List         :[Literature,Math,Art]

Course List Clone   :[Literature,Art]

PrevIoUs Course List        :[Literature,Art]

PrevIoUs Course List Clone  :[Literature,Math]

Blist   : [123,567]

BlistCp :[123,567]

Blist   : [1234,567]

BlistCp :[1234,567]

解决方法

来自 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,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,C,D ]
L2 = [ A,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 ]

对于您的问题:

那么,主要问题是,我必须始终在不可变类中使用深拷贝吗?这是错误的用法吗?在不可变类中使用集合的安全方法是什么?

这取决于您想要实现的目标。有两种情况:

场景 A:您希望类的用户收到列表的不可变视图(意味着没有用户可以修改列表),但您希望原始列表发生的任何更改通过不可变视图传播。

场景 B:您希望列表的所有版本都是不可变的,即使是内部保存的版本也是如此。

这两种情况都可以通过使用 Collections.unmodifiableList 来回答,它在 Javadoc 中声明:

返回指定列表的不可修改视图。对返回列表的查询操作“通读”到指定列表,并尝试修改返回的列表,无论是直接还是通过其迭代器,都会导致 UnsupportedOperationException。

不同之处在于你在哪里使用它。对于 Scenario A,您将调用 getter 中的方法,以便每个调用者都会收到列表的不可修改视图,但该类仍会在内部保留可修改视图。对于 Scenario B,您将在类内部存储对调用 unmodifiableList 的结果的引用。请注意,在较新版本的 Java 上,您还可以使用 List.copyOf 创建不可变列表,您可以将其用于 Scenario B

根据您的描述,我相信您想要达到的目标是 scenario A

,

为什么我看不到以前复制的版本的变化?

正是因为你复制了它!它是一个副本 - 与原始对象不同的 ArrayList 对象,恰好包含相同的元素。

这是引用,我认为应该指向相同的内存区域

这仅适用于以下情况:

public List<String> getCourseList() {
    return courseList;
}

这就是您在 clist1 上看到变化的原因。使用 clone(),您正在创建一个新对象并分配新内存。当然,您仍然返回一个引用到一个对象,但它与 courseList 存储的引用不同。这是对副本的引用。

我必须始终在不可变类中使用深拷贝吗?

不需要,只要集合中的元素是不可变的。制作副本的全部目的是让用户不能做这样的事情:

List<String> list = student.getCourseList();
list.add("New Course");

如果getCourseList没有返回副本,上面的代码会改变student的课程列表!我们当然不希望在不可变的类中发生这种情况,对吗?

如果列表元素也是不可变的,那么你的类的用户无论如何都不能改变它们,所以你不需要复制列表元素。


当然,如果您只使用不可变列表,则可以避免所有这些复制:

private final List<String> courseList;
public ImmutableStudent(String _name,Long _id,String _uni,ArrayList<String> _courseList){
    name = _name;
    id = _id;
    uni = _uni;
    courseList = Collections.unmodifiableList(_courseList)
};

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?