为什么泛型方法和泛型类型具有不同的类型引入语法?

Tim*_*Tim 42 java generics

在研究泛型时,我注意到泛型方法泛型类型(类或接口)之间的类型引入语法的不同使我感到困惑.

泛型方法的语法是

<T> void doStuff(T t) {
    // Do stuff with T
}
Run Code Online (Sandbox Code Playgroud)

文档说

泛型方法的语法包括一个类型参数,在尖括号内,并出现在方法的返回类型之前

泛型类型的语法是

class Stuff<T> {
    // Do stuff with T
    T t;
}
Run Code Online (Sandbox Code Playgroud)

文档说

由尖括号(<>)分隔的类型参数部分跟在类名后面.它指定了类型参数

因为它既没有说明为什么它必须在之前或之后.


为了保持彼此一致,我期望方法语法
void doStuff<T>(T t) {}或类型语法(对于类)class <T>Stuff {},但显然不是这种情况.

为什么之前必须引入一个,另一个之后呢?

我使用泛型主要是以形式出现List<String>并且认为<String>List可能看起来很奇怪,但这是一个主观论证,除了方法之外它也是如此.您可以拨打doStuffthis.<String>doStuff("a string");

寻找技术解释我认为可能<T>必须在指定返回类型之前引入方法,因为T可能返回类型,编译器可能无法像那样向前看,但这听起来很奇怪,因为编译器很聪明.

我认为除了"语言设计师就是这样做"之外,还有一个解释,但我找不到它.

Fra*_*man 17

答案确实存在于已经链接的GJ规范中,引自该文件,第14页:

通过解析约束使得在方法名称之前传递参数的约定是必要的:使用更常规的"方法名称之后的类型参数"约定,表达式f (a<b,c>(d))将具有两个可能的解析.

评论实施:

f(a<b,c>(d))可以解析为f(a < b, c > d) (传递给f的比较中的两个布尔值)f(a<B, C>(d)) (带有类型参数B和C的值调用以及传递给f的值参数d).我认为这也可能是为什么Scala选择使用[]而不是<>用于泛型.

  • 不,只是含糊不清.`f(a <b,c>(d))`可以解析为`f(a <b,c> d)`(来自传递给`f`的比较的两个`boolean`s)或`f(a <B,C>(d))`(调用`a`,类型参数`B`和`C`,值参数`d`传递给`f`).我想这也可能是为什么Scala选择使用`[]`而不是`<>`来表示泛型. (8认同)

Rad*_*anu 10

据我所知,Java中的泛型,当它们被引入时,基于GJ的泛型的概念(支持泛型类型的Java编程语言的扩展).因此语法取自GJ,参见GJ规范.

这是对您的问题的正式回答,但不是GJ背景下您的问题的答案.但很明显它与C++语法无关,因为在C++中,参数部分先于class方法的关键字和返回类型.


dan*_*niu 8

我强烈的假设是,因为正如你所说的方法,泛型参数也可以是函数的返回类型:

public <RETURN_TYPE> RETURN_TYPE getResult();
Run Code Online (Sandbox Code Playgroud)

所以当编译器到达函数的返回类型时,它的类型已经被遇到(例如,它知道它是泛型类型).

如果你有一个像这样的语法

public RETURN_TYPE getResult<RETURN_TYPE>();
Run Code Online (Sandbox Code Playgroud)

它需要第二次扫描才能解析.

对于类,这不是问题,因为对泛型类型的所有引用都出现在类定义块中,即在声明泛型类型之后.


EJo*_*ica 5

对此没有一些深刻的理论原因 - 这似乎是"语言设计者就是这样做的".例如,C#确实使用了你想知道为什么Java没有实现的语法.以下代码:

private T Test<T>(T abc)
{
    throw new NotImplementedException();
}
Run Code Online (Sandbox Code Playgroud)

将编译.C#与Java类似,这意味着Java没有理论上的原因也不能实现同样的东西(特别是考虑到两种语言在开发的早期都实现了泛型).

现在Java语法的优点是使用当前语法为方法实现LL(1)解析器稍微容易一些.