Java 番石榴';s的不可变列表并非真的不可变

Java 番石榴';s的不可变列表并非真的不可变,java,guava,Java,Guava,不可变列表: 尽管这个类不是final,但它不能被子类化,因为它没有公共或受保护的构造函数 我知道这有点牵强,但是可以在com.google.common.collectpackage(因为它的构造函数不是私有的,而是包私有的)中创建一个子类ImmutableList,它是可变的。从这一点上讲,任何人只要获得对不可变列表的引用,就不能确定它确实是不可变的。这是否违背了ImmutableList的目的?即使您扩展了ImmutableList,您也无法使其可变,因为它的所有变异方法都是最终的,例如:

不可变列表

尽管这个类不是final,但它不能被子类化,因为它没有公共或受保护的构造函数


我知道这有点牵强,但是可以在
com.google.common.collect
package(因为它的构造函数不是私有的,而是包私有的)中创建一个子类
ImmutableList
,它是可变的。从这一点上讲,任何人只要获得对
不可变列表的引用,就不能确定它确实是不可变的。这是否违背了
ImmutableList
的目的?

即使您扩展了
ImmutableList
,您也无法使其可变,因为它的所有变异方法都是最终的,例如:

  public final void add(int index, E element) {
    throw new UnsupportedOperationException();
  }

它的迭代器返回的remove方法也是final

在一天结束时,Guava只是一堆你正在执行的二进制文件。您可以用它做任何您想做的事情,包括违反代码提供的契约

你可以:

  • 运行打破番石榴的自定义JVM
  • 运行一个单独的应用程序,操纵JVM的物理内存,并打破番石榴
  • 操纵番石榴字节码并打破番石榴
  • 配置类加载器以加载其他字节码并中断Guava
  • 致电
    sun.misc.Unsafe
    打破番石榴
  • 用倒影打破番石榴
  • 滥用包装层次结构,破坏番石榴
仅举几个例子。这些都不是新奇的,也不是。这是你的机器,你应该能做这些事情。当然,你能做的和你应该做的是完全不同的事情。以任何一种方式滥用Java都会给您和任何试图运行您的代码的人带来问题

Java类型系统(包括可见性限制)不是警官。它的存在是为了帮助您编写易于使用的高质量代码。Guava利用Java的文档化行为为您提供了更多的工具,使您能够编写更易于使用的高质量代码。如果你选择破坏这些工具,那是你的特权。但不要期望有人愿意使用您的代码


为了具体解决这一点:

可以在
com.google.common.collect
包中创建
ImmutableList
的子类,该子类是可变的(因为它的构造函数不是私有的,而是包私有的)


无需做很多工作,您就可以以大致相同的方式将私有构造函数类操纵为可变类。正如我上面提到的,滥用包层次结构只是您不应该做的许多事情之一。如果您不相信第三方代码在这方面表现良好,您就不应该使用它。

如果没有可见的父类构造函数,子类将如何调用父类构造函数?即使构造函数是私有的,也可以使用反射来修改列表。不变性不是一种安全机制。这是一种OO设计方法,可以避免射中自己的脚。当然,通过使用反射或在你不拥有的包中定义某个不应该存在的东西的子类来打破所有规则,是一种肯定的打击你自己的方法。只是不要这样做。@JBNizet:这一点很好,但通过反射访问字段可能会被SecurityManager阻止。另一方面,包也可以用密封来保护,但它不是密封的。我提到的问题可以通过期末考试或密封包来防止,所以,我不明白为什么不这样做?两件事:首先,这非常接近。然后,关于“final”,看看ImmutableList。它是抽象的。这意味着它将被划分为子类。任何人都不能。这允许根据列表包含的元素数量进行一些优化。例如,存在大小为零的情况:对于这种情况,始终返回相同的列表。另一种情况是大小为1的情况:可以使用单例列表(但显然效率低下)。剩下的是一个更常规的ImmutableList实现。当然,我可以通过声明另一个,比如说doAdd(E element)方法,将元素添加到list-1中,从而使它变为可变的。只需覆盖其他方法,如
get(int)
,就可以让列表看起来被更改(假设
get
在每次访问时都返回一个随机对象)。@Phillipp,在这种情况下,列表实际上并没有被修改。用你自己的话来说,它看起来只是被修改了。但是,是的,这可能是一种模糊但可能会破坏合同的方式。@Katona,你的观点是当你收到一个不可变列表时(在问题中)。这意味着您收到的是一个ImmutableList,而不是一个具有doAdd方法的MutableChildOfImmutableList。除非您知道它是一个MutableChildOfImmutableList,否则doAdd方法是不可访问的。在这种情况下,作为接收者,我不能向它添加任何内容。@ogregoire我的观点是,接收ImmutableList没有意义,因为并不能保证实际实现是不可变的,您必须创建您的防御副本。我在问题中指出的是在任何符合标准的JVM(而不是我的JVM)上创建可变的
不可变列表
)的一种完全有效的方法。这为什么有趣?假设您创建了一个接受
不可变列表的java库ist
并假设它确实是不可变的,但它可能不是,所以你的库可能会被破坏。但我猜,guava密封了包,所以问题不再存在。只有当你运行恶意破坏代码不变量的代码时,你的库才会被破坏。这同样是真的,不仅仅是guava或Java,但是任何语言和环境。除了非常受限的沙盒环境之外,不可能阻止您描述的行为。任何“不可变”代码都可以通过恶意变得可变。我还将提到上面的一些方法