为什么我不能扩展接口“generics方法”并将其类型缩小到我inheritance的接口“class generic”?
我展示了一个我的意思的例子,哪个更容易。 想象一下,generics类型C表示颜色类型:因此,为了直观简化,假设C是C扩展颜色
interface Screen { Background render(Plane plane); } interface MonochromeScreen extends Screen{ @Override Background render(Plane plane); }
这将引发名称冲突编译错误,说明两者都具有相同类型的擦除但不可覆盖。
但我无法理解为什么我们不能简单地允许覆盖签名,只要它更具限制性。 我的意思是,毕竟,唯一的区别是generics类型的范围,在Screen中是方法范围的,而MonochromeScreen是在类范围内。
允许子方法在其父级在类级别强制执行时允许子方法作为方法范围的generics覆盖是没有意义的,但我认为它不这样做:我的父接口可以有20个方法与不相关的generics类型,但我的子类将强制它们全部与非不兼容的额外规格/合同相同(这是任何扩展接口所做的),毕竟,单色屏幕仍然是一个屏幕,因为它可以涂上任何颜色,我只是强制执行该颜色,无论哪个颜色,都与孩子的其他function保持一致,只是缩小了课堂级别的可能性,而不是方法级别。
考虑这个function有没有根本错误的假设?
编辑:我接受了Sotirios Delimanolis的回答,因为他非常聪明地发现了正确的麻烦而且我没有要求解决方案,但是对于那些想要知道如何克服这种情况的人来说,有一个技巧在我自己的答案中解释
这是打破的地方:
MonochromeScreen redScreen = ...; Screen redScreenJustAScreen = redScreen; Plane bluePlane = null; redScreenJustAScreen. render(bluePlane);
如果您建议在编译时工作,上面的代码段可能必须在运行时因ClassCastException
而失败,因为redScreenJustAScreen
引用的对象需要Plane
但接收到Plane
。
适当使用的generics应该可以防止上述情况发生。 如果允许这样的压倒一切的规则,generics就会失败。
我对你的用例知之甚少,但似乎并不真正需要generics。
不允许这样做的原因是它违反了Liskov替代原则 。
interface Screen { Background render(Plane plane); }
这意味着您可以随时使用任意类型作为C
调用render()
。
你可以这样做:
Screen s = ...; Background b1 = s.render(new Plane ()); Background b2 = s.render(new Plane ());
现在,如果我们看一下MonochromeScreen
:
interface MonochromeScreen extends Screen{ Background render(Plane plane); }
这个声明所说的是:当你创建这个对象的实例时,你必须选择一个类型作为C
,你只能在该对象的整个生命周期中使用它。
MonochromeScreen s = ...; Background b1 = s.render(new Plane ()); Background b2 = s.render(new Plane ()); // this won't compile because you declared that s only works with Red type.
因此, Screen s = new MonochromeScreen
不是有效的强制转换, MonochromeScreen
不能是Screen
的子类。
好的,让我们转过头来看看。 假设所有颜色都是单个Color
类的实例,而不是单独的类。 那么我们的代码会是什么样的呢?
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
的实体更多,因此它不能是它的超类。
仅供参考:这是我找到解决案例并传递方法覆盖的唯一方法(如果设计模式有一个名称,我很高兴知道ir!最后是通过generics方法扩展接口的方式使它类通用。你仍然可以使用父类型(也称为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 Background render(Plane plane){ return new Background (plane.getColor()); } }
MonochromeScreen.java
interface MonochromeScreen extends Screen{ static MonochromeScreen getScreen(final Class colorClass){ return new MonochromeScreen (){ @Override public Class getColor() { return colorClass; }; }; } public Class getColor(); @Override @SuppressWarnings("unchecked") default Background render(@SuppressWarnings("rawtypes") Plane plane){ try{ C planeColor = (C) this.getColor().cast(plane.getColor()); return new Background (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 { private final C color; public Plane(C color) {this.color = color;} public C getColor() {return this.color;} }
Background.java
public class Background { private final C color; public Background(C color) {this.color = color;} public C getColor() {return this.color;} }
MainTest.java
public class MainTest { public static void main(String[] args) { Plane redPlane = new Plane<>(new Red()); Plane bluePlane = new Plane<>(new Blue()); Screen coloredScreen = Screen.getScreen(); MonochromeScreen redMonoScreen = MonochromeScreen.getScreen(Red.class); MonochromeScreen 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 } }
- 在redScreen中添加蓝色平面时抛出exception:
java.lang.UnsupportedOperationException:当前屏幕实现基于单色“红色”,但被要求渲染“蓝色”彩色平面
我不认为你的代码是你想要的,这就是你得到错误的原因。
我想这就是你想要的
public interface Screen { Background render(Plane plane); }
和
public interface MonochromeScreen extends Screen { Background render(Plane plane); }
你在思考时可能会错误的是因为
是两个接口,它是同一个东西。 它不是。
这个
public interface MonochromeScreen extends Screen { Background render(Plane plane); }
与上面的代码完全相同。 C和HI只是通用占位符的名称。 通过使用extends Screen
扩展Screen
,我们告诉java,C和HI是同一个地方,所以它会做它的魔力。
在你的代码中
Background render(Plane plane);
我们宣布了一个全新的占位符,只有该方法的上下文。 所以我们可以编写这段代码
MonochromeScreen ms; ms.render(new Plane()); Background render(Plane plane);
被重新定义为
Background render(Plane plane);
但
Background render(Plane plane);
被重新定义为
Background render(Plane plane);
哪个冲突,所以java给你一个错误。