【问题标题】:Why declare variables and objects as different types?为什么将变量和对象声明为不同的类型?
【发布时间】:2013-11-20 00:12:42
【问题描述】:

我在 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 类有一个重载(而不是重载)方法,前两个变量将无法使用它,因为它们的变量类型没有该方法签名。

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

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

根据我非常有限的经验,我制作了超类类型的 ArrayList,并用各种子类的对象填充它。我看到它派上用场了。但是是否存在您专门创建与其对象不同类型的变量的情况?你输入过“SuperClass myObject = new SubClass();”吗?我看不出有实际用途。

【问题讨论】:

  • 我的猜测是你过于关注它是一个局部变量的事实。为了保持测试的可读性,这可能是一种简化,仅此而已。看到 C# 的 var 关键字(隐式将变量类型作为其初始化表达式的类型)可能是常用的,我猜下面答案中给出的原因比其他任何东西都更具追溯性。至于我自己,我会使用List foo = new ArrayList(),因为List 打字更短。
  • @millimoose 我认为 C# var 或 C++ auto 与此处无关。首先,在具有该特性的语言中,将实现从例如 HashSet 更改为 Tree 或反之亦然,甚至更容易。您可以在任何合理的 Java 指南或更早的 S.O. 中找到将变量声明为接口的建议。 stackoverflow.com/questions/1484445/…
  • @AndrewLazarus 我主要是在解决这个答案,说它比通过比较返回时隐含的向上转换要好。另外,我想说的是,当您不依赖于该实现的具体细节时,换出实现(我认为这对于局部变量及其非常有限的范围是一个绝对可笑的问题)更容易,实际上是在输入因为界面将帮助您完成。在搜索和替换框中保存一次单击似乎并不是一个很大的实际优势,尤其是在静态类型语言中。

标签: java inheritance


【解决方案1】:

在利用抽象和多态时发挥作用。很难与测试中的人为示例进行交流。基本上它是关于延迟决策并允许替换。

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

public Collection makeSomeFancyCollection() {
}

此代码的调用者不需要知道或关心实际返回的 Collection 类型(他们当然不应该沮丧)。

Collection fancy = makeSomeFancyCollection();

也许有一天,该方法的实现返回与以前不同的 Collection 实现是很有意义的。调用者永远不会注意到,至少在编译时是这样。

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

public ArrayList makeSomeFancyCollectionConcrete() {
    …
}

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

ArrayList fancy = makeSomeFancyCollectionConcrete();

但是,最明智的做法是:

Collection fancy = makeSomeFancyCollectionConcrete();

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

【讨论】:

  • 顺便说一句,我喜欢@connor 使用的类比。我试图解决“为什么将更具体的类型实例分配给不太具体的引用”。
【解决方案2】:

“你输入过“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接口。

【讨论】:

    【解决方案3】:

    我喜欢使用的一个类比是拥有一个 Animal 超类和各种子类,例如 Cat、Dog、Bird、Lion 等。 所以对于你的第一个问题,这条线是有意义的:Animal myBird = new Bird();Bird myBird = new Animal(); 不会。那是因为所有的鸟都是动物,但并非所有的动物都是鸟。所有的 Animals 都会继承像 Breath() 和 move() 这样的方法,但是 Bird 可能有像 fly() 这样的额外方法。

    拥有一个 Animal 类型的数组列表来保存一堆 Animal 是您已经发现的一种用途。另一种可能的用途是,如果您有一个采用超类类型的对象的方法。例如,Lion 可以有一个可以获取 Animal 对象的 eat 方法。然后你可以将鸟、斑马、鱼或任何其他动物传递给该方法。

    【讨论】:

      【解决方案4】:

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

      你创建了一个超类 Animal 并同时扩展了 CatDog。您使该方法采用Animal 的参数(例如feed(Animal a)),您可以将CatDog 传递给它。

      变量也一样。您有一个可以保存Animal 的变量,因为您不在乎它是Cat 还是Dog。随便放哪一个都行。

      这允许您编写一堆关于Animal 的逻辑,不需要为您编写的每个CatDogFish 类重复。归根结底,它是一种节省劳动力的设备。

      【讨论】:

        【解决方案5】:

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

        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 等,这使得交换实现变得更容易。

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

        【讨论】:

        • 你的“信念”(风格偏好)并不完全是技术原因。
        • @millimoose 我不太确定我是否有这个分歧。没有技术理由不使用好的变量名。不过,关于匿名子类,我还要补充一点。
        • 好的,但是好的变量名是好的,因为它们更具描述性。 (另外我不认为像“为什么我应该使用长变量名?”这样的问题首先适用于 SO,或者关于该主题的任何扩展讨论。)我不确定你能否提出具体的品质,甚至是主观的品质,使得返回时的隐式向上转换不如赋值中的隐式向上转换清晰,或者在返回某种表达式时,方法体中根本没有显式类型信息。这是一个相当特殊的情况。
        猜你喜欢
        • 2016-07-03
        • 2012-07-23
        • 1970-01-01
        • 1970-01-01
        • 2011-04-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多