【问题标题】:In java, Can we override a method by passing subclass of the parameter used in super class method?在java中,我们可以通过传递超类方法中使用的参数的子类来覆盖方法吗?
【发布时间】:2015-10-27 12:57:11
【问题描述】:

根据规则,在覆盖子类中的方法时,参数不能更改,并且必须与超类中的相同。 如果我们在覆盖方法时传递参数的子类怎么办? 会被称为重载还是覆盖?

根据我的查询,我在下面编写了一些代码。
我期待输出为“狗吃肉食”,但令我惊讶的是输出是“动物吃肉食” 如果有人能解释当分配的对象是 Dog 类型时如何调用 Animal 方法,将不胜感激?

    class Food {
        public String toString(){
            return "Normal Food";
        }
    }

    class Flesh extends Food {
        public String toString(){
            return "Flesh Food";
        }
    }

    class Animal {
        public void eat(Food food){
            System.out.println("Animal eats "+ food);
        }
    }

    class Dog extends Animal{

        public void eat(Flesh flesh){
            System.out.println("Dog eats "+ flesh);
        }
    }

    public class MyUtil {

        public static void main(String[] args) {

            Animal animal = new Dog(); 

            Flesh flesh = new Flesh();

            animal.eat(flesh);
        }
    }

【问题讨论】:

  • 您已经编写了代码 - 尝试一下,看看编译器是怎么说的。
  • 参见“访问者模式”
  • 编译器不会抱怨任何事情。实际上程序运行这就是为什么我能够看到输出但它很奇怪。如果我将该方法显式注释为@Override,编译器会抱怨,这意味着它没有被覆盖。你也会感到惊讶。只需在你的eclipse中复制代码,猜测输出,然后执行并验证实际输出。
  • 如果我在你的代码中使用animal.eat(new Food());,你希望发生什么?为什么?

标签: java overloading overriding


【解决方案1】:

Dog 中的方法eat 不会覆盖Animal 中的方法eat。这是因为参数不同(一个需要Flesh,另一个需要Food)。

eat 方法是重载。

在重载之间进行选择发生在编译时,而不是运行时。它不是基于调用方法的对象的实际类,而是基于编译时类型(如何声明变量)。

animal 具有编译时类型Animal。我们知道这一点是因为变量animal 的声明是Animal animal = ...。事实上,它实际上是一个 Dog 是无关紧要的 - 必须调用的是 Animal 中的 eat 的版本。

另一方面,Flesh 中的toString 方法确实覆盖了Food 中的toString 方法。

当一个方法覆盖另一个方法时,调用该方法的对象的实际类决定了运行哪个版本。

Animaleat方法中,即使参数是编译时类型Food,如果你传递一个Flesh的实例给它,它就是Flesh中的toString方法这将执行。

因此您收到消息"Animal eats Flesh Food"

