📜  用Java重写Override

📅  最后修改于: 2020-03-19 12:26:44             🧑  作者: Mango

在任何面向对象的编程语言中,重写Override都是一项功能,它允许子类提供其超类或父类之一已提供的方法的特定实现。当子类中的方法与其父类中的方法具有相同的名称、相同的参数或签名以及相同的返回类型(或子类型)时,则该子类中的方法被称为重写父类中的方法。

方法重写是Java实现运行时多态的一种方法。执行方法的版本将由调用它的对象确定。如果使用父类的对象来调用该方法,则将执行父类中的版本,但是如果使用子类的对象来调用该方法,则将执行子类中的版本。换句话说,确定要执行哪个版本的重写方法,取决于被引用对象的类型(而不是引用变量的类型)。

// Java展示方法重写
class Parent {
    void show()
    {
        System.out.println("父类的 show()");
    }
}
// 集成类class
class Child extends Parent {
    // 重写父类的show()
    @Override
    void show()
    {
        System.out.println("子类的 show()");
    }
}
// 测试代码
class Main {
    public static void main(String[] args)
    {
        
        Parent obj1 = new Parent();
        obj1.show();

        // 多态.
        Parent obj2 = new Child();
        obj2.show();
    }
}

输出:

父类的 show()
子类的 show()

方法重写的规则:

  1. 重写和访问修饰符:覆盖方法的访问修饰符可以允许比重写方法更多的访问。例如,超类中的受保护实例方法可以在子类中公开,但不能私有。这样做会产生编译时错误。
    // Java展示重写和访问修饰符
    
    class Parent {
        // private 方法没有被重写
        private void m1()
        {
            System.out.println("父类的 m1()");
        }
        protected void m2()
        {
            System.out.println("父类的 m2()");
        }
    }
    class Child extends Parent {
        // 新的m1()方法
        private void m1()
        {
            System.out.println("子类的 m1()");
        }
        // 重写方法
        @Override
        public void m2()
        {
            System.out.println("子类的 m2()");
        }
    }
    // 测试代码
    class Main {
        public static void main(String[] args)
        {
            Parent obj1 = new Parent();
            obj1.m2();
            Parent obj2 = new Child();
            obj2.m2();
        }
    }

    输出:

    父类的 m2()
    子类的 m2()
  2. 不能重写final方法:如果我们不希望重写某个方法,则将其声明为final
    // Java展示final修饰符不能被重写
    class Parent {
        // 无法被重写
        final void show() {}
    }
    class Child extends Parent {
        // 这会报错
        void show() {}
    }

    输出:

    13: error: show() in Child cannot override show() in Parent
        void show() {  }
             ^
      overridden method is final
  3. 静态方法不能被重写(Method Overriding vs Method Hiding):当您定义一个静态方法与基类中的静态方法具有相同签名的静态方法时,称为方法隐藏下表总结了在定义具有与超类中的方法相同的签名的方法时发生的情况。
    超类实例方法 超类静态方法
    子类实例方法 重写 产生编译时错误
    子类静态方法 产生编译时错误 隐藏
    // Java展示如果一个派生类重写了static方法,该方法被隐藏了
    class Parent {
        // Static方法
        static void m1()
        {
            System.out.println("父类 "
                               + "静态 m1()");
        }
        // 非静态方法
        void m2()
        {
            System.out.println("父类 "
                               + "非静态实例 m2()");
        }
    }
    class Child extends Parent {
        // 如下方法隐藏了父类的m1方法
        static void m1()
        {
            System.out.println("子类静态 m1()");
        }
        // 如下方法重写了父类的m2()
        @Override
        public void m2()
        {
            System.out.println("子类 "
                               + "非静态实例 m2()");
        }
    }
    // 测试代码
    class Main {
        public static void main(String[] args)
        {
            Parent obj1 = new Child();
    
            obj1.m1();
    
            obj1.m2();
        }
    }

    输出:

    父类 静态 m1()
    子类 非静态实例 m2()
  4. 私有方法不能被重写: 私有方法不能被重写,因为它们在编译期间是绑定的。因此,我们也无法重写子类中的私有方法。
  5. 重写方法必须具有相同的返回类型(或子类型):从Java 5.0开始,子类中的重写方法可能具有不同的返回类型,但是子对象的返回类型应该是父级返回类型的子类型。这种现象称为协变返回类型
  6. 从子类中调用重写方法:我们可以使用super关键字在重写方法中调用父类方法。
    // Java展示子类调用被重写的父类方法
    // 基类Class
    class Parent {
        void show()
        {
            System.out.println("父类 show()");
        }
    }
    // 子类集成
    class Child extends Parent {
        // 这个方法重写父类的show()
        @Override
        void show()
        {
            super.show();
            System.out.println("子类 show()");
        }
    }
    // 测试代码
    class Main {
        public static void main(String[] args)
        {
            Parent obj = new Child();
            obj.show();
        }
    }

    输出:

    父类 show()
    子类 show()
  7. 重写和构造函数:我们不能重写构造函数,因为父类和子类永远不能具有相同名称的构造函数(构造函数名称必须始终与类名称相同)
  8. 重写和异常处理:重写与异常处理相关的方法时,需要注意以下两个规则。
    • 规则1:如果超类重写方法没有引发异常,则子类重写方法只能引发未检查的异常,抛出检查的异常将导致编译时错误。
      // Java展示父类没有抛出异常
      class Parent {
          void m1()
          {
              System.out.println("父类 m1()");
          }
          void m2()
          {
              System.out.println("父类  m2()");
          }
      }
      class Child extends Parent {
          @Override
          // 报出未检查的异常是可以的
          void m1() throws ArithmeticException
          {
              System.out.println("子类 m1()");
          }
          @Override
          // 编译错误
          void m2() throws Exception
          {
              System.out.println("子类 m2");
          }
      }

      输出:

      error: m2() in Child cannot override m2() in Parent
          void m2() throws Exception{ System.out.println("子类 m2");}
               ^
        overridden method does not throw Exception
    • 规则2:如果超类重写方法确实引发异常,则子类重写方法只能引发相同的子类异常。在Exception层次结构中引发父异常将导致编译时错误。如果子类重写方法未引发任何异常,也没有问题。
      // Java展示父类引发异常
      class Parent {
          void m1() throws RuntimeException
          {
              System.out.println("父类 m1()");
          }
      }
      class Child1 extends Parent {
          @Override
          // 没问题
          void m1() throws RuntimeException
          {
              System.out.println("子类1 m1()");
          }
      }
      class Child2 extends Parent {
          @Override
          // 没问题
          void m1() throws ArithmeticException
          {
              System.out.println("子类2 m1()");
          }
      }
      class Child3 extends Parent {
          @Override
          // 没问题
          void m1()
          {
              System.out.println("子类3 m1()");
          }
      }
      class Child4 extends Parent {
          @Override
          // 编译错误
          void m1() throws Exception
          {
              System.out.println("子类4 m1()");
          }
      }

      输出:

      error: m1() in Child4 cannot override m1() in Parent
          void m1() throws Exception
               ^
        overridden method does not throw Exception
  9. 重写和抽象方法:接口或抽象类中的抽象方法应在派生的具体类中被重写,否则将引发编译时错误。
  10. 重写和synced / strictfp方法:同步/ strictfp修饰符与method的存在对重写规则没有影响,即,synchronized / strictfp方法可能会重写非sync / strictfp方法,反之亦然。

