【问题标题】:Overriding vs Hiding Java - Confused覆盖与隐藏 Java - 困惑
【发布时间】:2024-01-08 04:09:01
【问题描述】:

我对覆盖与隐藏在 Java 中的区别感到困惑。任何人都可以提供有关这些差异的更多详细信息吗?我阅读了Java Tutorial,但示例代码仍然让我感到困惑。

更清楚地说,我很了解覆盖。我的问题是我看不出隐藏有什么不同,除了一个在实例级别而另一个在类级别。

看Java教程代码:

public class Animal {
    public static void testClassMethod() {
        System.out.println("Class" + " method in Animal.");
    }
    public void testInstanceMethod() {
        System.out.println("Instance " + " method in Animal.");
    }
}

然后我们有一个子类Cat

public class Cat extends Animal {
    public static void testClassMethod() {
        System.out.println("The class method" + " in Cat.");
    }
    public void testInstanceMethod() {
        System.out.println("The instance method" + " in Cat.");
    }

    public static void main(String[] args) {
        Cat myCat = new Cat();
        Animal myAnimal = myCat;
        Animal.testClassMethod();
        myAnimal.testInstanceMethod();
    }
}

然后他们说:

这个程序的输出如下:

Animal 中的类方法。

Cat中的实例方法

对我来说,直接从Animal 类调用类方法testClassMethod() 会执行Animal 类中的方法这一事实非常明显,没有什么特别之处。然后他们从对myCat 的引用中调用testInstanceMethod(),因此很明显,随后执行的方法是Cat 实例中的方法。

据我所知,调用隐藏的行为就像覆盖一样,那么为什么要区分呢?如果我使用上面的类运行此代码:

Cat.testClassMethod();

我会得到: Cat 中的类方法 但是,如果我从 Cat 中删除 testClassMethod(),那么我会得到: Animal 中的类方法。

这告诉我,在子类中编写一个与父类具有相同签名的静态方法几乎可以实现覆盖。

希望我能弄清楚我在哪里感到困惑,并且有人可以解释一下。提前非常感谢!

【问题讨论】:

标签: java inheritance methods overriding method-hiding


【解决方案1】:

覆盖基本上支持后期绑定。因此,在运行时决定调用哪个方法。它适用于非静态方法。

隐藏适用于所有其他成员(静态方法、实例成员、静态成员)。它基于早期绑定。更清楚地说,要调用或使用的方法或成员是在编译时决定的。

在您的示例中,第一次调用 Animal.testClassMethod() 是对 static 方法的调用,因此很确定将调用哪个方法。

在第二个调用myAnimal.testInstanceMethod() 中,您调用了一个非静态方法。这就是你所说的运行时多态性。直到运行时才决定调用哪个方法。

如需进一步说明,请阅读Overriding Vs Hiding

【讨论】:

  • 感谢您的快速回答,这澄清了它!我注意到在 JavaRanch 示例中,他们使用变量来调用类方法,而不是直接使用类,这样更容易理解。我猜在 Java 教程中他们直接使用了类,因为使用实例调用静态方法可能不是好的做法,但他们应该使用 myAnimal.testClassMethod() 而不是 Animal。 testClassMethod().
  • +1 因为能够正确地用文字而不是举例说明! :)
  • @Kazekage Gaara 重载和隐藏有区别吗?
  • 我当然同意这个答案,但是private methods 怎么样?它们不能是overridden,因为子类不知道它们的存在。因此它们可能是hidden
  • 优秀的答案!尽管为了完整起见,您可能会在 coderanch 添加示例:)
【解决方案2】:

静态方法被隐藏,非静态方法被覆盖。 当调用不限定“something()”与“this.something()”时,差异是显着的。

我似乎真的无法把它放在文字上,所以这里举个例子:

public class Animal {

    public static void something() {
        System.out.println("animal.something");
    }

    public void eat() {
        System.out.println("animal.eat");
    }

    public Animal() {
        // This will always call Animal.something(), since it can't be overriden, because it is static.
        something();
        // This will call the eat() defined in overriding classes.
        eat();
    }

}


public class Dog extends Animal {

    public static void something() {
        // This method merely hides Animal.something(), making it uncallable, but does not override it, or alter calls to it in any way.
        System.out.println("dog.something");
    }

    public void eat() {
        // This method overrides eat(), and will affect calls to eat()
        System.out.println("dog.eat");
    }

    public Dog() {
        super();
    }

    public static void main(String[] args) {
        new Dog();
    }

}

输出:

animal.something
dog.eat

