我最近看到,可以声明一个返回类型,该返回类型也受接口限制。考虑以下类和接口:
public class Foo {
public String getFoo() { ... }
}
public interface Bar {
public void setBar(String bar);
}
我可以这样声明一个返回类型:
public class FooBar {
public static <T extends Foo & Bar> T getFooBar() {
//some implementation that returns a Foo object,
//which is forced to implement Bar
}
}
如果我从某个地方调用该方法,那么IDE会告诉我返回类型具有方法String getFoo()
以及setBar(String)
,但是仅当我在
Function 后面指向一个点时才这样:
FooBar.getFooBar(). // here the IDE is showing the available methods.
有没有办法获得对这样一个对象的引用?我的意思是,如果我愿意做这样的事情:
//bar only has the method setBar(String)
Bar bar = FooBar.getFooBar();
//foo only has the getFoo():String method
Foo foo = FooBar.getFooBar();
我想有一个这样的参考(伪代码):
<T extents Foo & Bar> fooBar = FooBar.getFooBar();
//or maybe
$1Bar bar = FooBar.getFooBar();
//or else maybe
Foo&Bar bar = FooBar.getFooBar();
这在Java中是有可能的,还是我只能声明这样的返回类型?我认为Java也必须以某种方式键入它。我宁愿不要求助于这样的包装器,因为它感觉像作弊:
public class FooBarWrapper<T extends Foo&Bar> extends Foo implements Bar {
public T foobar;
public TestClass(T val){
foobar = val;
}
@Override
public void setBar(String bar) {
foobar.setBar(bar);
}
@Override
public String getFoo() {
return foobar.getFoo();
}
}
Java真的发明了这样一个不错的功能,但是忘记了要引用它吗?
虽然通用方法的类型参数可以受限制,例如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
,采用aFooBarContainer
并返回其引用:
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>>
并将其返回另一种方法。你明白我的想法。
我试图用Java中的一个通用返回类型来实现< code >命令模式。 在SO上查看了这个答案后,我创建了一个类(接口),如下所示: 然而,Eclipse一直抱怨: SearchResultsPage 类型的方法 execute(ArrayList, T) 必须重写或实现超类型方法 但当我点击 在超类型命令中创建 execute() Eclipse自动生成方法<code>T execute(Arra
问题内容: 假设我有一个超类,它定义了以下抽象方法 现在,如果我想在某些子类中覆盖它 我收到有关类型安全和未经检查的转换的警告: 类型安全:返回类型为从类型需要选中转换,以符合从类型 没有下摔倒,如果?有什么办法可以适当消除警告吗? 问题答案: 重写方法的返回类型必须是重写方法的返回类型的子类型。 不是where 的子类型。T在这里未知。 是每个子类型化规则的的子类型。 有关通配符的一些子类型化规
这个问题与这个问题很接近,但有一个主要区别。 可能的要求: (1) 我想生成一个带有通用返回值的Java函数。 (2)输入参数列表总是相同的。( (3) 函数应知道预期的返回参数类型。 我的尝试: 因为没有生成的实例,所以它不起作用。尝试使用
问题内容: 按引用返回的切片为空: 如何通过引用从函数返回切片? 问题答案: 通过分配给,您可以更改指向的位置,而不是指向的值。要做后者,而不是write 。
为了理解TypeScript的精神,我在我的组件和服务中编写了全类型签名,这扩展到了angular2表单的自定义验证函数。 我知道我可以重载函数签名,但这需要每个返回类型的参数不同,因为将每个签名编译为单独的函数: 我还知道我可以返回单个类型(如Promise),它本身可以是多个子类型: 但是,在angular2自定义表单验证器的上下文中,单个签名(一个类型为的参数)可以返回两种不同的类型:带有表
问题内容: 我在很多地方都读过,包括在方法返回类型中使用有界通配符是一个坏主意。但是,我在课堂上找不到避免这种情况的方法。我想念什么吗? 情况看起来像这样: 总而言之,这是一个我希望能够 使用 任何与英语有所不同的出版物的类。该类需要允许从外部访问发布,但理想情况下,的调用者不希望将结果用作有界通配符。他们会很高兴的。 有办法解决吗? 问题答案: 有界通配符具有传染性,这就是您链接到的页面似乎在哀