Java泛型方法参数类型括号内和括号外的区别

22

这两者有什么区别:

static void findMax(LinkedList<? extends Number> list){...}

并且:

static <T extends Number> void findMax(LinkedList<T> list){...}

由于两者都可以使用,我想知道它们之间是否有任何重大区别,并且哪一个更受推荐。


4
如果你的方法不是空的,那么更容易发现它们之间的区别:static <T extends Number> T findMax(List<T> list){ return list.get(0)} - Balint Bako
5个回答

14
主要区别在于第二个版本中可以访问类型T,而在第一个版本中无法访问。
例如,您可能希望返回与T相关联的内容(例如返回T而不是void):
static <T extends Number> T findMax(LinkedList<T> list){...}

或许你需要创建一个新的T列表:

static <T extends Number> void findMax(LinkedList<T> list){
    List<T> copyAsArrayList = new ArrayList<> (list);
    //do something with the copy
}

如果您不需要访问T,两个版本在功能上是等效的。


1
Darijan提出了第二个功能上的不同之处。如果您不需要访问T,它们在功能上是不等效的。 - emory
完全不同,功能上根本不等价。 - darijan
@darijan 我并没有说它们是等价的。只有当“*你不需要访问T*”时,它们才是等价的。特别是当你写list.add(someT);时,你需要访问T(对于List<E>,签名是add(E e)),在这种情况下使用? extends Number是行不通的。如果你只需要检查列表的大小(例如),你不需要访问T,两个版本都是功能上等价的。请重新阅读我的答案。 - assylias
这是非常明显的,以至于没有成千上万的问题在问:“为什么我尝试向ArrayList <? extends Something>添加值时会得到编译器错误?” - darijan
1
在第二个版本中,您可以访问类型T,而在第一个版本中则不行。但这是方法内部的实现细节。对外部来说,没有区别。 - newacct

10

第一种情况适用于您只关心findMax接受符合某些条件的列表(在您的情况下,是扩展Number类型的列表)。

static Number findMax(LinkedList<? extends Number> list) {
    Number max = list.get(0);
    for (Number number : list) {
        if (number > max) {
            max = number;
        }
    }
    return max;
}

此方法返回Number。例如,如果您有一个自己扩展了Number并具有一些特殊方法的类,并且希望稍后在此方法的结果上使用,则可能会出现问题。


第二个应该在您计划在方法体中使用精确类型T,作为方法参数或方法返回类型时使用。

static <T extends Number> T findMax(LinkedList<T> list, T currentMax) {
    T max = currentMax;
    for (T number : list) {
        if (number > max) {
            max = number;
        }
    }
    return max;
}

结论:

从功能上来看,它们几乎是等同的,唯一的区别是,具有未知类型 (?) 的列表不能被修改。


你的最后一句话说得非常好。OP 应该用 findMax(LinkedList<? extends T> list) 替换 findMax(LinkedList<T> list) - emory
更准确地说,正如Evgeniy所指出的那样,您无法向列表中添加内容。但是您仍然可以通过删除元素来修改它。 - assylias
@assylias:你仍然可以添加到列表中。你只需要将它传递给另一个方法来完成。 - newacct

4
这里是不同之处。
static void findMax(LinkedList<? extends Number> list){
    list.add(list.get(0));  <-- compile error
}

您只能向列表中添加null,不能添加其他内容。

与此同时,这样编译不会出现任何错误或警告。

static <T extends Number> void findMax2(LinkedList<T> list){
    list.add(list.get(0));  <-- no error
}

1
这是有任何逻辑解释吗?我真的无法理解Java的编译错误信息: 类型LinkedList<capture#1-of ? extends Number>中的方法add(capture#1-of ? extends Number)不适用于参数(capture#2-of ? extends Number)。 - Paz
1
根据通用规则,List<? extends Number> 表示实际的 List 类型可以是 Number 的任何子类,例如 List<Integer> 或 List<Double>,因此 javac 不允许向这样的列表中添加任何内容,因为实际类型是未知的。 - Evgeniy Dorofeev

2
就签名的能力而言,两者完全相同。我的意思是如果您有使用第二个签名的任何API,则可以将其替换为使用第一个签名的API,并且使用该API的任何代码都将像以前一样正常工作,而以前无法工作的任何代码也不会起作用。因此,对于外部代码来说,两者没有区别。
如何将第二个实现更改为第一个实现?其他人已经指出了使用list.add(list.get(0));的示例。第一个签名的API能执行这个操作吗?可以。非常简单。只需让第一个调用第二个(将第二个变成内部私有方法)。这被称为“捕获助手”。事实上,您可以通过这样做证明两者可以从外部代码的角度“做”相同的事情(“做”的方式包括调用其他方法等内部手段)。
static void findMax(LinkedList<? extends Number> list){
    findMaxPrivate(list);
}
static static <T extends Number> void findMaxPrivate(LinkedList<T> list){
    list.add(list.get(0));
}

0

我认为关于在第二个方法体中访问参数T的问题已经有了很好的答案。

我想补充说,如果您有许多需要按类型链接的参数,则需要使用第二种符号。例如:

static <T extends Number> int getPosition(LinkedList<T> list, T element){...}

如果不使用一个通用类型参数<T>,您将无法强制上述约束条件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接