This Java tutorial on generics表示:
通配符还有一个优点,那就是可以在方法签名之外使用,比如字段、局部变量和数组的类型。
回到我们的形状绘制问题,假设我们想要保存绘制请求的历史,我们可以在类Shape中的一个静态变量中保存历史,并让drawAll()将传入的参数存储到历史字段中。
static List<List<? extends Shape>> \\ 1
history = new ArrayList<List<? extends Shape>>(); \\ 2
public void drawAll(List<? extends Shape> shapes) { \\ 3
history.addLast(shapes); \\ 4
for (Shape s: shapes) { \\ 5
s.draw(this); \\ 6
}
}
(我在代码片段中添加了行号。)
有三个问题困扰着我的代码片段。
1.在第2行中,通配符用于示例化一个对象。对象的类型是什么?在本教程到目前为止提供的所有示例中,通配符仅用作形式类型参数,而不是实际类型参数。实际类型参数始终是具体类,您可以在某些源文件中读取其类定义。
1.在第4行中,history
字段通过它的add
方法被修改,但是a previous page of the same tutorial声明通过用上限通配符类型声明的变量修改对象是非法的,例如List<? extends Shape> shapes
。
1.在静态变量声明的第1行中使用通配符似乎与a later page of the same tutorial中的以下词语不一致:
这就是为什么在静态方法或初始值设定项中,或者在静态变量的声明或初始值设定项中引用类型声明的类型参数是非法的。
2条答案
按热度按时间mwngjboj1#
ArrayList
。这里的泛型 * 没有 * 用于"创建一个新的?
"--这是不可能的。你只是在这里创建一个新的数组列表,然后这个数组列表被参数化--它只包含类型签名与List<? extends Shape>
匹配的东西,因为它是在外部<>
里面的。创建一个新的数组列表并不会立即用一堆数据来填充它。一个数组列表被创建为空,因此,不需要在外部<>
中创建任何类型的东西。一些代码可能会在稍后向这个数组列表添加一些列表。如果它是List<Shape>
,如果它是List<Rectangle>
,那也没问题--两者都与List<? extends Shape>
签名兼容。在第4行中,通过add方法修改了history字段。
这是一种令人困惑的、近乎不正确的说法。
history
字段根本没有修改;要修改它,你必须写history = something
。不,history
字段引用了某个对象(它就像地址簿中的一个页面,而不是一个房子)-history.add
将取消引用这个引用(通过转到地址走到实际的房子-我们根本没有修改地址簿),然后我们更改房子。声明通过变量修改对象是非法的
这是一个过于简单化的方法,并不是在所有情况下都成立,这里是成立的,但是,技巧是,这个数组列表**不是通过
? extends
声明的,请看下面的例子:它被声明为List<List<X>>
.组件类型(这个列表将要包含的对象的类型)根本没有extends
.当然,X
* 有 *,但是那是更深的一层,而且这个过于简化的规则只涉及直接组件(外部<>
内部的东西)。它过于简化了,因为它实际上应用了PECS。本质上,给定:
ArrayList
/List
)声明了一个类型变量E
。? extends
通配符来使用此类型(请注意,只有?
是? extends Object
的缩写,因此在此逻辑中也是extends
通配符)。E
(除了字面上的null
,但这不是很有用)。这是因为实际的
E
,我们在这里不知道,因为你用? extends
绑定声明了它,可能是Shape,或者Square,或者Circle,我们只是不知道,没有java表达式有这样的属性,它可以合法地作为方法参数传递,其中参数类型是我们不知道的,我们所知道的是-它是Square,或者Shape,或者Circle。与
? super Square
绑定相比:这里我们也不知道实际的类型是什么(这里仍然使用?
,所以我们不知道),但是我们知道它是Square
,或者Shape
,或者Object
,并且它不能是其他任何东西。new Square()
在这里是有效的。想象一下这三个方法:new Square()
对任何一个都行,所以对void foo(? super Square foo) {}
也行,那本身不是合法的java,但是void foo(E foo) {}
是,如果E
绑定为? super Square
,现在您知道为什么可以在List<? super TypeOfX>
上调用.add(x)
,而在List<? extends Type>
上根本不能调用.add()
。(除了字面上的null
,它适合所有类型,但这是一个学术上的好奇心,不是一个有用的事情)。关键是,我们不是在泛型类型为
? extends
的List
上调用.add
,而是在泛型类型为List<X>
的List
上调用.add
,其中,当然,X
确实包含extends
,但这不是要查看的相关位置。3.
<>
语法用于both声明变量以及**使用它们。给定:
我们没有使用
x
,我们声明:"现在我将声明一个名为x
的变量,该变量受到以下约束:它将永远只指向空(null
),或者是String
的示例或其子类型的对象(对于字符串来说,它只是'字符串的示例',因为字符串是final
,所以不存在子类型)"。而对于
System.out.println(x)
,我们没有声明一个名为x的新变量,而是使用了它。这同样适用于泛型,但在声明和使用看起来非常相似的意义上,它更容易混淆。
创建类型变量有两种方法:
所有其他地方都是它的 * 用法 *。
类型声明的类型参数
是指
class Foo<T> {}
中的T
。你不能在静态的任何东西中使用 * those *。T
。此语句中没有任何类型变量。<? extends Shape>
不是"类型变量"。它是类型绑定。如上所述,类型变量是class Foo<T>{}
中的T
或<T> returnType methodName() {}
中的T
。按照惯例,它们是单大写字母。dxpyg8gm2#
您在问题中提供的代码段使用通配符作为第2行中List对象的类型参数。创建的对象类型是Shape的未知子类型的List。创建的List对象可以存储Shape的任何子类型,但在编译时不知道确切的类型。
关于修改第4行中的history字段的问题,本教程可能提到这样一个事实,即修改用上限通配符类型声明的变量所引用的List对象的内容是不法律的的。相反,它的一个引用被添加到历史列表中.将一个List〈?extends Shape〉类型的对象的引用添加到List〈?extends Shape〉对象是法律的的.
关于第1行静态变量声明中通配符的使用,在静态方法或初始化器中,或者在静态变量的声明或初始化器中引用类型声明的类型参数的限制适用于封闭类的类型参数的使用。在您提供的示例中,封闭类Shape的类型参数未被使用;相反,通配符被用作List对象的类型参数。2因此,与教程中提到的限制没有冲突。