为什么将变量和对象声明为不同的类型?

时间:2013-11-20 00:12:42

标签: java inheritance

我有一个关于Java考试继承的问题。它是这样的:

class A{
    public int getInt(){
        return 0;
        }
    }

class B extends A{
    public int getInt(){
        return 60;
        }
    }

class C extends B{
    public int getInt(){
        return 150;
        }    
    }

class Z4{
    public static void main(String[] args){
        A c1 = new C();
        B c2 = new C();
        C c3 = new C();
        System.out.println(c1.getInt() + " " + c2.getInt() + " " + c3.getInt());
        }
    }

代码打印出“150 150 150”

我理解为什么会这样:变量是在运算符左侧表示的类型,但是对象是在运算符右侧表示的类型。由于所有对象都是C类,因此它们都使用C的重写方法。

此外,如果类C具有重载(而不是重写)方法,则前两个变量将无法使用它,因为它们的变量类型没有该方法签名。

另外,类型超类的变量可以引用类型子类的对象。但是类型子类的变量不能引用类型为超类的对象。 “Super myObject = new Sub();”作品。 “Sub myObject = new Super();”没有。

我的第一个问题是:上述陈述是否正确?我的第二个问题是:你什么时候这样做?

在我非常有限的经历中,我创建了类型超类的ArrayLists,并用各种子类的对象填充它。我看到它派上用场了。但是有没有一种情况下你专门创建与其对象不同类型的变量?你有没有输入“SuperClass myObject = new SubClass();”?我看不出它的实际用途。

5 个答案:

答案 0 :(得分:1)

利用抽象和多态性发挥作用。很难与人为的例子进行交流,例如测试中的例子。基本上它是关于延迟决定和允许替换。

在具有立即赋值的纯变量声明中,它有点微妙。使用返回的对象引用的示例可能更好(诚然欺骗了一点,因为我使用的是接口而不是超类):

public Collection makeSomeFancyCollection() {
}

此代码的调用者不需要知道或关心实际返回什么类型的Collection(并且他们当然不应该向下转发)。

Collection fancy = makeSomeFancyCollection();

可能有一天,返回Collection的不同实现的方法的实现比以前更有意义。呼叫者永远不会注意到,至少在编译时。

但是,如果该方法是这样写的:

public ArrayList makeSomeFancyCollectionConcrete() {
    …
}

如果实现更改为返回不同类型的集合,则需要更改并重新编译此类方法的调用方(如果它使用特定类型):

ArrayList fancy = makeSomeFancyCollectionConcrete();

然而,最明智的做法是:

Collection fancy = makeSomeFancyCollectionConcrete();

(只要Collection接口足够,无论如何)。

答案 1 :(得分:1)

  

“你有没有输入”SuperClass myObject = new SubClass();“?我看不出它的实际用途。”

 A c1 = new C();
 B c2 = new C();
 C c3 = new C();

或者这个

 List a = new ArrayList(); 

真的不是理解多态性的最好例子。第二种表示法主要用于因为它在我看来更简洁。从理论上讲,它可用于指定不同的实现,实际上它只发生在不到2%的情况下。

功能语言

在许多函数式语言(即Scala,Dart,Kotlin,静态Groovy,Java 8 lambdas)中,左侧部分被认为是多余的,整个事情看起来像:

val a = Array();          (Scala)
def a = new ArrayList();  (Groovy)

更好的例子可能是您构建一个模块化系统,该系统建议多个接口实现。这些实现映射通常是为整个项目配置的,即依赖注入库。

示例

即。您的模块通过Client接口接收电子邮件。电子邮件客户端可能支持不同的协议 - PopClientIMAPClient类。所以好处是使用此客户端接收电子邮件的逻辑不知道它是如何工作的 - 它使用Client接口。

答案 2 :(得分:0)

我喜欢使用的类比是动物超类和各种子类,如猫,狗,鸟,狮子等。 因此,对于您的第一个问题,此行有意义:Animal myBird = new Bird();Bird myBird = new Animal();不会。这是因为所有鸟类都是动物,但并非所有动物都是鸟类。所有动物都会继承像safhe()和move()这样的方法,但Bird可能会有其他方法,比如fly()。

拥有一个类型为Animal的arraylist来容纳一堆动物是你已经发现的一种用法。另一种可能的用法是,如果你有一个方法来获取超类的类型的对象。例如,Lion可以使用一个可以接受Animal对象的eat方法。然后你可以将Bird,Zebra,Fish或任何其他Animal传递给该方法。

答案 3 :(得分:0)

快速回答是你做抽象。您可能有一堆专门的类(例如CatDog),但您不想为每个类编写新方法。在某些情况下,你有一个方法(例如`feed()'),它在两个类上都是一样的。

你创建了一个超级课程Animal,你可以让CatDog扩展它。您使该方法的参数为Animal(例如feed(Animal a)),您可以将CatDog传递给它。

变量同样如此。您有一个可以容纳Animal的变量,因为您不关心它是Cat还是Dog。无论哪一个恰好放在那里都会没事的。

这使您可以编写一堆关于Animal的逻辑,这些逻辑不需要为您编写的每个CatDogFish类重复。最终,它是一种节省劳力的装置。

答案 4 :(得分:0)

简短回答是肯定的。这是一个较长答案的例子。我们来一个数据库连接。实际数据库可能在配置或运行时参数中指定,包括实际驱动程序类的名称。

DriverManager.getConnection(/* bunch of parameters to identify which connection */)

根据数据库驱动程序返回JdbcConnection的某些子类。可能是OracleConnection,PostgreqlConnection,甚至是CsvFileConnection。 (那些不是真正的驱动程序名称,但它们比真实的更明显!)。

在司机内部,我的信念是写起来更清楚

JdbcConnection value_to_return = new MyProprietaryDBConnection( /* all sorts of stuff */);

而不是依赖于回归时的隐式演员。

对于在其上调用的方法,仅使用 minimal 接口定义变量也是非常。如果您认为Collection应该是ArrayList而不是Set等,那么交换实现会更容易。

更新以回应评论:我还要提到,有时会看到右侧是匿名子类的分配。在这种情况下,必须使用实际匿名类的超类。