重写(Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
在面向对象原则里,重写意味着可以重写任何现有方法。实例如下:

  1. class Animal {
  2. public void move() {
  3. System.out.println("动物可以移动");
  4. }
  5. }
  6. class Dog extends Animal {
  7. @Override
  8. public void move() {
  9. System.out.println("狗可以跑和走");
  10. }
  11. }
  12. public class TestDog {
  13. public static void main(String args[]) {
  14. Animal a = new Animal(); // Animal 对象
  15. Animal b = new Dog(); // Dog 对象
  16. a.move(); // 执行 Animal 类的方法
  17. b.move(); // 执行 Dog 类的方法
  18. }
  19. }
  20. /*
  21. 动物可以移动
  22. 狗可以跑和走
  23. */

在上面的例子中可以看到,尽管 b 属于 Animal 类型,但是它运行的是 Dog 类的 move方法。
这是由于在编译阶段,只是检查参数的引用类型。
然而在运行时,Java 虚拟机(JVM)指定对象的类型并且运行该对象的方法。
因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。
思考以下例子:

  1. class Animal {
  2. public void move() {
  3. System.out.println("动物可以移动");
  4. }
  5. }
  6. class Dog extends Animal {
  7. public void move() {
  8. System.out.println("狗可以跑和走");
  9. }
  10. public void bark() {
  11. System.out.println("狗可以吠叫");
  12. }
  13. }
  14. public class TestDog {
  15. public static void main(String args[]) {
  16. Animal a1 = new Animal(); // Animal 对象
  17. Animal a2 = new Dog(); // Animal 对象
  18. Dog d1 = new Dog(); // Dog 对象
  19. a1.move(); // 执行 Animal 类的方法
  20. a2.move(); // 执行 Animal 类的方法
  21. d1.bark(); // 执行 Dog 类的方法
  22. a2.bark(); // The method bark() is undefined for the type Animal
  23. }
  24. }

以上实例编译运行结果如下:

  1. TestDog.java:30: cannot find symbol
  2. symbol : method bark()
  3. location: class Animal
  4. a2.bark();
  5. ^

该程序将抛出一个编译错误,因为b的引用类型Animal没有bark方法

重写Object类的方法

在IntelliJ IDEA中,可以点击Code菜单中的 Generate…
也可以使用快捷键 alt+insert,点击需要包含的成员变量并确定

  1. import java.util.*;
  2. class Student {
  3. private String name;
  4. private int age;
  5. public Student() {
  6. }
  7. public Student(String name, int age) {
  8. this.name = name;
  9. this.age = age;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public int getAge() {
  18. return age;
  19. }
  20. public void setAge(int age) {
  21. this.age = age;
  22. }
  23. @Override
  24. public int hashCode() {
  25. return Objects.hash(name, age);
  26. }
  27. @Override
  28. public boolean equals(Object o) {
  29. if (this == o) return true;
  30. if (o == null || getClass() != o.getClass()) return false;
  31. Student student = (Student) o;
  32. return age == student.age && Objects.equals(name, student.name);
  33. }
  34. @Override
  35. public String toString() {
  36. return "person{" + "name='" + name + '\'' + ", age=" + age + '}';
  37. }
  38. }
  39. public class Test {
  40. public static void main(String[] args) {
  41. //创建集合对象 该集合中存储 Student类型对象
  42. HashSet<Student> stuSet = new HashSet<Student>();
  43. //存储
  44. stuSet.add(new Student("于谦", 1));
  45. stuSet.add(new Student("郭德纲", 44));
  46. stuSet.add(new Student("郭德纲", 44));
  47. stuSet.add(new Student("于谦", 43));
  48. stuSet.add(new Student("郭麒麟", 23));
  49. for (Student stu2 : stuSet) {
  50. System.out.println(stu2);
  51. }
  52. }
  53. }
  54. /*
  55. 执行结果:
  56. person{name='郭德纲', age=44}
  57. person{name='于谦', age=43}
  58. person{name='郭麒麟', age=23}
  59. person{name='于谦', age=1}
  60. */

⚠️小贴士:在我们直接使用输出语句输出对象名的时候,其实通过该对象调用了其toString()方法

方法的重写规则

  • 参数列表与被重写方法的参数列表必须完全相同。
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  • 父类的成员方法只能被它的子类重写。
  • 声明为 final 的方法不能被重写。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个方法,则不能重写这个方法

Super 关键字的使用

当需要在子类中调用父类的被重写方法时,要使用 super 关键字

class Animal {
  public void move() {
    System.out.println("动物可以移动");
  }
}

class Dog extends Animal {
  public void move() {
    super.move(); // 应用super类的方法
    System.out.println("狗可以跑和走");
  }
}

public class TestDog {
  public static void main(String args[]) {

    Animal b = new Dog(); // Dog 对象
    b.move(); // 执行 Dog类的方法

  }
}

/* 
动物可以移动
狗可以跑和走 
*/

重载(Overload)

  • 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同
  • 与修饰符和返回值类型无关
  • 每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表
  • 最常用的地方就是构造器的重载

重载规则

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准

重载方法调用

JVM通过方法的参数列表,调用不同的方法

无参数直接调用

public class TestOverload {
  public static void main(String[] args) {
      print();
  }

  public static void print(){
    System.out.println("方法被调用");
  }

}

不同的实参赋值调用

public class TestOverload {
  public static void main(String[] args) {
    int sum = getSum(10, 20);
    System.out.println(sum); //赋值调用:调用方法,在方法前面定义变量,接收方法返回值

    System.out.println(getSum("10", "20")); // 输出语句调用
  }

  public static int getSum(int a, int b) {
    return a + b;
  }

  public static String getSum(String a, String b) {
    return a + b;
  }

}

重写与重载之间的区别

区别点 重载方法 重写方法
参数列表 必须修改 一定不能修改
返回类型 可以修改 一定不能修改
异常 可以修改 可以减少或删除,一定不能抛出新的或者更广的异常
访问 可以修改 一定不能做更严格的限制(可以降低限制)

总结

方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式

  • (1)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)
  • (2)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)
  • (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现

Snipaste_2020-08-19_22-39-23.png
Java 重写(Override)与重载(Overload) - 图2