Java: Curious ImmutableList 添加

Java: Curious ImmutableList add

我不明白这个方法的实现。这是

public static <T> List<T> add(List<T> list, T element) {
    final int size = list.size();
    if (size == 0) {
        return ImmutableList.of(element);
    } else if (list instanceof ImmutableList) {
        if (size == 1) {
            final T val = list.get(0);
            list = Lists.newArrayList();
            list.add(val);
        } else {
            list = Lists.newArrayList(list);
        }
    }
    list.add(element);
    return list;
}

为什么不直截了当list.add(element)

代码正在执行添加到给定列表的操作。如果输入列表是 ImmutableList,它首先创建一个可变列表(否则无法添加)并将元素复制到其中。如果不是,它只使用现有列表。

有点奇怪,如果传入的列表是空的,它 returns 一个 ImmutableList,但是如果给它一个非空的 [=10],它是一个(可变的)ArrayList =] 添加到,但也许在更广泛的上下文中使用它以及如何使用它是有意义的。但这种不一致肯定是我在代码审查中要查询的问题。

ImmutableList

的补充

Why not a strightforward list.add(element)?

如果给定列表不可变,则无法调用该方法。实际上你可以,但通常这样的方法会抛出一个 UnsupportedOperationException。番石榴 ImmutableList#adddocumentation

Deprecated. Unsupported operation.
Guaranteed to throw an exception and leave the list unmodified.

然而,该方法的目标似乎也是通过创建 可变克隆 支持对 ImmutableList 添加 。因此 直接 实施将是:

public static <T> List<T> add(List<T> list, T element) {
    if (list instanceof ImmutableList) {
        // Create mutable clone, ArrayList is mutable
        list = Lists.newArrayList(list);
    }
    list.add(element);
    return list;
}

其他内容

请注意,类型可能会发生变化。虽然输入可能是 ImmutableList,但输出肯定 不是

您可以通过创建一个临时克隆来保留类型,添加到它(如图所示)然后再次环绕一些 ImmutableList。然而,这似乎不是这种方法的目标。

另请注意,在相同情况下,该方法可能会向给定列表添加一些内容,而在某些情况下会创建一个新实例。所以方法的调用者必须知道方法有时会改变他的参数,有时不会。对我来说,这是一个非常奇怪的行为,它绝对必须在文档中突出显示,但我不建议这样做。

似乎该方法的另一个目标是保持列表不可变,如果它在方法调用时为。这有点奇怪,但可能在其 文档 中突出显示。因此他们添加了这个调用:

if (size == 0) {
    return ImmutableList.of(element);
}

除此之外,他们通过调用

做一些小事
Lists.newArrayList();

而不是

Lists.newArrayList(list);

如果 list 当前大小为 1。但是我不确定他们为什么要执行此步骤。在我看来,他们可以保持原样。


所以总而言之,我可能会实现这样的方法

/**
 * Creates a new list with the contents of the given list
 * and the given element added to the end.
 *
 * <T> The type of the lists elements
 *
 * @params list The list to use elements of, the list will not be changed
 * @params element The element to add to the end of the resulting list
 *
 * @return A new list with the contents of the given list and
 *   the given element added to the end. If the given list was
 *   of type {@link ImmutableList} the resulting list will
 *   also be of type {@link ImmutableList}.
**/
public static <T> List<T> add(List<T> list, T element) {
    List<T> result;

    // Create a Stream of all elements for the result
    Stream<T> elements = Stream.concat(list.stream(), Stream.of(element));

    // If the list was immutable, make the result also immutable
    if (list instanceof ImmutableList) {
        result = ImmutableList.of(elements.toArray(T[]::new));
    } else {
        result = elements.collect(Collectors.toList());
    }

    return result;
}

这样一来,您将永远不会更改参数 list,并且如果是的话,您还将保留列表 ImmutableList。使用 Stream#concat 方法可以使这里的事情更高效(这是一种惰性方法),否则我们需要在两者之间创建临时克隆。

但是我们不知道你的方法有哪些目标,所以可能在你的特定方法的上下文中它做了什么更有意义.

此方法不是"a straightforward list.add(element)"的原因是因为此方法旨在能够向ImmutableList添加元素。很明显,它们是不可变的(如果你看的话,它们的原生 add 方法 throws an UnsupportedOperationException)因此对它们 "add" 的唯一方法是创建一个新列表。

新 returned 列表现在是可变的这一事实是一个奇怪的设计决定,只有更广泛的上下文或代码作者的输入才能帮助解决这个问题。

空输入列表 return 不可变列表的特殊情况是另一个奇怪的设计决定。如果没有该条件分支,该函数将正常工作。


因为这个方法return是列表的一个副本,你应该小心地将结果分配给一些东西,可能是原始变量:

myList = TheClass.add(myList, newElement);

并注意以下用法实际上什么都不做:

TheClass.add(myList, newElement);

此方法是一种反模式,不应使用。它改变了可变和不可变的数据结构,提供了两种实现中最糟糕的一种。

  • 如果您使用的是不可变数据结构,您应该在类型中明确说明 - 强制转换为 List 会失去重要的上下文。请参阅 "Interfaces" 而不是 ImmutableCollection 的实现 部分。
  • 如果您使用的是可变数据,则应避免进行线性时间复制,而应(谨慎)利用数据结构的可变性。

互换使用这两种类型通常没有意义 - 如果您想向现有集合中添加内容,请使用您拥有的可变集合。如果您希望集合不可变,请不要尝试向其中添加内容。此方法放弃了该意图,将导致运行时错误 and/or 降低性能。