为什么我不能扩展接口 "generic method" 并将其类型缩小到我继承的接口 "class generic"?
Why can't I extend an interface "generic method" and narrow its type to my inherited interface "class generic"?
我展示了我的意思的例子,这更容易。
假设通用类型 C 表示颜色类型:所以对于
视觉简化假设 C 是 C extends Color
interface Screen {
<C> Background<C> render(Plane<C> plane);
}
interface MonochromeScreen<C> extends Screen{
@Override
Background<C> render(Plane<C> plane);
}
这会引发名称冲突编译错误,解释两者具有相同的类型擦除但不可覆盖。
但我不明白为什么我们不能简单地允许覆盖签名,只要它更具限制性。我的意思是,毕竟,唯一的区别是泛型类型的范围,在 Screen 中是 method-wide 而在 MonochromeScreen 中是 class-wide.
当 parent 在 class 级别强制执行一致性时,允许 child 方法作为 method-scoped 泛型覆盖是没有意义的,但我认为它否则:我的 parent 接口可能有 20 个具有不相关泛型类型的方法,但是我的 child class 会强制它们与 non-incompatible 额外的 [=29] 相同=](这是任何扩展接口所做的),毕竟,单色屏幕仍然是一个屏幕,因为它可以用任何颜色绘制,我只是强制执行该颜色,无论是哪种颜色,以使其与其他颜色保持一致child 的功能,只是缩小 class 级别的可能性,而不是方法级别。
考虑该特征是否存在根本错误的假设?
编辑:我接受了 Sotirios Delimanolis 的回答,因为他非常聪明地发现了正确的问题,我并不是在寻求解决方案,但对于那些想知道如何克服这种情况的人来说,有一个技巧在我自己的回答中解释
我不认为你的代码是你想要的,这就是你出错的原因。
我想这就是你想要的
public interface Screen<C> {
Background<C> render(Plane<C> plane);
}
和
public interface MonochromeScreen<C> extends Screen<C> {
Background<C> render(Plane<C> plane);
}
你可能会误以为因为<C>
是两个接口所以是一回事。不是。
这个
public interface MonochromeScreen<HI> extends Screen<HI> {
Background<HI> render(Plane<HI> plane);
}
和上面的代码完全一样。 C 和 HI 只是通用占位符的名称。通过用 extends Screen<HI>
扩展 Screen<C>
我们告诉 java C 和 HI 是同一个 placeHolder,所以它会很神奇。
在你的代码中
<C> Background<C> render(Plane<C> plane);
我们已经声明了一个全新的占位符,它在该方法中只有上下文。
所以我们可以写这段代码
MonochromeScreen<String> ms;
ms.render(new Plane<Banana>());
<C> Background<C> render(Plane<C> plane);
重新定义为
Background<Banana> render(Plane<Banana> plane);
但是
Background<C> render(Plane<C> plane);
重新定义为
Background<String> render(Plane<String> plane);
哪个冲突,所以 java 给你一个错误。
这是中断的地方:
MonochromeScreen<Red> redScreen = ...;
Screen redScreenJustAScreen = redScreen;
Plane<Blue> bluePlane = null;
redScreenJustAScreen.<Blue>render(bluePlane);
如果您建议的内容在编译时有效,上面的代码片段可能会在运行时失败并返回 ClassCastException
,因为 redScreenJustAScreen
引用的对象需要 Plane<Red>
但收到了Plane<Blue>
.
如果使用得当,泛型应该可以防止上述情况的发生。如果允许这样的压倒一切的规则,泛型就会失败。
我对你的用例了解不够,但似乎并不真的需要泛型。
不允许这样做的原因是它违反了 Liskov substitution principle。
interface Screen {
<C> Background<C> render(Plane<C> plane);
}
这意味着您可以随时使用任意类型 C
.
调用 render()
您可以这样做,例如:
Screen s = ...;
Background<Red> b1 = s.render(new Plane<Red>());
Background<Blue> b2 = s.render(new Plane<Blue>());
现在如果我们看 MonochromeScreen
:
interface MonochromeScreen<C> extends Screen{
Background<C> render(Plane<C> plane);
}
这个声明的意思是:当你创建这个对象的一个实例时,你必须选择一个类型作为 C
,并且你只能在那个对象的整个生命周期中使用它。
MonochromeScreen<Red> s = ...;
Background<Red> b1 = s.render(new Plane<Red>());
Background<Blue> b2 = s.render(new Plane<Blue>()); // this won't compile because you declared that s only works with Red type.
因此 Screen s = new MonochromeScreen<Red>();
不是有效的转换,MonochromeScreen
不能是 Screen
的子 class。
好吧,让我们稍微改变一下。假设所有颜色都是单个 Color
class 的实例,而不是单独的 classes。那么我们的代码会是什么样子?
interface Plane {
Color getColor();
}
interface Background {
Color getColor();
}
interface Screen {
Background render(Plane plane);
}
到目前为止,还不错。现在我们定义一个单色屏幕:
class MonochromeScreen implements Screen {
private final Color color; // this is the only colour we have
public Background render(Plane plane) {
if (!plane.getColor().equals(color))
throw new IllegalArgumentException( "I can't render this colour.");
return new Background() {...};
}
}
这会编译得很好,并且会有或多或少相同的语义。
问题是:这是好的代码吗?毕竟,您仍然可以这样做:
public void renderPrimaryPlanes(Screen s) { //this looks like a good method
s.render(new Plane(Color.RED));
s.render(new Plane(Color.GREEN));
s.render(new Plane(Color.BLUE));
}
...
Screen s = new MonochromeScreen(Color.RED);
renderPrimaryPlanes(s); //this would throw an IAE
嗯,不。这绝对不是您对无辜 renderPrimaryPlanes()
方法的期望。事情会以意想不到的方式破裂。这是为什么?
这是因为尽管它在形式上是有效的和可编译的,但这段代码也以与原始代码完全相同的方式破坏了 LSP。问题不在于语言,而在于模型:您称为 Screen
的实体可以比称为 MonochromeScreen
的实体做更多的事情,因此它不可能是它的超级 class .
仅供参考:这是我找到的解决案例并通过方法重写的唯一方法(如果设计模式有名称,我很高兴知道 ir!它最终是扩展的方式具有泛型方法的接口,使其成为 class-泛型。您仍然可以使用父类型(又名 Color)键入它以将其用作原始通用类型旧接口。:
Screen.java
public interface Screen {
public interface Color {}
public class Red implements Color {}
public class Blue implements Color {}
static Screen getScreen(){
return new Screen(){};
}
default <C extends Color> Background<C> render(Plane<C> plane){
return new Background<C>(plane.getColor());
}
}
单色Screen.java
interface MonochromeScreen<C extends Color> extends Screen{
static <C extends Color> MonochromeScreen<C> getScreen(final Class<C> colorClass){
return new MonochromeScreen<C>(){
@Override public Class<C> getColor() { return colorClass; };
};
}
public Class<C> getColor();
@Override
@SuppressWarnings("unchecked")
default Background<C> render(@SuppressWarnings("rawtypes") Plane plane){
try{
C planeColor = (C) this.getColor().cast(plane.getColor());
return new Background<C>(planeColor);
} catch (ClassCastException e){
throw new UnsupportedOperationException("Current screen implementation is based in mono color '"
+ this.getColor().getSimpleName() + "' but was asked to render a '"
+ plane.getColor().getClass().getSimpleName() + "' colored plane" );
}
}
}
Plane.java
public class Plane<C extends Color> {
private final C color;
public Plane(C color) {this.color = color;}
public C getColor() {return this.color;}
}
Background.java
public class Background<C extends Color> {
private final C color;
public Background(C color) {this.color = color;}
public C getColor() {return this.color;}
}
MainTest.java
public class MainTest<C> {
public static void main(String[] args) {
Plane<Red> redPlane = new Plane<>(new Red());
Plane<Blue> bluePlane = new Plane<>(new Blue());
Screen coloredScreen = Screen.getScreen();
MonochromeScreen<Red> redMonoScreen = MonochromeScreen.getScreen(Red.class);
MonochromeScreen<Color> xMonoScreen = MonochromeScreen.getScreen(Color.class);
Screen redScreenAsScreen = (Screen) redMonoScreen;
coloredScreen.render(redPlane);
coloredScreen.render(bluePlane);
redMonoScreen.render(redPlane);
//redMonoScreen.render(bluePlane); --> This throws UnsupportedOperationException*
redScreenAsScreen.render(redPlane);
//redScreenAsScreen.render(bluePlane); --> This throws UnsupportedOperationException*
xMonoScreen.render(new Plane<>(new Color(){})); //--> And still I can define a Monochrome screen as of type Color so
System.out.println("Test Finished!"); //still would have a wildcard to make it work as a raw screen (not useful
//in my physical model but it is in other abstract models where this problem arises
}
}
- 在红色屏幕中添加蓝色平面时抛出异常:
java.lang.UnsupportedOperationException: Current screen implementation
is based in mono color 'Red' but was asked to render a 'Blue' colored
plane
我展示了我的意思的例子,这更容易。 假设通用类型 C 表示颜色类型:所以对于 视觉简化假设 C 是 C extends Color
interface Screen {
<C> Background<C> render(Plane<C> plane);
}
interface MonochromeScreen<C> extends Screen{
@Override
Background<C> render(Plane<C> plane);
}
这会引发名称冲突编译错误,解释两者具有相同的类型擦除但不可覆盖。
但我不明白为什么我们不能简单地允许覆盖签名,只要它更具限制性。我的意思是,毕竟,唯一的区别是泛型类型的范围,在 Screen 中是 method-wide 而在 MonochromeScreen 中是 class-wide.
当 parent 在 class 级别强制执行一致性时,允许 child 方法作为 method-scoped 泛型覆盖是没有意义的,但我认为它否则:我的 parent 接口可能有 20 个具有不相关泛型类型的方法,但是我的 child class 会强制它们与 non-incompatible 额外的 [=29] 相同=](这是任何扩展接口所做的),毕竟,单色屏幕仍然是一个屏幕,因为它可以用任何颜色绘制,我只是强制执行该颜色,无论是哪种颜色,以使其与其他颜色保持一致child 的功能,只是缩小 class 级别的可能性,而不是方法级别。
考虑该特征是否存在根本错误的假设?
编辑:我接受了 Sotirios Delimanolis 的回答,因为他非常聪明地发现了正确的问题,我并不是在寻求解决方案,但对于那些想知道如何克服这种情况的人来说,有一个技巧在我自己的回答中解释
我不认为你的代码是你想要的,这就是你出错的原因。
我想这就是你想要的
public interface Screen<C> {
Background<C> render(Plane<C> plane);
}
和
public interface MonochromeScreen<C> extends Screen<C> {
Background<C> render(Plane<C> plane);
}
你可能会误以为因为<C>
是两个接口所以是一回事。不是。
这个
public interface MonochromeScreen<HI> extends Screen<HI> {
Background<HI> render(Plane<HI> plane);
}
和上面的代码完全一样。 C 和 HI 只是通用占位符的名称。通过用 extends Screen<HI>
扩展 Screen<C>
我们告诉 java C 和 HI 是同一个 placeHolder,所以它会很神奇。
在你的代码中
<C> Background<C> render(Plane<C> plane);
我们已经声明了一个全新的占位符,它在该方法中只有上下文。 所以我们可以写这段代码
MonochromeScreen<String> ms;
ms.render(new Plane<Banana>());
<C> Background<C> render(Plane<C> plane);
重新定义为
Background<Banana> render(Plane<Banana> plane);
但是
Background<C> render(Plane<C> plane);
重新定义为
Background<String> render(Plane<String> plane);
哪个冲突,所以 java 给你一个错误。
这是中断的地方:
MonochromeScreen<Red> redScreen = ...;
Screen redScreenJustAScreen = redScreen;
Plane<Blue> bluePlane = null;
redScreenJustAScreen.<Blue>render(bluePlane);
如果您建议的内容在编译时有效,上面的代码片段可能会在运行时失败并返回 ClassCastException
,因为 redScreenJustAScreen
引用的对象需要 Plane<Red>
但收到了Plane<Blue>
.
如果使用得当,泛型应该可以防止上述情况的发生。如果允许这样的压倒一切的规则,泛型就会失败。
我对你的用例了解不够,但似乎并不真的需要泛型。
不允许这样做的原因是它违反了 Liskov substitution principle。
interface Screen {
<C> Background<C> render(Plane<C> plane);
}
这意味着您可以随时使用任意类型 C
.
render()
您可以这样做,例如:
Screen s = ...;
Background<Red> b1 = s.render(new Plane<Red>());
Background<Blue> b2 = s.render(new Plane<Blue>());
现在如果我们看 MonochromeScreen
:
interface MonochromeScreen<C> extends Screen{
Background<C> render(Plane<C> plane);
}
这个声明的意思是:当你创建这个对象的一个实例时,你必须选择一个类型作为 C
,并且你只能在那个对象的整个生命周期中使用它。
MonochromeScreen<Red> s = ...;
Background<Red> b1 = s.render(new Plane<Red>());
Background<Blue> b2 = s.render(new Plane<Blue>()); // this won't compile because you declared that s only works with Red type.
因此 Screen s = new MonochromeScreen<Red>();
不是有效的转换,MonochromeScreen
不能是 Screen
的子 class。
好吧,让我们稍微改变一下。假设所有颜色都是单个 Color
class 的实例,而不是单独的 classes。那么我们的代码会是什么样子?
interface Plane {
Color getColor();
}
interface Background {
Color getColor();
}
interface Screen {
Background render(Plane plane);
}
到目前为止,还不错。现在我们定义一个单色屏幕:
class MonochromeScreen implements Screen {
private final Color color; // this is the only colour we have
public Background render(Plane plane) {
if (!plane.getColor().equals(color))
throw new IllegalArgumentException( "I can't render this colour.");
return new Background() {...};
}
}
这会编译得很好,并且会有或多或少相同的语义。
问题是:这是好的代码吗?毕竟,您仍然可以这样做:
public void renderPrimaryPlanes(Screen s) { //this looks like a good method
s.render(new Plane(Color.RED));
s.render(new Plane(Color.GREEN));
s.render(new Plane(Color.BLUE));
}
...
Screen s = new MonochromeScreen(Color.RED);
renderPrimaryPlanes(s); //this would throw an IAE
嗯,不。这绝对不是您对无辜 renderPrimaryPlanes()
方法的期望。事情会以意想不到的方式破裂。这是为什么?
这是因为尽管它在形式上是有效的和可编译的,但这段代码也以与原始代码完全相同的方式破坏了 LSP。问题不在于语言,而在于模型:您称为 Screen
的实体可以比称为 MonochromeScreen
的实体做更多的事情,因此它不可能是它的超级 class .
仅供参考:这是我找到的解决案例并通过方法重写的唯一方法(如果设计模式有名称,我很高兴知道 ir!它最终是扩展的方式具有泛型方法的接口,使其成为 class-泛型。您仍然可以使用父类型(又名 Color)键入它以将其用作原始通用类型旧接口。:
Screen.java
public interface Screen {
public interface Color {}
public class Red implements Color {}
public class Blue implements Color {}
static Screen getScreen(){
return new Screen(){};
}
default <C extends Color> Background<C> render(Plane<C> plane){
return new Background<C>(plane.getColor());
}
}
单色Screen.java
interface MonochromeScreen<C extends Color> extends Screen{
static <C extends Color> MonochromeScreen<C> getScreen(final Class<C> colorClass){
return new MonochromeScreen<C>(){
@Override public Class<C> getColor() { return colorClass; };
};
}
public Class<C> getColor();
@Override
@SuppressWarnings("unchecked")
default Background<C> render(@SuppressWarnings("rawtypes") Plane plane){
try{
C planeColor = (C) this.getColor().cast(plane.getColor());
return new Background<C>(planeColor);
} catch (ClassCastException e){
throw new UnsupportedOperationException("Current screen implementation is based in mono color '"
+ this.getColor().getSimpleName() + "' but was asked to render a '"
+ plane.getColor().getClass().getSimpleName() + "' colored plane" );
}
}
}
Plane.java
public class Plane<C extends Color> {
private final C color;
public Plane(C color) {this.color = color;}
public C getColor() {return this.color;}
}
Background.java
public class Background<C extends Color> {
private final C color;
public Background(C color) {this.color = color;}
public C getColor() {return this.color;}
}
MainTest.java
public class MainTest<C> {
public static void main(String[] args) {
Plane<Red> redPlane = new Plane<>(new Red());
Plane<Blue> bluePlane = new Plane<>(new Blue());
Screen coloredScreen = Screen.getScreen();
MonochromeScreen<Red> redMonoScreen = MonochromeScreen.getScreen(Red.class);
MonochromeScreen<Color> xMonoScreen = MonochromeScreen.getScreen(Color.class);
Screen redScreenAsScreen = (Screen) redMonoScreen;
coloredScreen.render(redPlane);
coloredScreen.render(bluePlane);
redMonoScreen.render(redPlane);
//redMonoScreen.render(bluePlane); --> This throws UnsupportedOperationException*
redScreenAsScreen.render(redPlane);
//redScreenAsScreen.render(bluePlane); --> This throws UnsupportedOperationException*
xMonoScreen.render(new Plane<>(new Color(){})); //--> And still I can define a Monochrome screen as of type Color so
System.out.println("Test Finished!"); //still would have a wildcard to make it work as a raw screen (not useful
//in my physical model but it is in other abstract models where this problem arises
}
}
- 在红色屏幕中添加蓝色平面时抛出异常:
java.lang.UnsupportedOperationException: Current screen implementation is based in mono color 'Red' but was asked to render a 'Blue' colored plane