我知道这个问题已被提出很多问题,但在我看来,通常的答案远非令人满意。
给出以下类层次结构:
class SuperClass{}
class SubClass extends SuperClass{}
为什么人们使用这种模式来实例化SubClass:
SuperClass instance = new SubClass();
而不是这一个:
SubClass instance = new SubClass();
现在,我看到的通常答案是,这是为了将instance
作为参数发送到需要SuperClass实例的方法,如下所示:
void aFunction(SuperClass param){}
//somewhere else in the code...
...
aFunction(instance);
...
但是我可以将一个SubClass实例发送给aFunction,而不管其持有的变量类型如何!意味着以下代码将编译并运行而没有错误(假设先前提供的aFunction定义):
SubClass instance = new SubClass();
aFunction(instance);
实际上,AFAIK变量类型在运行时没有意义。它们仅由编译器使用!
将变量定义为SuperClass的另一个可能的原因是,如果它有几个不同的子类,并且该变量应该在运行时将它的引用切换为其中的几个,但我举例说,这只发生在类中(不是超级,不要只是上课。绝对不足以要求一般模式......
答案 0 :(得分:8)
此类编码的主要论据是因为Liskov Substituion Principle,其中指出如果X
是T
类型的子类型,那么T
的任何实例应该可以与X
交换出来。
这样做的好处很简单。假设我们有一个包含属性文件的程序,如下所示:
mode="Run"
你的程序看起来像这样:
public void Program
{
public Mode mode;
public static void main(String[] args)
{
mode = Config.getMode();
mode.run();
}
}
简而言之,这个程序将使用配置文件来定义该程序将要启动的模式。在Config
类中,getMode()
可能如下所示:
public Mode getMode()
{
String type = getProperty("mode"); // Now equals "Run" in our example.
switch(type)
{
case "Run": return new RunMode();
case "Halt": return new HaltMode();
}
}
为什么这不起作用
现在,因为您有Mode
类型的引用,所以只需更改mode
属性的值即可完全更改程序的功能。如果您有public RunMode mode
,则无法使用此类功能。
为什么这是一件好事
这种模式已经很好地发挥作用,因为它为可扩展性打开了程序。这意味着如果作者希望实现这种功能,那么这种类型的所需功能可以通过最少量的更改实现。我的意思是,来吧。您可以更改配置文件中的一个单词并完全更改程序流,而无需编辑任何一行代码。这是可取的。
答案 1 :(得分:3)
在许多情况下,它并不重要,但被认为是好的风格。
您将提供给用户的信息限制为必要的参考,即它是SuperClass
类型的实例。它不会(也不应该)关注变量是否引用SuperClass
或SubClass
类型的对象。
<强>更新强>:
对于从未用作参数等的局部变量也是如此。 正如我所说,它通常无关紧要,但被认为是好的风格,因为你可能稍后更改变量以保存参数或超类型的另一个子类型。在这种情况下,如果您首先使用子类型,那么您的其他代码(在单个范围内,例如方法)可能意外地依赖于一个特定子类型的API并更改该变量以保存另一个类型可能会破坏你的代码。
我将扩展克里斯的例子:
考虑您有以下内容:
RunMode mode = new RunMode();
...
您现在可能依赖于mode
是RunMode
。
但是,稍后您可能希望将该行更改为:
RunMode mode = Config.getMode(); //breaks
糟糕,无法编译。好的,让我们改变它。
Mode mode = Config.getMode();
该行现在可以编译,但是您的其他代码可能会中断,因为您意外地依赖mode
作为RunMode
的实例。请注意,它可能会编译,但可能会在运行时中断或使您的逻辑失效。
答案 2 :(得分:1)
它被称为多态,它是对子类对象的超类引用。
In fact, AFAIK variable types are meaningless at runtime. They are used
only by the compiler!
不确定你从哪里读到这个。在编译时,编译器只知道引用类型的类(所以在多态性的情况下如此所说的超类)。在运行时,java知道Object的实际类型(.getClass())。在编译时,java编译器仅检查调用的方法定义是否在引用类的类中。调用哪个方法(函数重载)是在运行时根据对象的实际类型确定的。
Why polymorphism?
谷歌搜索更多,但这里是一个例子。你有一个共同的方法draw(Shape s)
。现在形状可以是Rectangle
,Circle
任意CustomShape
。如果你不在draw()
方法中使用Shape引用,则必须为每种类型的(子类)形状创建不同的方法。
答案 3 :(得分:1)
SuperClass instance = new SubClass1()
在某些行之后,您可以执行instance = new SubClass2();
但如果你写,SubClass1 instance = new SubClass1()
;
在某些行之后,您无法执行instance = new SubClass2()
答案 4 :(得分:0)
从设计的角度来看,您将拥有一个超级类,并且可以有多个子类,您希望在其中扩展功能。
必须编写子类的实现者只需关注要覆盖的方法