【问题标题】:Understanding inheritance and abstract classes in Java了解 Java 中的继承和抽象类
【发布时间】:2011-05-23 18:58:39
【问题描述】:

好的,所以我已经通过谷歌搜索寻找文档,但是我没有找到任何真正描述我想要回答的内容,所以我在这里问你们。

所以我得到了继承,以及它是如何工作的。我遇到的问题是有时我看到一个对象最初定义为一种类型,然后设置为不同的类型,我不明白到底发生了什么。这是一个例子:

假设我有一个动物类,以及扩展动物的猫和狗类。猫、动物和狗都有一个方法 speak(),猫打印“喵”,狗打印“woof”,动物“不会说话”。

好的,最后这是我的问题。如果 a 生成一只猫 (c) 然后运行 ​​Animal a = c; 会发生什么?如果我运行 a.speak(); 会发生什么?调用哪种说话方法?当我这样改变类型时到底发生了什么?我会有任何真正的理由使用它吗?

就抽象方法而言,我的问题是拥有它们到底有什么意义?在我看到的示例中,它们已被放入超类中,并且它们下的类定义了确切的行为。将抽象方法放在超类中,是不是要求其下的所有类都实现?

感谢您的帮助!

【问题讨论】:

    标签: java inheritance interface


    【解决方案1】:

    如果 a 制造一只猫会发生什么 (c) 然后运行 ​​Animal a = c;?什么 如果我运行 a.speak(); 会发生什么?哪一个 说话的方法叫什么?究竟是什么 当我改变类型时发生了 那?我会有任何真正的理由吗 用这个?

    始终是真实类的方法,例如在这种情况下,猫的speak() 方法。

    就抽象方法而言,我的 问题是这到底是什么意思 有他们吗?

    例如,他们确保每只动物都有一个方法walk(),您可以调用每只动物。这是一个保证,上面写着“每个Animal 对象都有这个方法,你不必关心它”。

    在我的例子中 看到他们被安排在超级班 并且它们下面的类定义了 确切的行为。通过放一个摘要 超类中的方法是一个 要求它下的所有类 实施吗?

    实现它或抽象化,是的。

    【讨论】:

      【解决方案2】:

      猫 c = 新猫(); 动物 a = c; a.speak() 将打印喵。

      请检查java polymorphism

      关于抽象类:

      当一个抽象类被子类化时, 子类通常提供 所有的实现 其父类中的抽象方法。 但是,如果没有,子类 也必须声明为抽象的。

      JLS,第 5.2 节解释了为什么 Cat 可以分配给 Animal。 (请注意,Animal 不能隐式分配给Cat,因为Cat 是一种“更具体”的类型;CatAnimal 的子类型,AnimalCat 的超类型)

      将编译时引用类型 S(源)的值分配给编译时引用类型 T(目标)的变量检查如下:

      • 如果 S 是类类型:
        • 如果 T 是类类型,则 S 必须与 T 属于同一类,或者 S 必须是 T 的子类,否则会发生编译时错误。
        • 如果 T 是接口类型,则 S 必须实现接口 T,否则会发生编译时错误。
        • 如果 T 是数组类型,则会发生编译时错误。
      • 如果 S 是接口类型:
        • 如果 T 是类类型,则 T 必须是 Object,否则会发生编译时错误。
        • 如果 T 是接口类型,则 T 必须是与 S 相同的接口或 S 的超接口,否则会发生编译时错误。
        • 如果 T 是数组类型,则会发生编译时错误。
      • 如果 S 是数组类型 SC[],即 SC 类型的组件数组:

      [为简洁省略]

      【讨论】:

      • 子类型多态性解释了a.speak(),但它没有解释为什么 Cat 可以隐式分配给 Animal。更多细节(JLS 链接会很好),这将是一个很好的答案。
      • 第 5.2 节包含 assignable 要求。 java.sun.com/docs/books/jls/second_edition/html/…。在其中搜索“将编译时引用类型 S(源)的值分配给编译时引用类型 T(目标)的变量检查如下”。
      • A Cat 可以隐式分配给Animal,因为Cat 扩展了Animal。一般来说,如果A 扩展B,那么A 可以分配给B。如果A 实现了一个接口C,那么它可以分配给C。这些关系是可传递的,因此如果X 扩展Y 而后者又扩展ZX 的实例可以分配给声明为XYZ 中的任何一个的变量.
      【解决方案3】:

      假设我有一个动物类,以及扩展动物的猫和狗类。猫、动物和狗都有一个方法 speak(),猫打印“喵”,狗打印“woof”,动物“不会说话”。

      好的,最后这是我的问题。如果制作一只猫(c)然后运行Animal a = c;,究竟会发生什么?如果我运行a.speak(); 会发生什么?调用了哪个speak() 方法?当我这样改变类型时到底发生了什么?我会有任何真正的理由使用它吗?

      Java 中的对象确切地知道它们被创建为什么类型;它实际上是一个隐藏字段(您可以使用 Object.getClass() 方法检索)。此外,所有非静态方法解析都从最具体的类的方法定义开始,然后向最通用的类​​进行(Object);因为在 Java 中只有单一的实现继承,所以这是一个简单的搜索。 Cat 知道它是 Animal 的子类型,Object 的子类型,c 知道它是 Cat,与变量的类型无关。

      当你进行赋值时,编译器会检查被赋值的已知类型是否是被赋值给的类型或其子类型之一。如果是,则分配有效。如果不是,则需要显式强制转换(在运行时进行适当的类型检查;强制转换不能破坏 Java 的类型系统,它们只会让它变得丑陋)。它并没有改变方法查找仍然是动态完成的事实,并且对象仍然知道它实际上是什么类型;程序所做的只是忽略了一些信息。 如果您了解 C++,那么可以将 Java 视为只有虚拟方法(和静态方法),并且处理对 vtable 的查找非常简单,因为继承菱形或其他邪恶情况没有问题。

      在处理接口的实现时,除了进行更复杂的查找之外几乎相同(即首先查找 vtable 中的索引,然后再像以前一样继续)。但是,拥有一个实现接口的对象意味着必须有一个完全实现接口的类,到那时一切都相对简单了。请记住,所有真正复杂的事情都是在编译时完成的;在运行时,所有情况下事情都相对简单。

      那么,你会利用这些吗?好吧,你应该(实际上你会发现在实际代码中很难避免)。考虑由接口或超类定义的契约是一种很好的风格,其中子类型遵循契约而调用者不必知道细节。 Java 库大量使用了这一点,特别是因为合约类型细节的可见性及其实现可能不同。客户端代码所知道的就是对象遵守契约,它是给定的类型。

      【讨论】:

      • 您提出的其他问题几乎得到了其他人的回答,并且在许多其他面向对象的语言中也有几乎相同的答案。
      【解决方案4】:

      根据您的背景,C++ 或 Java 的东西可能会变得非常混乱。

      在 C++ 中,有虚函数的概念,在运行时查找虚函数以确定该函数属于哪个实际类。还有一些非虚函数会根据变量类型被调用。

      虽然在 Java 中所有方法本质上都是虚拟的,这意味着 Java 方法总是在运行时查找,这个过程称为 运行时多态性

      这样做的好处是这样的

      class Animal{
          public void type(){
              System.out.println("animal");
      
          }
      }
      
      class Dog extends Animal{
          public void type(){
              System.out.println("dog");
          }
      }
      
      class Cat extends Animal{
          public void type(){
              System.out.println("cat");
          }
      }
      
      public class Driver{
          public static void main(String[] args){
              Animal[] animals = new Animal[3];
              animals[0] = new Animal();
              animals[1] = new Dog();
              animals[2] = new Cat();
      
              for(Animal animal: animals){
                  animal.type();
              }
          }
      }
      

      这将输出

      animal
      dog
      cat
      

      【讨论】:

        【解决方案5】:

        据我了解,您使用接口来解耦代码。您想针对接口而不是实现进行编程:What does it mean to "program to an interface"?。您将使用抽象类来实现对所有实现类来说都是微不足道的功能,因此您不需要在所有实现类中编写它。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-08-04
          • 2010-09-29
          • 1970-01-01
          • 1970-01-01
          • 2016-07-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多