【问题标题】:Generic wildcard types should not be used in return parameters不应在返回参数中使用通用通配符类型
【发布时间】:2023-03-15 16:01:02
【问题描述】:

在方法的返回参数中不应该使用泛型通配符类型是否可行?

换句话说,声明如下接口确实有意义:

interface Foo<T> {
  Collection<? extends T> next();
}

此外,是否可以说通用通配符类型仅在方法的参数声明中才有意义?

【问题讨论】:

标签: java generics


【解决方案1】:

https://rules.sonarsource.com/java/RSPEC-1452

强烈建议不要使用通配符类型作为返回类型。 因为类型推断规则相当复杂,所以不太可能 该 API 的用户将知道如何正确使用它。让我们采取 返回“列表”的方法示例。可能吗 在这个列表上添加一只狗,一只猫,......我们根本不知道。和 编译器也没有,这就是为什么它不允许这样的 直接使用。通配符类型的使用应仅限于方法 参数。

当方法返回通配符类型时,此规则会引发问题。

不合规代码示例

  List<? extends Animal> getAnimals(){...}  

合规解决方案

  List<Animal> getAnimals(){...}    or

  List<Dog> getAnimals(){...}

【讨论】:

  • 在不提及来源的情况下引用来源被认为是不礼貌的。所以,老实说,你从Sonar Rule 1452复制了这个解释。
  • @JacobvanLingen 感谢您提醒我。我已经更新了源代码。 :)
【解决方案2】:

在方法形式参数中使用通配符类型的主要好处是为用户提供了传递的灵活性,比如CollectionList 或任何实现 Collection 的类型(假设集合是声明为Collection&lt;?&gt;)。您经常会发现自己在形式参数中使用通配符类型。

但理想情况下,您应该避免将它们用作方法的返回类型。因为那样,您将强制该方法的用户在调用者端使用通配符类型,即使他们不想这样做。通过使用通配符类型,您就是在说,嘿!这个方法可以返回任何类型的Collection,所以你的工作就是照顾它。你不应该那样做。最好使用有界类型参数。使用有界类型参数,将根据您传递的类型或方法调用的目标类型来推断类型。

这是来自Effective Java Item 28 的引述:

不要使用通配符类型作为返回类型。而不是提供 为您的用户提供额外的灵活性,这将迫使他们使用 客户端代码中的通配符类型。
正确使用,通配符类型是 类的用户几乎看不见。它们导致方法接受 他们应该接受和拒绝他们应该拒绝的参数。 如果 类的用户必须考虑通配符类型,有 类的 API 可能有问题。

【讨论】:

  • 所以,据我了解 - 如果我声明返回类型而不使用通配符,我不会失去任何灵活性,它只会使接口的使用更加困难?否则,我能想到的唯一优点是它将“锁定”返回的集合,并使其成为不可变的,因为我们不允许将任意元素添加到使用通配符引用的集合中。
  • @jilt3d 不可变是完全不同的东西。使用有界类型参数宁愿提供类型安全。您仍然可以添加与 Collection 类型兼容的元素。
  • @jilt3d 关于“不变性”(我在回答的编辑中也提到过):关于向集合添加新元素的可能性,这是正确的。但总的来说是正确的:例如,您仍然可以添加null,或者只是在返回的集合上调用collection.clear()
  • 同意“不可模仿性”。您能否编辑您的第一段以便我接受答案 - 当您使用Collection&lt;T&gt; 作为形式参数并假设TString 时,您仍然可以通过List&lt;String&gt;。在这种情况下使用通配符的主要好处是我可以传递像List&lt;MyString&gt; 这样的集合(如果我们假设MyString extends String)。
  • @jilt3d 是的。顺便说一句,String 是最后一课。它不能扩展:)
【解决方案3】:

不,这样说是不可行的。

或者这样说:拥有这样的界面确实有意义。

想象一下

interface Foo<T>  
{
    Collection<? extends T> next();
}

class FooInteger implements Foo<Number> 
{
    private final List<Integer> integers = new ArrayList<Integer>();
    void useInternally()
    {
        integers.add(123);
        Integer i = integers.get(0);
    }

    @Override
    public Collection<? extends Number> next() 
    { 
        return integers;
    }
}

// Using it:
Foo<Number> foo = new FooInteger();
Collection<? extends Number> next = foo.next();
Number n = next.iterator().next();

如果您将返回类型写为Collection&lt;T&gt;,则无法返回包含T 子类型的集合。

是否需要这样的返回类型取决于应用情况。在某些情况下,它可能只是必要。但如果它很容易避免,那么你可以这样做。


编辑:编辑代码以指出差异,即您可能无法始终在内部选择类型。但是,在大多数情况下,可以避免返回涉及通配符 的内容 - 正如我所说,如果可能,应该避免。

上面的例子仍然应该被视为一个例子来强调重点。当然,这样的实现将是一个不好的做法,因为它暴露了内部状态。

在这种情况和类似情况下,通常可以返回类似 a

return Collections.<Number>unmodifiableList(integers);

由此,将返回类型声明为Colletion&lt;Number&gt;unmodifiableList 方法解决了暴露内部状态的问题,并且具有允许将类型参数更改为超类型的简洁属性,因为列表是然后......好吧,无论如何都无法修改。

【讨论】:

  • 实际上,即使我将返回类型声明为 Collection,我也可以返回包含 T 子类型元素的集合:final Collection&lt;Number&gt; arrayList = new ArrayList&lt;Number&gt;(); arrayList.add(new Integer(0));
  • @jilt3d 添加了解释作为编辑
  • 查看编辑后的示例,我仍然认为没有理由使用通配符声明返回类型?
  • @jilt3d 该集合在内部声明为List&lt;Integer&gt;。如果返回类型必须为Collection&lt;Number&gt;,则无法从此方法返回它。当retun类型为Collection&lt;? extends Number&gt;时,也可以返回任意Collection&lt;Integer&gt;。 (再次重申:这只是一个人为的例子,但我认为它表明在某些情况下这种返回类型是有意义的——无论是否应该为了简洁而避免它)
  • 好吧,您可以将内部集合声明为private final List&lt;Number&gt; integers = new ArrayList&lt;Number&gt;();,您仍然可以添加Integer 实例。是的,这一次当你调用 get() 时你会得到一个 Number(而不是一个 Integer),但在现实世界的场景中,这还是更可取的。
猜你喜欢
  • 2016-04-11
  • 1970-01-01
  • 1970-01-01
  • 2019-03-29
  • 1970-01-01
  • 1970-01-01
  • 2015-08-30
  • 1970-01-01
相关资源
最近更新 更多