【问题标题】:guava's ImmutableList is not really immutable番石榴的 ImmutableList 并不是真正不可变的
【发布时间】:2013-07-30 09:03:36
【问题描述】:

ImmutableListdocumentation says

虽然这个类不是最终类,但它不能被子类化,因为它没有公共或受保护的构造函数。

我知道这有点牵强,但是可以在com.google.common.collect 包中创建ImmutableList 的子类(因为它的构造函数不是私有的,而是包私有的),它是可变的。从那时起,没有人得到对ImmutableList 的引用可以确定它确实是不可变的。这不破坏ImmutableList的目的吗?

【问题讨论】:

  • 如果没有可见的子类如何调用父类构造函数?
  • 即使构造函数是私有的,也可以使用反射改变列表。不变性不是一种安全机制。这是一种 OO 设计方式,可以避免在脚上开枪。当然,通过使用反射或定义不应该包含在您不拥有的包中的某些东西的子类来打破所有规则,是一种肯定的方式来打自己的脚。只是不要这样做。
  • @JBNizet:很好,但是 SecurityManagers 可以阻止通过反射访问字段。另一方面,包裹也可以密封保护,但不是密封的。
  • 我提到的问题可以通过将类设为final或密封包来防止,所以,我不明白为什么不这样做?
  • 两件事:首先这非常接近code.google.com/p/guava-libraries/issues/detail?id=1485。那么关于“final”,看一下 ImmutableList。它是抽象的。这意味着它打算被子类化。只是不被任何人。这允许基于列表包含的元素数量进行一些优化。例如,存在大小为零的情况:对于这种情况,总是返回相同的列表。另一种情况是大小为 1:单例列表可用(但显然效率低下)。其余的是更常规的 ImmutableList 实现。

标签: java guava


【解决方案1】:

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

你可以:

  • 运行破坏 Guava 的自定义 JVM
  • 运行单独的应用程序并操作 JVM 的物理内存并破坏 Guava
  • 操纵 Guava 字节码并破坏 Guava
  • 配置类加载器以加载其他字节码并破坏 Guava
  • 致电sun.misc.Unsafe 破坏 Guava
  • 使用反射破坏 Guava
  • 滥用包层级,破坏 Guava

仅举几例。这些都不是新奇的,unique to Guava,或unique to Java。这是你的机器,你应该能够做这些事情。当然,你能做什么和你应该做什么是完全不同的事情。以任何这些方式滥用 Java 都会给您和任何其他试图运行您的代码的人带来问题。

Java 类型系统,包括可见性限制,不是警察。它的存在是为了帮助您编写易于使用的高质量代码。 Guava 利用 Java 的记录行为为您提供更多工具,使您能够编写更容易使用的质量更高的代码。如果您选择破坏工具,那是您的特权。但不要指望任何人愿意使用您的代码。


具体说明这一点:

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

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

【讨论】:

  • 我在问题中指出的是在任何符合标准的 JVM 上创建可变 ImmutableList 的完全有效的方法,而不是我的。为什么这很有趣?假设您创建了一个接受 ImmutableList 的 java 库,并假设它确实是不可变的,但它可能不是,所以您的库可能会受到损害。但我猜,番石榴把包裹密封了,所以问题不存在了。
  • 只有当您运行恶意破坏代码不变量的代码时,您的库才会受到威胁。同样,这不仅适用于 Guava 或 Java,而且适用于任何语言和环境。如果没有非常受限的沙箱环境,就不可能阻止您所描述的行为。任何“不可变”的代码都可以通过恶意变得可变。
  • 我还会提到上面的一些可能违反 Java 不变量的方式可以通过 security managers 控制。尽管名称如此,但仍然有必要信任您正在执行的代码,但它们会捕获一些更令人震惊的行为。
【解决方案2】:

即使你扩展 ImmutableList 你也不能让它可变,因为它的所有 mutator 方法都是最终的,就像这个:

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

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

【讨论】:

  • 确定我可以让它可变,通过声明另一个,比如说 doAdd(E element) 方法,它将元素添加到列表中
  • -1 只需要覆盖get(int) 等其他方法,让列表看起来已更改(假设get 在每次访问时返回一个随机对象)。
  • @Phillipp,在这种情况下,列表实际上并没有被修改。用你自己的话来说,它似乎只是被改变了。但是,是的,这将是一种晦涩但可能的违约方式。
  • @Katona,你的意思是当你 receive 一个 ImmutableList (在问题中)。这意味着您收到的是 ImmutableList,而不是具有 doAdd 方法的 MutableChildOfImmutableList。除非您知道它是一个 MutableChildOfImmutableList,否则 doAdd 方法是不可访问的。在这种情况下,作为接收者,我无法向其添加任何内容。
  • @ogregoire 好吧,我的意思是接收一个 ImmutableList 是没有意义的,因为不能保证实际的实现真的是不可变的,你必须创建你的防御副本。
猜你喜欢
  • 1970-01-01
  • 2012-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-24
  • 2012-07-18
相关资源
最近更新 更多