首页 文章

不可变与不可修改的集合

提问于
浏览
141

来自Collections Framework Overview

不支持修改操作的集合(例如添加,删除和清除)称为不可修改 . 不可修改的集合是可修改的 . 另外保证Collection对象中没有可见变化的集合称为不可变 . 不可变的集合是可变的 .

我无法理解这种区别 .
这里不可修改和不可变的区别是什么?

6 回答

  • 11

    不可修改的集合通常是可修改集合的包装,其他代码可能仍然可以访问 . 所以虽然你可以依靠不改变的内容 .

    不可变集合保证不再有任何东西可以改变集合 . 如果它包装了一个可修改的集合,它确保没有其他代码可以访问该可修改的集合 . 请注意,虽然没有代码可以更改集合包含引用的对象,但是对象本身可能仍然是可变的 - 创建 StringBuilder 的不可变集合并不以某种方式"freeze"这些对象 .

    基本上,区别在于其他代码是否能够改变背后的集合 .

  • 2

    基本上 unModifiable 集合是一个视图,所以间接它可能仍然是来自其他一些可修改的引用的'modified' . 此外,它只是一个 readonly view 的另一个集合,当源集合更改时,unModifiable Collection将始终显示最新值 .

    但是 immutable Collection可以视为另一个集合的 readonly copy ,无法修改 . 在这种情况下,当源集合发生更改时,不可变集合不会反映更改

    这是一个可视化这种差异的测试用例 .

    @Test
    public void testList() {
    
        List<String> modifiableList = new ArrayList<String>();
        modifiableList.add("a");
    
        System.out.println("modifiableList:"+modifiableList);
        System.out.println("--");
    
    
        //unModifiableList
    
        assertEquals(1, modifiableList.size());
    
        List<String> unModifiableList=Collections.unmodifiableList(
                                            modifiableList);
    
        modifiableList.add("b");
    
        boolean exceptionThrown=false;
        try {
            unModifiableList.add("b");
            fail("add supported for unModifiableList!!");
        } catch (UnsupportedOperationException e) {
            exceptionThrown=true;
            System.out.println("unModifiableList.add() not supported");
        }
        assertTrue(exceptionThrown);
    
        System.out.println("modifiableList:"+modifiableList);
        System.out.println("unModifiableList:"+unModifiableList);
    
        assertEquals(2, modifiableList.size());
        assertEquals(2, unModifiableList.size());
                System.out.println("--");
    
    
    
                //immutableList
    
    
        List<String> immutableList=Collections.unmodifiableList(
                                new ArrayList<String>(modifiableList));
    
        modifiableList.add("c");
    
        exceptionThrown=false;
        try {
            immutableList.add("c");
            fail("add supported for immutableList!!");
        } catch (UnsupportedOperationException e) {
            exceptionThrown=true;
            System.out.println("immutableList.add() not supported");
        }
        assertTrue(exceptionThrown);
    
    
        System.out.println("modifiableList:"+modifiableList);
        System.out.println("unModifiableList:"+unModifiableList);
        System.out.println("immutableList:"+immutableList);
        System.out.println("--");
    
        assertEquals(3, modifiableList.size());
        assertEquals(3, unModifiableList.size());
        assertEquals(2, immutableList.size());
    
    }
    

    Output

    modifiableList:[a]
    --
    unModifiableList.add() not supported
    modifiableList:[a, b]
    unModifiableList:[a, b]
    --
    immutableList.add() not supported
    modifiableList:[a, b, c]
    unModifiableList:[a, b, c]
    immutableList:[a, b]
    --
    
  • 2

    我认为主要区别在于可变集合的所有者可能希望提供对其他代码的集合访问,但是通过不允许其他代码修改集合的接口提供该访问(同时保留该功能)拥有代码) . 因此集合不是不可变的,但某些用户不允许更改集合 .

    Oracle的Java Collection Wrapper tutorial有这样说(重点补充):

    不可修改的包装器有两个主要用途,如下所示:在构建集合后使集合不可变 . 在这种情况下,最好不要维护对支持集合的引用 . 这绝对保证了不变性 . 允许某些客户端以只读方式访问您的数据结构 . 您保留对支持集合的引用,但分发对包装器的引用 . 通过这种方式,客户可以查看但不能修改,同时保持完全访问权限 .

  • 181

    如果我们谈论JDK Unmodifiable* vs guava Immutable* ,实际上差异也在 performance . 如果它们是常规集合的包装器(JDK实现是包装器),则不可变集合可以更快,更高效 . Citing the guava team

    JDK提供了Collections.unmodifiableXXX方法,但在我们看来,这些方法可以

    <...>

    • 效率低下: the data structures still have all the overhead of mutable collections, including concurrent modification checks, extra space in hash tables, etc.
  • 1

    引用The Java™ Tutorials

    与为包装集合添加功能的同步包装器不同,不可修改的包装器可以消除功能 . 特别是,它们通过拦截将修改集合并抛出UnsupportedOperationException的所有操作来消除修改集合的能力 . 不可修改的包装器有两个主要用途,如下所示:在构建集合后使集合不可变 . 在这种情况下,最好不要维护对支持集合的引用 . 这绝对保证了不变性 . 允许某些客户端以只读方式访问您的数据结构 . 您保留对支持集合的引用,但分发对包装器的引用 . 通过这种方式,客户可以查看但不能修改,同时保持完全访问权限 .

    (强调我的)

    这真的总结了 .

  • 77

    如上所述,不可修改不像是不可变的,因为如果例如不可修改的集合具有由其他对象引用的底层委托集合并且该对象改变它,则可以改变不可修改的集合 .

    关于不可变的,它甚至没有明确定义 . 但是,通常它意味着对象“不会改变”,但需要递归地定义 . 例如,我可以在类上定义不可变,其实例变量都是基元,并且其方法都不包含参数和返回基元 . 然后,这些方法递归地允许实例变量是不可变的,并且所有方法都包含不可变的参数并返回不可变的值 . 应保证方法随时间返回相同的值 .

    假设我们可以做到这一点,那么概念线程也是安全的 . And you might be led to believe that immutable (or not changeble over time) also implies thread safe. However that is not the case 这是我在这里提出的主要观点,在其他答案中尚未注意到 . 我可以构造一个永远返回相同结果但不是线程安全的不可变对象 . 为了看到这一点,假设我通过随时间保持添加和删除来构造不可变集合 . 现在,immutable集合通过查看内部集合(可能随时间变化)返回其元素,然后(内部)添加和删除在创建集合后添加或删除的元素 . 显然,虽然集合总是返回相同的元素,但它不是线程安全的,仅仅因为它永远不会改变 Value .

    现在我们可以将不可变的对象定义为线程安全且永远不会更改的对象 . 有一些指导用于创建通常会导致此类的不可变类,但请记住,可能存在创建不可变类的方法,这需要注意线程安全性,例如,如上面的“快照”集合示例中所述 .

相关问题