【问题标题】:How to avoid generics warnings when using "raw types" as static field / return type?使用“原始类型”作为静态字段/返回类型时如何避免泛型警告?
【发布时间】:2023-03-31 17:42:01
【问题描述】:

这基本上是昨天question 的后续。我试图实现“最通用的方式”(我能想到的)以构建“真正的”不可修改的集合。下面的代码工作正常;但它需要抑制警告(“rawtypes”和“unchecked”)。

我尝试了各种方法来避免这些警告;但根本找不到替代方案。所以,我的问题是:有没有一种干净的方法来避免我正在压制的警告?

(我看到了另一个question;但我不确定它是否真的适用于我的代码;因为我不确定是否可以在不使用原始类型的情况下写下“生成器”接口)。

private interface Generator<T> {
    T generateNewInstance();
}

@SuppressWarnings("rawtypes")
private final static Generator ListGenerator = new Generator<List>() {
    @Override
    public List generateNewInstance() {
        return new ArrayList();
    }
};

public static <T> List<T> unmodifiableListBasedOnCloneOf(List<T> elements) {
    @SuppressWarnings("unchecked")
    List<T> newInstance = makeNewInstanceOf(elements.getClass(), ListGenerator);
    newInstance.addAll(elements);
    return Collections.unmodifiableList(newInstance);
}

private static <T> T makeNewInstanceOf(Class<T> incomingClass, Generator<T> fallbackGenerator) {
    try {
        return (T) incomingClass.newInstance();
    } catch (IllegalAccessException | InstantiationException e) {
        return fallbackGenerator.generateNewInstance();
    }
}

【问题讨论】:

  • 我真的不明白您在这里要做什么:如果列表包含在 unmodifiableList 中,那么底层列表的类型有什么关系?为什么Collections.unmodifiableList(new ArrayList&lt;&gt;(elements)) 不够用?
  • 请阅读我的另一个问题。关键是:它不是。该 Collections 方法仅将 decorator 添加到现有列表中。如果您通过未修饰的界面访问该“初始列表”,则可以更改该列表;然后你所谓的不可修改的列表在封面下发生了变化。
  • Collections.unmodifiableCollection(new ArrayList&lt;&gt;(elements)) 不是elements 的视图:它将elements 复制到一个新列表中(就像您在此处所做的那样),并且该新列表的引用无法访问unmodifiableCollection以外的任何东西。
  • 我强烈建议您使用现有的解决方案。例如,Guava 有一些很棒的不可变集合,它们不仅比这些基于装饰器的方法性能更高,而且还可以避免不必要的复制。 Guava 的不可变集合的主要警告是它们不支持空值。
  • 正如我在这个问题中提到的另一个问题中所写的那样:番石榴不是一种选择。

标签: java generics unchecked raw-types


【解决方案1】:

如果您将List&lt;T&gt; 复制到一个新的ArrayList&lt;T&gt; 中,并将该引用内联传递给Collections.unmodifiableList,那么除了不可修改的装饰器之外,没有任何东西可以访问该副本来修改它:

    List<String> original = new ArrayList<>(Arrays.asList("Hello", "World"));

    // This simply decorates original, so updates to original are
    // reflected in this list.
    List<String> unmodifiableDecorator = Collections.unmodifiableList(original);

    // The reference to the new ArrayList is scoped to the call to
    // Collections.unmodifiableList, so nothing else can have a
    // reference to it.
    List<String> unmodifiableCopy =
        Collections.unmodifiableList(new ArrayList<>(original));

    System.out.println("Before clear:");
    System.out.println(original);
    System.out.println(unmodifiableDecorator);
    System.out.println(unmodifiableCopy);

    original.clear();

    System.out.println("After clear:");
    System.out.println(original);
    System.out.println(unmodifiableDecorator);
    System.out.println(unmodifiableCopy);

输出:

Before clear:
[Hello, World]
[Hello, World]
[Hello, World]
After clear:
[]
[]
[Hello, World]

所以unmodifiableCopy 没有被修改,也不能被修改,除非你能以某种方式说服unmodifiableList 给你代表参考。

由于您无法修改它,因此您不需要将副本设置为与输入列表相同的具体列表类型。只需将其设为ArrayList,因为它具有几乎可以通过List 实现实现的最佳读取性能。

所以你的方法,没有警告或额外的类,可以是:

public static <T> List<T> unmodifiableListBasedOnCloneOf(List<T> elements) {
  return Collections.unmodifiableList(new ArrayList<>(elements));
}

【讨论】:

  • 我实现的重点是:如果那个“后备列表”不是 ArrayList 怎么办?但也许是 LinkedList?我实现的重点是仅使用“ArrayList”作为后备,当代码无法创建与即将进入的列表具有相同性质的列表时...
  • 链表支持它有什么好处?不可修改的列表只能使用List上定义的方法; LinkedList 唯一具有优势的方法是中间列表插入或删除,它们没有被使用。与 ArrayList 相比,所有只读方法的性能都相同或更差。
  • 嗯,这个例子是关于列表的;但我想为 Collections 中的所有 5 种方法提供“真正不可修改”的包装器。那里也是一样的。并且要非常清楚这一点:我认为包含与问题基本相同的代码的答案并不是真正的答案。我知道我可以使用 ArrayList 制作副本;但这不是我要求的。
  • 答案在所有 (6) 种情况下都是相同的:为每个方法使用具有最佳读取性能并保留插入顺序的实现。
【解决方案2】:

避免@SuppressWarnings 注释并不总是可能的。我尝试将其范围限制为局部变量。

我想出了这个:

private interface Generator<T> {

    T generateNewInstance();
}

private static class ListGenerator<T> implements Generator<List<T>> {
    @Override
    public List<T> generateNewInstance() {
        return new ArrayList<>();
    }
};

public static <T> List<T> unmodifiableListBasedOnCloneOf(List<T> elements) {
    @SuppressWarnings("unchecked")
    Class<List<T>> clazz = (Class<List<T>>)elements.getClass();
    List<T> newInstance = makeNewInstanceOf(clazz , new ListGenerator<T>());
    newInstance.addAll(elements);
    return Collections.unmodifiableList(newInstance);
}

private static <T> T makeNewInstanceOf(Class<T> incomingClass, Generator<T> fallbackGenerator) {
    try {
        return (T) incomingClass.newInstance();
    } catch (IllegalAccessException | InstantiationException e) {
        return fallbackGenerator.generateNewInstance();
    }
}

【讨论】:

  • 我明白了。这节省了其中一个警告;以单个“新”语句的微妙价格(可能那是过早的优化;但我故意提出了一个解决方案,该解决方案试图最小化在没有抛出异常时为“好”情况发生的“活动”)。
  • incomingClass.newInstance() 可以绕过编译器对检查异常的支持。如果您调用incomingClass.getConstructor().newInstance(),它将强制您捕获InvocationTargetException,它将包装构造函数中抛出的异常,允许您处理它们或使用您认为合适的fallbackGenerator
猜你喜欢
  • 2012-03-27
  • 1970-01-01
  • 2019-12-18
  • 2022-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-05
相关资源
最近更新 更多