【讨论】:

  • 好的,那么如果我调用 `dog husky = new dog();' 会发生什么并调用husky.Animal(); 它会打印animal.something 还是dog.something?我想它 错误地说 ** 那 **这将始终调用 Animal.something()
  • @amarnathharish 你不能做.Animal(),记住Animal()是一个构造函数。
  • 对于任何想知道的人进一步澄清,Animal() 中的something() always 调用的 Animal 的 something() 的原因是因为对静态方法的调用在编译时而不是运行时。这意味着Animal() 中的静态方法调用总是隐式调用Animal.something()。如果您考虑一下,这非常直观:除非调用在同一个类中,否则对静态方法的调用必须以类名(即className.staticMethodName())开头。
【解决方案3】:

这就是覆盖和隐藏的区别,

  1. 如果父类和子类中的方法都是实例方法,则称为overrides。
  2. 如果父类和子类中的方法都是静态方法,则称为隐藏。
  3. 一个方法在父级中不能是静态的,在子级中不能作为实例。反之亦然。

【讨论】:

  • 您直接从 OP 说的教程中剪切并粘贴了该表,他说这并不能帮助他理解。
  • 表格很清楚,在示例中并未考虑所有情况。
【解决方案4】:

如果我正确理解你的问题,那么答案是“你已经在压倒一切”。

“这告诉我,在子类中编写一个与父类同名的静态方法几乎可以实现覆盖。”

如果您在子类中编写的方法与超类中的方法名称完全相同,它将覆盖超类的方法。 @Override 注解不需要覆盖方法。但是,它确实使您的代码更具可读性,并强制编译器检查您是否实际上覆盖了一个方法(例如,没有拼错子类方法)。

【讨论】:

  • 这个答案无法解决关于覆盖/隐藏的实例与静态方法。
【解决方案5】:

覆盖只发生在实例方法中。 当引用变量的类型是 Animal 并且对象是 Cat 时,从 Cat 调用实例方法(这是覆盖)。对于同一个 acat 对象,使用 Animal 的类方法。

public static void main(String[] args) {
    Animal acat = new Cat();
    acat.testInstanceMethod();
    acat.testClassMethod();

}

输出是:

The instance method in Cat.
Class method in Animal.

【讨论】:

    【解决方案6】:
    public class First {
    
        public void Overriding(int i) {  /* will be overridden in class Second */ }
    
        public static void Hiding(int i) {  /* will be hidden in class Second
                                               because it's static */ }
    }
    
    
    public class Second extends First {
    
        public void Overriding(int i) {  /* overridden here */  }
    
        public static void Hiding(int i) {  /* hides method in class First
                                               because it's static */ } 
    }
    

    记忆规则很简单:扩展类中的方法 不能将静态更改为无效和 无法将 void 更改为 static。 这会导致编译错误。

    但如果void Name 更改为void Name,它就是覆盖。

    如果static Name 更改为static Name,它就是隐藏。 (子类的静态方法和超类的静态方法都可以调用,这取决于用于调用该方法的引用的类型。)

    【讨论】:

      【解决方案7】:

      在这段代码 sn-p 中,我使用“private”访问修饰符而不是“static”来向您展示隐藏方法和覆盖方法之间的区别。

      class Animal {
      // Use 'static' or 'private' access modifiers to see how method hiding work.
      private void testInstancePrivateMethod(String source) {
          System.out.println("\tAnimal: instance Private method calling from "+source);
      }
      public void testInstanceMethodUsingPrivateMethodInside() {
          System.out.println("\tAnimal: instance Public method with using of Private method.");
          testInstancePrivateMethod( Animal.class.getSimpleName() );
      }
      
      // Use default, 'protected' or 'public' access modifiers to see  how method overriding work.
      protected void testInstanceProtectedMethod(String source) {
          System.out.println("\tAnimal: instance Protected method calling from "+source);
      }
      public void testInstanceMethodUsingProtectedMethodInside() {
          System.out.println("\tAnimal: instance Public method with using of Protected method.");
          testInstanceProtectedMethod( Animal.class.getSimpleName() );
        } 
      }  
      
      
      public class Cat extends Animal {
      private void testInstancePrivateMethod(String source) {
          System.out.println("Cat: instance Private method calling from " + source );
      }
      public void testInstanceMethodUsingPrivateMethodInside() {
          System.out.println("Cat: instance Public method with using of Private method.");
          testInstancePrivateMethod( Cat.class.getSimpleName());
          System.out.println("Cat: and calling parent after:");
          super.testInstanceMethodUsingPrivateMethodInside();
      }
      
      protected void testInstanceProtectedMethod(String source) {
          System.out.println("Cat: instance Protected method calling from "+ source );
      }
      public void testInstanceMethodUsingProtectedMethodInside() {
          System.out.println("Cat: instance Public method with using of Protected method.");
          testInstanceProtectedMethod(Cat.class.getSimpleName());
          System.out.println("Cat: and calling parent after:");
          super.testInstanceMethodUsingProtectedMethodInside();
      }
      
      public static void main(String[] args) {
          Cat myCat = new Cat();
          System.out.println("----- Method hiding -------");
          myCat.testInstanceMethodUsingPrivateMethodInside();
          System.out.println("\n----- Method overriding -------");
          myCat.testInstanceMethodUsingProtectedMethodInside();
      }
      }
      

      输出:

      ----- Method hiding -------
      Cat: instance Public method with using of Private method.
      Cat: instance Private method calling from Cat
      Cat: and calling parent after:
         Animal: instance Public method with using of Private method.
         Animal: instance Private method calling from Animal
      
      ----- Method overriding -------
      Cat: instance Public method with using of Protected method.
      Cat: instance Protected method calling from Cat
      Cat: and calling parent after:
         Animal: instance Public method with using of Protected method.
      Cat: instance Protected method calling from Animal
      

      【讨论】:

      • 想知道为什么它没有得到支持......非常感谢答案。
      【解决方案8】:

      我认为这还没有完全解释。 请看下面的例子。

      class Animal {
          public static void testClassMethod() {
              System.out.println("The static method in Animal");
          }
          public void testInstanceMethod() {
              System.out.println("The instance method in Animal");
          }
      }
      
      
      public class Cat extends Animal {
          public static void testClassMethod() {
              System.out.println("The static method in Cat");
          }
          public void testInstanceMethod() {
              System.out.println("The instance method in Cat");
          }
      
          public static void main(String[] args) {
              Animal myCat = new Cat();
              Cat myCat2 = new Cat();
              myCat.testClassMethod();
              myCat2.testClassMethod();
              
              
              myCat.testInstanceMethod();
              myCat2.testInstanceMethod();
          }
      }
      

      输出如下。

      The static method in Animal
      The static method in Cat
      The instance method in Cat
      The instance method in Cat
      

      【讨论】:

        【解决方案9】:

        基于我最近的 Java 研究

        • 方法覆盖,当子类在子类中具有相同的方法且具有相同的签名时。
        • 方法隐藏,当子类具有相同的方法名称,但不同的参数。在这种情况下,您没有覆盖父方法,而是隐藏它。

        来自 OCP Java 7 书籍第 70-71 页的示例:

        public class Point {
          private int xPos, yPos;
          public Point(int x, int y) {
                xPos = x;
                yPos = y;
          }
        
          public boolean equals(Point other){
          .... sexy code here ...... 
          }
        
          public static void main(String []args) {
           Point p1 = new Point(10, 20);
           Point p2 = new Point(50, 100);
           Point p3 = new Point(10, 20);
           System.out.println("p1 equals p2 is " + p1.equals(p2));
           System.out.println("p1 equals p3 is " + p1.equals(p3));
           //point's class equals method get invoked
          }
        }
        

        但是如果我们写下面的 main:

          public static void main(String []args) {
           Object p1 = new Point(10, 20);
           Object p2 = new Point(50, 100);
           Object p3 = new Point(10, 20);
           System.out.println("p1 equals p2 is " + p1.equals(p2));
           System.out.println("p1 equals p3 is " + p1.equals(p3));
           //Object's class equals method get invoked
          }
        

        在第二个main中,我们使用Object类作为静态类型,所以当我们在Point对象中调用equal方法时,它正在等待Point类作为参数到达,但是Object来了。所以 Object 类的 equals 方法开始运行,因为我们有一个 equals(Object o) 。在这种情况下,Point 的 equals 类不会覆盖,而是隐藏 Object 类的 equals 方法

        【讨论】:

          【解决方案10】:
          public class Parent {
          
            public static void show(){
              System.out.println("Parent");
            }
          }
          
          public class Child extends Parent{
          
            public static void show(){
              System.out.println("Child");
            }
          }
          
          public class Main {
          
          public static void main(String[] args) {
              Parent parent=new Child();
              parent.show(); // it will call parent show method
            }
          }
          
          // We can call static method by reference ( as shown above) or by using class name (Parent.show())
          

          【讨论】:

            【解决方案11】:

            链接的java教程页面解释了覆盖和隐藏的概念

            子类中具有相同签名(名称,加上其参数的编号和类型)和返回类型的实例方法作为超类中的实例方法会覆盖超类的方法。

            如果子类定义了与父类中的静态方法具有相同签名的静态方法,则子类中的方法会隐藏超类中的方法。

            隐藏静态方法和覆盖实例方法之间的区别具有重要意义:

            1. 被调用的重写实例方法的版本是子类中的版本。
            2. 被调用的隐藏静态方法的版本取决于它是从超类调用还是从子类调用。

            回到你的例子:

            Animal myAnimal = myCat;
            
             /* invokes static method on Animal, expected. */
             Animal.testClassMethod(); 
            
             /* invokes child class instance method (non-static - it's overriding) */
             myAnimal.testInstanceMethod();
            

            上面的语句还没有显示隐藏。

            现在更改如下代码以获得不同的输出:

              Animal myAnimal = myCat;
              
              /* Even though myAnimal is Cat, Animal class method is invoked instead of Cat method*/
              myAnimal.testClassMethod();
              
              /* invokes child class instance method (non-static - it's overriding) */
              myAnimal.testInstanceMethod();
            

            【讨论】:

            • 我试图理解隐藏本身这个词的含义。什么是隐藏什么?父类中的静态方法是否因为(最不期望的)被调用而隐藏?还是子类中的静态方法被隐藏了,因为它没有被调用?
            【解决方案12】:

            除了上面列出的示例之外,这里还有一个小示例代码来阐明隐藏和覆盖之间的区别:

            public class Parent {
            
                // to be hidden (static)
                public static String toBeHidden() {
                    return "Parent";
                }
            
                // to be overridden (non-static)
                public String toBeOverridden() {
                    return "Parent";
                }
            
                public void printParent() {
                    System.out.println("to be hidden: " + toBeHidden());
                    System.out.println("to be overridden: " + toBeOverridden());
                }
            }
            
            public class Child extends Parent {
            
                public static String toBeHidden() {
                    return "Child";
                }
            
                public String toBeOverridden() {
                    return "Child";
                }
            
                public void printChild() {
                    System.out.println("to be hidden: " + toBeHidden());
                    System.out.println("to be overridden: " + toBeOverridden());
                }
            }
            
            public class Main {
            
                public static void main(String[] args) {
                    Child child = new Child();
                    child.printParent();
                    child.printChild();
                }
            }
            

            child.printParent() 的调用输出:
            要隐藏:父母
            被覆盖:孩子

            child.printChild() 的调用输出:
            被隐藏:孩子
            被覆盖:孩子

            从上面的输出(尤其是粗体标记的输出)可以看出,方法隐藏与覆盖的行为不同。

            Java 只允许隐藏和覆盖方法。同样的规则不适用于变量。不允许覆盖变量,因此只能隐藏变量(静态变量和非静态变量之间没有区别)。下面的例子展示了方法getName()是如何被覆盖并且变量name是隐藏的:

            public class Main {
            
                public static void main(String[] args) {
                    Parent p = new Child();
                    System.out.println(p.name); // prints Parent (since hiding)
                    System.out.println(p.getName()); // prints Child (since overriding)
                }
            }
            
            class Parent {
                String name = "Parent";
            
                String getName() {
                    return name;
                }
            }
            
            class Child extends Parent {
                String name = "Child";
            
                String getName() {
                    return name;
                }
            }
            

            【讨论】:

              【解决方案13】:

              在运行时,总是为实例执行被覆盖方法的子版本 无论方法调用是在父类方法还是子类方法中定义。在这个 方式,除非显式调用父方法,否则永远不会使用父方法 引用,使用语法 ParentClassName.method()。 或者,在运行时父 如果对方法的调用在 父类。

              【讨论】:

                【解决方案14】:

                overriding方法中,方法解析是由JVM根据运行时对象来完成的。而在方法隐藏中,方法解析是由编译器在引用的基础上完成的。 因此,

                如果代码可以写成,

                public static void main(String[] args) {
                        Animal myCat = new Cat();        
                        myCat.testClassMethod();        
                    }
                

                输出如下:
                Animal 中的类方法。

                【讨论】:

                  【解决方案15】:

                  之所以称为隐藏,是因为当子类具有相同的静态方法时,编译器会隐藏超类方法的实现。

                  编译器对重写的方法没有限制可见性,只有在运行时才决定使用哪一个。

                  【讨论】:

                    【解决方案16】:

                    这是覆盖和隐藏的区别:

                    动物 a = new Cat();

                    a.testClassMethod() 将调用父类中的方法,因为它是方法隐藏的示例。调用的方法由引用变量的类型决定,在编译时决定。

                    a.testInstanceMethod() 将调用子类中的方法,因为它是方法覆盖的示例。调用的方法由运行时调用该方法的对象决定。

                    【讨论】:

                      【解决方案17】:

                      静态方法隐藏在java中是如何发生的? Cat 类正在扩展 Animal 类。所以在 Cat 类中会有两个静态方法(我的意思是子类的静态方法和父类的静态方法) 但是JVM如何隐藏Parent静态方法?它在堆和堆栈中的处理方式如何?

                      【讨论】:

                      • 这不是答案。这是所提问题的延伸。它本身可能是一个单独的问题,也可能是该问题的一部分。