Java 对子类实例使用超类类型

Java 对子类实例使用超类类型,java,inheritance,casting,polymorphism,type-conversion,Java,Inheritance,Casting,Polymorphism,Type Conversion,我知道这个问题被问了很多,但在我看来,通常的答案远远不能令人满意 给定以下类层次结构: class SuperClass{} class SubClass extends SuperClass{} 为什么人们使用此模式来实例化子类: SuperClass instance = new SubClass(); 而不是这个: SubClass instance = new SubClass(); 现在,我看到的通常答案是,这是为了将实例作为参数发送给需要超类实例的方法,如下所示: void a

我知道这个问题被问了很多,但在我看来,通常的答案远远不能令人满意

给定以下类层次结构:

class SuperClass{}
class SubClass extends SuperClass{}
为什么人们使用此模式来实例化子类:

SuperClass instance = new SubClass();
而不是这个:

SubClass instance = new SubClass();
现在,我看到的通常答案是,这是为了将
实例
作为参数发送给需要超类实例的方法,如下所示:

void aFunction(SuperClass param){}

//somewhere else in the code...
...
aFunction(instance);
...
但我可以将子类的实例发送到函数,而不管包含它的变量类型是什么!这意味着以下代码将编译并运行,不会出现错误(假设前面提供的函数定义):

事实上,AFAIK变量类型在运行时毫无意义。它们仅由编译器使用


将变量定义为超类的另一个可能原因是,如果它有几个不同的子类,并且该变量应该在运行时将其引用切换到其中的几个子类,但是我只看到这种情况在类中发生(不是超级,不是子类,只是类)。显然不足以要求一个通用模式…

这是从设计的角度来看的,您将有一个超类,并且可以有多个子类,其中您希望扩展功能


必须编写子类的实现者只需关注覆盖它的方法称为多态,它是对子类对象的超类引用。

In fact, AFAIK variable types are meaningless at runtime. They are used 
only by the compiler!
不知道你从哪里读到的。在编译时,编译器只知道引用类型的类(如您所述,在多态性的情况下是超类)。在运行时,java知道对象的实际类型(.getClass())。在编译时,java编译器只检查被调用的方法定义是否在引用类型的类中。调用哪个方法(函数重载)是在运行时根据对象的实际类型确定的

Why polymorphism?

谷歌想找到更多,但这里有一个例子。您有一个常用的方法
绘制(形状s)
。现在形状可以是一个
矩形
,一个
圆形
任何
自定义形状
。如果在
draw()
方法中不使用Shape reference,则必须为每种类型的(子类)Shape创建不同的方法。

在许多情况下,这并不重要,但被认为是好的样式。 您将提供给引用用户的信息限制为必需的内容,即它是类型
超类的实例。变量是否引用类型为
超类
子类
的对象并不重要(也不应该如此)

更新

对于从未用作参数等的局部变量也是如此。 正如我所说的,它通常不重要,但被认为是好的样式,因为您以后可能会更改变量以保存一个参数或超级类型的另一个子类型。在这种情况下,如果您首先使用子类型,您的后续代码(在该单一范围内,例如,方法)可能会意外地依赖于一个特定子类型的API,并且更改变量以保存另一个类型可能会破坏您的代码

我将进一步介绍Chris的例子:

假设您有以下几点:

RunMode mode = new RunMode();

...
您现在可以依赖这样一个事实:
mode
是一个
RunMode

但是,稍后您可能需要将该行更改为:

RunMode mode = Config.getMode(); //breaks
哎呀,那是不可编译的。好吧,让我们换个说法

Mode mode = Config.getMode(); 

这一行现在可以编译了,但您的后续代码可能会中断,因为您意外地依赖于
模式
作为
运行模式
的实例。请注意,它可能会编译,但可能会在运行时中断或破坏您的逻辑。

此类型编码的主要参数是因为,它指出,如果
X
T
类型的子类型,则
T
的任何实例都应该能够与
X
交换

这样做的好处很简单。假设我们有一个程序,它有一个属性文件,如下所示:

mode="Run"
public void Program
{
    public Mode mode;

    public static void main(String[] args)
    {
        mode = Config.getMode();
        mode.run();
    }
}
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="Run"
public void Program
{
    public Mode mode;

    public static void main(String[] args)
    {
        mode = Config.getMode();
        mode.run();
    }
}
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();  
    }
}
简单地说,这个程序将使用配置文件来定义这个程序启动的模式。在
Config
类中,
getMode()
可能如下所示:

mode="Run"
public void Program
{
    public Mode mode;

    public static void main(String[] args)
    {
        mode = Config.getMode();
        mode.run();
    }
}
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
属性的值,就可以完全更改程序的功能。如果您有
公共运行模式
,您将无法使用这种类型的功能

为什么这是件好事


这种模式非常流行,因为它为扩展性打开了程序。这意味着,如果作者希望实现这种功能,只要进行最小的更改,就可以实现这种理想的功能。我是说,来吧。您可以更改配置文件中的一个单词,并完全更改程序流,而无需编辑一行代码。这是需要的。

超类实例=新的子类1()

在一些行之后,您可以执行
instance=newsubclass 2()

但是如果您编写,
SubClass1 instance=newsubclass1()


在一些行之后,您不能执行
instance=newsubclass 2()

这是因为他们不想知道实例是什么类型的子类。他们对超类的上下文很感兴趣。换句话说,它取决于您需要什么,而不是最佳实践。对吗?这确实是一个很好的答案,但这里的优势取决于这种特定的设计(例如,将引导模式实现为HashMap中的自定义对象),但我可以看到模块化/可扩展应用程序如何从这种实践中获益。谢谢你的回答。另一件事,这个(像这里的大多数其他答案一样)处理的是参数而不是变量。所以基本上它不能回答我的问题。但我想真正的答案是当地的