【讨论】:

    【解决方案2】:

    不,我们不能在这种情况下,您可以通过在 Dog 类的 eat 方法上方添加 @Override 来检查它,因此编译错误将如下所示:

    @Override
    public void eat(Flesh flesh){
       System.out.println("Dog eats "+ flesh);
    }
    

    确保在 override 中的参数在类型和顺序上应该是父类。

    【讨论】:

      【解决方案3】:

      您可以使用@override 注解通知编译器您正在尝试覆盖超类中的方法。

      例如

       class Dog extends Animal{
      
          @Override
          public void eat(Flesh flesh){
              System.out.println("Dog eats "+ flesh);
          }
       }
      

      在你的代码中,现在编译器会报错,因为这个方法没有覆盖eat,你需要有相同的参数类型。

      但是,将Flesh 参数更改为Food 类型将解决问题:

      class Dog extends Animal{
      
          @Override
          public void eat(Food food){
              System.out.println("Dog eats "+ food);
          }
      }
      

      现在您可以执行以下操作:

      public class MyUtil {
      
          public static void main(String[] args) {
      
              Animal animal = new Dog(); 
      
              Food food = new Flesh();
      
              animal.eat(food);
          }
      }
      

      【讨论】:

        【解决方案4】:

        当你这样做时: Animal animal = new Dog(); 正在向上转型。

        而且你不会覆盖该方法

        public void eat(Flesh flesh){
            System.out.println("Dog eats "+ flesh);
        }
        

        所以它调用eat(Food food)的方法Animal

        【讨论】:

          【解决方案5】:

          在java中,我们可以通过传递超类方法中使用的参数的子类来覆盖方法吗?

          没有。

          举例说明:

          假设我们可以通过传递超类方法中使用的参数的子类来覆盖一个方法。

          class A {
              public void print(Oject obj) 
              {
                System.out.println(obj.toString());
               }
          }
          
          class B extends A {
              @Override
               public void print(String str) 
               {
                  System.out.println(str.toString());
               }
          }
          
          class C extends A {
             @Override
             public void print(Integer in) {
                System.out.println(in.toString());
             }
          }
          
          class MainTest {
             public static void main(String args[]) 
             {
                Integer in = new Integer(3);
                A a = new B(); // developer by mistake types new B() instead of new C()
                a.print(in);  // compiler will allow this because class A has print method which can take any subclass of Object type(in this case any class because Object is superclass of all classes in java).
              }
          }
          

          但是想想运行时会发生什么?

          在运行时,在引用指向的实际对象上调用方法。在我们的例子中,它指向 B 类。 因此 JVM 将查看 B 类以执行方法,这对 JVM 来说将是一个巨大的冲击,因为 B 类没有被覆盖 带有签名 public void print(Integer in) 的方法,它将在运行时失败。

          这样做的后果是应用程序将在运行时而不是编译时崩溃。

          Java 中的覆盖规则:

          • 参数必须相同,返回类型必须兼容
          • 该方法的可访问性不容小觑

          ==============================

          在你的例子中会发生什么:

          编译期间:编译器确保可以在引用类型(即 Aniaml 类)上调用 eat 方法。 由于 Animal 类有一个方法 public void eat(Food food) 可以采用任何 Food 类型和子类 食物类型作为输入参数(多态性),编译通过。

          一般来说,编译器保证特定方法可用于特定引用类型。

          在运行时: 调用的实际方法是该对象类型(Dog 类)的方法的最具体版本。因为 JVM 无法找到被覆盖的方法,即声明的方法名称和参数类型相同 在子类(Dog 类)中,检查其父类(Animal 类)并找到方法并执行。

          public void eat(Food food)公共无效吃(肉) 从覆盖方法的角度来看是不一样的。

          通常,当您在对象引用上调用方法时,您正在调用该方法的最具体版本 运行时的对象类型。但是,如果它没有找到一个,它会上升到层次结构。最坏的情况下,超级类肯定会有 方法实现,否则编译不会通过。

          【讨论】:

          • 好吧,实际上情况并非如此:如果编译器看到 A::print,它将为 A::print 调用放置字节码,甚至不会尝试放置 B::print 调用如果 A a =new B(),因为编译器从不猜测类型,它只解决方程。这里的情况只是覆盖另一个方法的方法必须具有相同的参数类型,否则它不是覆盖,它是一个新方法
          【解决方案6】:
          To be able to "dynamically load the subclass at runtime", use of generics makes it work perfectly.
          
          
          public class HelloWorld{
          
               public static void main(String []args){
                  Animal animal = new Dog(); 
          
                  Food flesh = new Flesh();
          
                  animal.eat(flesh);
               }
          }
          
           class Food {
                  public String toString(){
                      return "Normal Food";
                  }
              }
          
              class Flesh extends Food {
                  public String toString(){
                      return "Flesh Food";
                  }
              }
          
              class Animal<T extends Food> {
                  public void eat(T food){
                      System.out.println("Animal eats "+ food);
                  }
              }
          
              class Dog extends Animal<Flesh>{
                  
                  public void eat(Flesh flesh){
                      System.out.println("Dog eats "+ flesh);
                  }
              }
          

          【讨论】:

          • 可能会添加一段关于以下事实的段落,即由于问题中的另一个参数类型以及为什么您的解决方案会起作用,eat 方法没有被覆盖
          • 欢迎来到 SO,回答问题时不要只提供代码。添加一些有关代码以及您如何解决问题的信息。
          【解决方案7】:

          事实上你不能@Override 一个不同签名的方法,看看这个例子:

          public class Clase1 {
              
              public void metodo1 (ClasePadre atr1) {
                  System.out.println("Hola soy Clase1");
              }
          }
          
          public class Clase2 extends Clase1 {
              
              public void metodo1 (ClaseHija atr1) {
                  System.out.println("Hola soy Clase2");
              }
          
          }
          
          public class ClasePadre {
              
              public void metodo1 () {
                  System.out.println("Hola soy ClasePadre");
              }
          }
          
          public class ClaseHija extends ClasePadre {
              
              public void metodo1 () {
                  System.out.println("Hola soy ClaseHija");
              }
          }
          

          当你做一个主方法并实例化这个 Clase2 时,clase2 = new Clase2();你会看到两个不同的方法metodo1(ClaseHija atr1)和metodo2(ClasePadre atr1),因此你是重载而不是覆盖。

          【讨论】:

            猜你喜欢
            • 2016-03-21
            • 2012-08-12
            • 1970-01-01
            • 2021-05-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-01-30
            相关资源
            最近更新 更多