注意 :

    1. 在C++中,我们需要virtual关键字来实现重写或运行时多态
    2. 我们可以进行多级方法重写。
      // Java程序展示多级重写
      // 基类
      class Parent {
          void show()
          {
              System.out.println("父类 show()");
          }
      }
      // 集成
      class Child extends Parent {
          // 这个方法重写show()
          void show() { System.out.println("子类 show()"); }
      }
      // Inherited class
      class GrandChild extends Child {
          // 重写父类show()
          void show()
          {
              System.out.println("孙子类 show()");
          }
      }
      // 测试类
      class Main {
          public static void main(String[] args)
          {
              Parent obj1 = new GrandChild();
              obj1.show();
          }
      }

      输出:

      孙子类 show()
    3. 重写与重载
        1. 重载是同一方法具有不同的签名。重写是关于相同的方法,相同的签名,但通过继承连接的不同类。

        2. 重载是编译器时多态的一个示例,重载是运行时多态的一个示例。

如前所述,重写的方法允许Java支持运行时多态。多态性对于面向对象的编程至关重要,原因之一是:多态性允许通用类指定对其所有派生类通用的方法,同时允许子类定义某些或所有这些方法的特定实现。重写方法是Java实现多态性的“一个接口,多个方法”方面的另一种方式。

动态方法分派是面向对象设计带来的最强大的机制之一,可影响代码的重用性和鲁棒性。存在的代码库能够在保持干净的抽象接口的同时不重新编译的情况下,调用新类实例上的方法的函数是一种强大的提现。

重写方法使我们可以调用任何派生类的方法,甚至不知道派生类对象的类型。

何时应用方法重写?(带有示例)

重写和继承:成功应用多态性的部分关键是要了解,超类和子类形成了一个层次结构,该层次结构从较小的专业化过渡到较大的专业化。如果正确使用,超类将提供子类可以直接使用的所有元素。它还定义了派生类必须自己实现的那些方法。这使子类可以灵活地定义其方法,但仍然可以强制使用一致的接口。因此,通过将继承与重写的方法相结合,超类可以定义将由其所有子类使用的方法的一般形式。

让我们看一个使用方法重写的更实际的示例。考虑一个组织的雇员管理软件,让代码具有一个简单的基类Employee,该类具有诸如raiseSalary(),transfer(),promove()等方法。不同类型的雇员,例如Manager,Engineer 、…等可能具有基类Employee中存在的方法的实现。在我们完整的软件中,我们只需要在各处传递一个员工列表并调用适当的方法,甚至不知道员工的类型。

 

例如,我们可以通过遍历员工列表轻松地提高所有员工的薪水。每种类型的员工都可以在其类中拥有自己的逻辑,我们不必担心,因为如果针对特定员工类型提供了raiseSalary(),则仅会调用该方法。

// Java展示重写
// 基类
class Employee {
    public static int base = 10000;
    int salary()
    {
        return base;
    }
}
// 继承类
class Manager extends Employee {
    // 重写基类 salary()
    int salary()
    {
        return base + 20000;
    }
}
// 继承
class Clerk extends Employee {
    // 重写父类的salary()
    int salary()
    {
        return base + 10000;
    }
}
// 测试代码
class Main {

    static void printSalary(Employee e)
    {
        System.out.println(e.salary());
    }
    public static void main(String[] args)
    {
        Employee obj1 = new Manager();

        System.out.print("经理工资 : ");
        printSalary(obj1);
        Employee obj2 = new Clerk();
        System.out.print("员工工资 : ");
        printSalary(obj2);
    }
}

输出:

经理工资 : 30000
员工工资 : 20000