【问题标题】:Java generics: Use this type as return type?Java泛型:使用这种类型作为返回类型?
【发布时间】:2013-06-16 23:05:44
【问题描述】:

我正在尝试使 API 尽可能用户友好。

让我们来:

class B extends A {}

class A {
    A setX(){ ...; return this; }
}

现在这个

B b = new B().setX();

无效,必须强制转换:

B b = (B) new B().setX();

有没有办法在A 中使用泛型来让编译器知道“this”类型并接受第一种方式——不进行强制转换,也不在使用的地方传递类型参数? (即不是new B<B>().setX(),那太丑了。)

我知道为什么在这种情况下 Java 需要重新输入。请不要回答解释 setX() 返回 A。我知道。 我在问泛型是否可以解决这个问题。

对于那些仍然想告诉我“这就是静态类型的工作原理”和“即使是泛型也无济于事”的人,请考虑以下有效的 Java 代码:

Map<String, String> map = new HashMap(){{ put( "foo", new RuntimeException() );
String foo = map.get("foo");  // ClassCastException!!

因此您可以看到,泛型确实允许您在代码中不出现实际类型转换的情况下获得 CCE。
这就是为什么我希望泛型允许摆脱显式类型转换的原因。

另外,IIRC C++ allows that.

【问题讨论】:

  • 这里的问题是子类不能持有父类声明的对象引用,这必然需要向下转换。即使是泛型也无法帮助您。
  • 他们有可能。它本身不需要向下转换。
  • 好吧,你可以说&lt;T extends A&gt; T setX() { return (T)this; },但请注意,你在这里是低调的。
  • 泛型不会抑制这一点。
  • Typescript 在这里有所需的概念 (typescriptlang.org/docs/handbook/…),所以这个问题在书面上是有效的。

标签: java generics this setter


【解决方案1】:

我在这里复制了answer to another question 的一部分,解释了对所谓“自我类型”的渴望以及Java 中的解决方法。

方法链

而不是写

foo.doA();
foo.doB();

很多人更愿意写作

foo.doA().doB();

不幸的是,该语言并不直接支持方法链接,尽管它正在成为一个越来越受欢迎的功能。解决方法是让doA() 返回foo。它有点脏但可以接受。

但是,如果 foo 位于类型层次结构中,则解决方法会被破坏

class Bar
    Bar doA()

class Foo extends Bar
    Foo doB();

foo.doA().doB(); // doesn't compile, since doA() returns Bar

所以有些人呼吁一种特殊的“自我类型”来解决这个问题。假设有一个关键字This 代表“自我类型”

class Bar
    This doA()

foo.doA().doB(); // works, doA() returns the type of foo, which is Foo

看来方法链是“self type”的唯一用例,所以语言可能永远不会引入它(最好直接支持方法链)

人们发现泛型可以解决这个问题

class Bar<This>
    This doA()

class Foo extends Bar<Foo>

Foo has a method "Foo doA()", inherited from Bar<Foo>

这是A extends B&lt;A&gt; 模式最流行的用例。这是一个孤立的解决方法/技巧。它没有在 A 和 B 之间的关系中添加语义。

约束Thislike也是一种流行的做法

class Bar<This extends Bar<This>>

它又丑又没用,我强烈建议不要这样做。只需使用“This”作为约定来表明它的用途。

【讨论】:

  • 感谢您的解释,我没有勇气写。干得好。
  • 我不认为class Bar&lt;This&gt; { This doA() { return this; } } 可以编译。否则,您可以执行new Bar&lt;String&gt;().doA(),它将Bar&lt;String&gt; 作为String 返回。所以你需要Bar&lt;This extends Bar&lt;This&gt;&gt;
  • @TroyDaniels - 是的,这会给This 增加一些约束;不幸的是,约束不够强。你可以有Zoo extends Bar&lt;Foo&gt;Foo extends Bar&lt;Foo&gt;Zoo 显然是个错误!但在实践中,我认为这并不重要,很少会犯这样的错误;这也是为什么我会一起放弃约束(在大多数用例中)。程序员直觉上不会写Bar&lt;String&gt;,因为String不是Bar
  • @ZhongYu 确实约束不够强,但它仍然确保偶然创建运行时异常比没有约束要困难得多。我对这两个版本的感觉很复杂
【解决方案2】:

在 A 类试试这个:

public <T extends A> T setX() {
    return (T) this;
}

你可以这样使用它

B b = new B().setX();

【讨论】:

  • 这真是太棒了!我从来没有想过你会通过(你可以分配任何不正确的子类型,如 B = a.setX(), C = a.setX()
  • 不过,这会产生未经检查的强制转换警告。
  • 为了避免未经检查的强制转换警告(因为在这种情况下,我们可以确定强制转换是正确的,即使编译器不知道),使用:`public T setX() { @SuppressWarnings("unchecked") final T that = (T) this;归还; } `
【解决方案3】:

如果你真的不喜欢演员表,你可以让B 覆盖该方法

@Override
public B setX() {
    super.setX();
    return this;
}

【讨论】:

  • 谢谢,这就是我现在要做的。我打算使 API 尽可能地对用户友好,所以我这边做一些工作是可以的。但仍然... :)
  • 当然,如果它有助于使您的 API 变得更好,那肯定是值得的。
【解决方案4】:

问题是setX() 方法返回A 类的对象,而您正试图将其写入B 类的对象。反之亦然,无需强制转换 (A a = new B();),但这样你必须强制转换它,因为声明 B b = new B().setX(); 类似于无法完成的 B b = new A();

【讨论】:

  • 请各位。我知道Java的基础知识。我正在寻找一种方法来克服这种不良影响以缩短代码。很明显,类型是B。我可以告诉它,你可以告诉它,所以编译器应该也可以告诉它。问题是:Java 能做到这一点吗?
  • @OndraŽižka 如果您如您所说了解基本 Java,那么您必须知道 这是不可能的 .
  • @OndraŽižka 那么好吧,你有一个评论和一个答案(基本上和我的评论一样)你不能这样做。请注意,泛型不能成为基础的一部分,但它是基于基础的。看起来你真的不了解基础知识...
  • @OndraŽižka 泛型不是基本的事实是一个借口。这个特性甚至没有理由属于泛型的范围——它可能已经存在于 Java 1.0 中,带有某种 thisclass 关键字。它根本不存在于整个 Java 中。这有点像问为什么饼干不在你够不到的一个抽屉里,而饼干不在整个房子里的原因是因为没有人买过饼干。
  • @Hovercraft。这取决于你的意思。显然它不能用运行时类型来完成,但我不太确定它不能静态地完成。泛型非常复杂。
猜你喜欢
  • 2021-12-13
  • 2015-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多