栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

如何引用具有多个界限的通用返回类型

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

如何引用具有多个界限的通用返回类型

虽然通用方法的类型参数可以受限制,例如

extends Foo &Bar
,但它们最终由调用者决定。致电时
getFooBar()
,呼叫站点已经知道
T
要解决的问题。通常,这些类型参数将由编译器 推断,这就是为什么您通常不需要指定它们的原因,如下所示:

FooBar.<FooAndBar>getFooBar();

但是即使

T
被推断为
FooAndBar
,这实际上是幕后发生的事情。

因此,要回答您的问题,请使用以下语法:

Foo&Bar bothFooAndBar = FooBar.getFooBar();

在实践中永远不会有用。原因是呼叫者 必须已经知道了 什么

T
。要么
T
是一些具体类型:

FooAndBar bothFooAndBar = FooBar.<FooAndBar>getFooBar(); // T is FooAndBar

或者,

T
是一个未解析的类型参数,我们在其范围内:

<U extends Foo & Bar> void someGenericMethod() {    U bothFooAndBar = FooBar.<U>getFooBar(); // T is U}

另一个例子:

class SomeGenericClass<V extends Foo & Bar> {    void someMethod() {        V bothFooAndBar = FooBar.<V>getFooBar(); // T is V    }}

从技术上讲,这就是答案。但我也想指出,您的示例方法

getFooBar
本质上是不安全的。记住,调用者决定要做什么
T
,而不是方法。由于
getFooBar
不采用与参数相关的任何参数
T
,并且由于类型擦除,因此唯一的选择是
null
通过进行未经检查的强制转换来返回或“说谎”,从而冒堆污染的风险。一个典型的解决方法是
getFooBar
采用一个
Class<T>
参数,或者
FooFactory<T>
例如。

更新资料

当我断言调用方

getFooBar
必须始终知道是什么时,事实证明我错了
T
。正如@MiserableVariable指出的那样,在某些情况下,泛型方法的类型参数被推断为
通配符捕获 ,而不是具体的类型或类型变量。

正如我们在评论中所讨论的,一个使用

getFooBar
混乱的示例,因为它不需要任何参数来推断
T
。某些编译器在无上下文调用时抛出错误,
getFooBar()
而其他编译器则对此表示满意。我
认为 不一致的编译错误以及调用
FooBar.<?>getFooBar()
是非法的事实证实了我的观点,但事实证明这些都是红色鲱鱼。

基于@MiserableVariable的答案,我整理了一个新示例,该示例使用带有参数的泛型方法来消除混乱。假设我们有接口

Foo
Bar
和实施
FooBarImpl

interface Foo { }interface Bar { }static class FooBarImpl implements Foo, Bar { }

我们还有一个简单的容器类,用于包装实现

Foo
和的某种类型的实例
Bar
。它声明一个愚蠢的静态方法
unwrap
,采用a
FooBarContainer
并返回其引用:

static class FooBarContainer<T extends Foo & Bar> {    private final T fooBar;    public FooBarContainer(T fooBar) {        this.fooBar = fooBar;    }    public T get() {        return fooBar;    }    static <T extends Foo & Bar> T unwrap(FooBarContainer<T> fooBarContainer) {        return fooBarContainer.get();    }}

现在假设我们有一个通配符参数化类型

FooBarContainer

FooBarContainer<?> unknownFooBarContainer = ...;

我们被允许

unknownFooBarContainer
进入
unwrap
。这表明我先前的断言是错误的,因为呼叫站点不知道是什么
T
-只是它是边界内的某种类型
extendsFoo & Bar

FooBarContainer.unwrap(unknownFooBarContainer); // T is a wildcard capture, ?

如前所述,

unwrap
使用通配符进行调用是非法的:

FooBarContainer.<?>unwrap(unknownFooBarContainer); // compiler error

我只能猜测这是因为通配符捕获永远无法匹配-

?
调用站点提供的参数是模棱两可的,没有办法说它应该与类型的通配符特别匹配
unknownFooBarContainer

因此,这是OP询问的语法的用例。调用

unwrap
unknownFooBarContainer
返回类型的引用
? extends Foo &Bar
。我们可以将该引用分配给
Foo
Bar
,但不能两者都使用:

Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);Bar bar = FooBarContainer.unwrap(unknownFooBarContainer);

如果由于某种原因

unwrap
昂贵,而我们只想调用一次,我们将被迫强制转换:

Foo foo = FooBarContainer.unwrap(unknownFooBarContainer);Bar bar = (Bar)foo;

因此,这是假设语法派上用场的地方:

Foo&Bar fooBar = FooBarContainer.unwrap(unknownFooBarContainer);

这只是一个相当晦涩的用例。允许使用这种语法,无论好坏,都会有非常广泛的含义。这将在不需要的地方打开滥用的空间,而且为什么语言设计人员没有实现这种目的也是完全可以理解的。但是我仍然认为思考很有趣。


关于堆污染的注意事项

主要针对@MiserableVariable以下是不安全方法如何

getFooBar
导致堆污染及其含义的演练。给定以下接口和实现:

interface Foo { }static class Foo1 implements Foo {    public void foo1Method() { }}static class Foo2 implements Foo { }

让我们实现一个unsafe方法

getFoo
getFooBar
该示例类似于但已简化:

@SuppressWarnings("unchecked")static <T extends Foo> T getFoo() {    //unchecked cast - ClassCastException is not thrown here if T is wrong    return (T)new Foo2();}public static void main(String[] args) {    Foo1 foo1 = getFoo(); //ClassCastException is thrown here}

在这里,当新的

Foo2
强制转换
T
,它是“未登记”,因为类型擦除意味着运行时不知道它应该会失败,即使它应该在这种情况下,由于
T
Foo1
。相反,堆是“污染的”,这意味着引用指向不应该允许的对象。

Foo2
实例尝试将实例分配给
foo1
具有可更改类型的引用时,方法返回后会发生失败
Foo1

您可能会想,“好吧,它在呼叫站点爆炸了,而不是方法,大不了了。” 但是,当涉及更多泛型时,它很容易变得更加复杂。例如:

static <T extends Foo> List<T> getFooList(int size) {    List<T> fooList = new ArrayList<T>(size);    for (int i = 0; i < size; i++) {        T foo = getFoo();        fooList.add(foo);    }    return fooList;}public static void main(String[] args) {    List<Foo1> foo1List = getFooList(5);    // a bunch of things happen    //sometime later maybe, depending on state    foo1List.get(0).foo1Method(); //ClassCastException is thrown here}

现在,它不会在呼叫站点爆炸。当

foo1List
get
的内容被使用时,它会炸毁。这就是堆污染变得更难以调试的方式,因为异常stacktrace不会将您引向实际的问题。

当调用者本身在通用范围内时,它变得更加复杂。想象一下,而不是

List<Foo1>
得到a
List<T>
,而是将其放入a
Map<K,List<T>>
并将其返回另一种方法。你明白我的想法。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/425886.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号