在返回参数

时间:2016-01-06 19:42:13

标签: java generics bounded-wildcard

通常不鼓励在Java中的返回参数中使用通用通配符类型。例如,Effective Java,Item 28声明:

  

不要将通配符类型用作返回类型。不会为用户提供额外的灵活性,而是强制他们在客户端代码中使用通配符类型。

  正确使用的通配符类型对于类的用户几乎是不可见的。它们使方法接受它们应该接受的参数并拒绝它们应该拒绝的参数。 如果一个类的用户必须考虑通配符类型,那么该类的API可能有问题。

但在某些情况下,它似乎是最佳选择,例如下面的代码:

import java.util.*;
import java.lang.*;
import java.io.*;

class Container
{
    private final Map<String, Type<?>> itemMap = new HashMap<>();

    public <T> void addItem(String key, T t) {
        itemMap.put(key, new Type<>(t));
    }

    public Collection<Type<?>> getAllItems() {
        return itemMap.values();
    }

    public static class Type<T> {
        private final T value;
        public Type(T value) {
            this.value = value;
        }
        @Override
        public String toString() {
            return "Type(" + value + ")";
        }
    }

    public static void main (String[] args)
    {
        Container c = new Container();
        c.addItem("double", 1.0);
        c.addItem("string", "abc");
        System.out.println(c.getAllItems());
    }
}

这个例子当然是简化的,我们假设Type<T>有一个真正的理由是通用的。

设计需要一个返回所有项目集合(getAllItems())的方法。存储在itemMap中的项可以具有不同的类型 - 因此类型参数T不能移动到Container类声明。出于同样的原因,我们无法返回Collection<Type<T>>,因为这意味着所有项目都具有相同的类型T,而不是。根据Effective Java,这个方法或者这个类API有问题,在我看来这两者似乎都不对。

我知道之前已经问过类似的问题(例如Generic wildcard types should not be used in return parameters),但是他们要么专注于具体的例子,这与我的略有不同,要么回答围绕着它没有意义的事实当可以使用有界类型参数时,返回形式为Collection<? extends ...>的集合。这不适用于上述用例(我对其他用户不感兴趣,因为我可以看到它们如何被解决)。

问题是这样 - 如果有效Java是正确的(其他来源,例如SonarQube规则和网络上的各种文章),在上面的代码中返回Collection<Type<?>>的替代方法是什么,或者什么是类Container的设计有错吗?

更新:

稍微修改/扩展的示例,以解决评论中提出的一些问题(这更接近我正在使用的实际代码):

import java.util.*;
import java.lang.*;
import java.io.*;

class Type<T> {
    private T value;
    private Class<T> clazz;

    public Type(T value, Class<T> clazz) {
        this.value = value;
        this.clazz = clazz;
    }
    T getValue() {
        return value;
    }
    void setValue(T value) {
        this.value = value;
    }
    Class<T> getItemClass() {
        return clazz;
    }
}

abstract class BaseContainer
{
    protected abstract Collection<Type<?>> getItems();
    // ... other methods
}

class Client {

    public void processItems(BaseContainer container) {
        Collection<Type<?>> items = container.getItems();
        for (Type<?> item: items) {
            processItem(item);
        }
    }

    public <T> void processItem(Type<T> item) {
        T currentValue = item.getValue();
        Class<T> clazz = item.getItemClass();
        T newValue = null;
        // ... calculate new value
        item.setValue(newValue);
    }
}

如果BaseContainer.getItems()返回类型被声明为Collection<Type<Object>>,我们为“流氓”客户端提供了一个漏洞,将无效类型的值放入Type的实例中。出于同样的原因,List<Object>List<Integer>不同 - 很容易看出返回的前者而不是后者将允许任何人将String作为整数列表放入{{1 } 但是List<Integer>)。

按照@isi建议的方法,我们可以使Type实现一些接口(非泛型),并在List<? extends Object>的返回类型中使用它。我可以看到它是如何工作的。但是在真正的代码中,这将意味着一个全新的界面模仿大多数没有类型参数的泛型类型的方法,这感觉有点笨拙。它看起来像是一个基本上用{T =对象写下getItems的接口,它接近代码重复。在某种程度上,我将Type<T>视为避免此工作和额外界面的便捷速记。

3 个答案:

答案 0 :(得分:3)

如果您真的打算存储绝对任何类型的对象,请使用Object。当您特别不想检查类型时,无需添加类型检查糖。

答案 1 :(得分:2)

我认为Type类默默提供两个接口 * 。一个用于关注泛型类型T的用户,另一个用于不关心泛型类型的用户。我会尝试用一些代码使它更清晰:

interface TypeWithoutT { Object getValueObj(); } // exposes untyped api

class Type<T> implements TypeWithoutT { // exposes several apis
    private final T value;

    // exposes typed part of the api (can be on a separate interface)
    public T getValue() { return value; }
    public Type(T value) { this.value = value; }

    // overrides commonly available apis
    @Override
    public String toString() { return "Type(" + value + ")"; }

    // exposes untyped part of the api and Object's api
    public Object getValueObj() { return value; }
}

Type<T>类型接口可以将更多类型特定的操作公开为对手无类型接口 TypeWithoutTtoString方法在每个实例上都可用,因此它是Object实例的常用接口的一部分。除了Object提供的操作之外,还有其他有用的操作,应该向用户公开,这是api的无类型部分。

改写

现在的问题是:用户可以使用一堆混合Type实例做什么?他们对Type个实例的混合集合中的每个实例有什么期望?

Object是否足够?

对于Type<T>的任何可能选择,他们可能只期望所有T个实例共有的同一组操作。与toString中的Object方法类似。如果这是所有API用户需要了解的有关使用Type个实例的集合的信息,那么在Collection<Type<Object>>方法上使用getAllItems作为返回类型就足够了。

还是不?

如果API用户期望的内容远远超过Object提供的界面,则可以使用上述方法返回更专业的集合。在这种情况下,getAllItems方法的返回类型可以更改为Collection<TypeWithoutT>

通过这种方式,可以向两个用户明确公开一个有用的API - 关心类型的人和不关心类型的人。

然而

我会坚持使用最佳做法而不返回通配符类型,除非我有充分的理由。其中一个给定的解决方案应该做 - 我猜。

interface * :我的意思是一般意义上的接口而不是显式的java接口。因此,有合同和一系列操作的东西。

答案 2 :(得分:1)

  

但在某些情况下,它似乎是最佳选择,   例如在下面的代码中:

但那不是&#34;通配符类型&#34;。 A&#34;通配符类型&#34;可能类似于Foo<?>Foo<? extends Something>Foo<? super Something>,其中类型参数是(可能是有界的)?。您可以将Foo个不同类型的参数分配给Foo<?>通配符类型的变量。通配符类型提供了一种多态性。

您在谈论类型Collection<Type<?>>。在这种情况下,类型参数是Type<?>,这是一种特定类型。这是非常不同的。那里有一个?,但它处于更深层次。使用类似Collection<Type<?>>的内容,您无法将Collection个不同类型的参数分配给Collection<Type<?>>类型的变量。 type参数固定为Type<?>;没有别的兼容。