前言

上一篇我们学习了接口和抽象类的区别,这一篇继续学习 Java 面向对象中非常经典的问题:重载和重写的区别

这个问题看起来很基础,但是面试时经常会继续追问:

  • 什么是方法重载?
  • 什么是方法重写?
  • 重载能不能只看返回值?
  • 重写时访问权限能不能变小?
  • 重写时异常能不能变大?
  • @Override 注解有什么作用?
  • 构造方法能不能重写?

这一篇我们就把重载和重写从语法、使用场景、规则和面试回答完整梳理一遍。

一、先给结论

可以先记住一句话:

重载发生在同一个类中,方法名相同,参数列表不同;
重写发生在父子类之间,子类对父类方法进行重新实现。

对比表:

对比点 重载 重写
英文 Overload Override
发生位置 同一个类中 父子类之间
方法名 必须相同 必须相同
参数列表 必须不同 必须相同
返回值 不能只靠返回值区分 返回值类型要兼容
访问权限 没有限制 子类不能比父类更严格
异常 没有限制 子类异常不能比父类更大
多态关系 编译期多态 运行期多态

简单记忆:

重载:同名不同参
重写:子类改父类

二、什么是方法重载?

方法重载指的是:在同一个类中,可以定义多个方法名相同、参数列表不同的方法。

示例:

public class Calculator {

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

    public double add(double a, double b) {
        return a + b;
    }

    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

这三个方法都叫:

add

但是参数列表不同,所以构成重载。

测试:

public class Test {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        System.out.println(calculator.add(1, 2));
        System.out.println(calculator.add(1.5, 2.5));
        System.out.println(calculator.add(1, 2, 3));
    }
}

输出:

3
4.0
6

Java 编译器会根据你传入的参数类型和参数个数,决定调用哪个方法。

三、重载看什么?

方法重载主要看参数列表。

参数列表不同包括:

  1. 参数个数不同
  2. 参数类型不同
  3. 参数顺序不同

1. 参数个数不同

public void print(String name) {
    System.out.println(name);
}

public void print(String name, Integer age) {
    System.out.println(name + ":" + age);
}

一个方法有 1 个参数,一个方法有 2 个参数,可以构成重载。

2. 参数类型不同

public void print(String value) {
    System.out.println(value);
}

public void print(Integer value) {
    System.out.println(value);
}

参数类型不同,也可以构成重载。

3. 参数顺序不同

public void print(String name, Integer age) {
    System.out.println(name + ":" + age);
}

public void print(Integer age, String name) {
    System.out.println(age + ":" + name);
}

参数类型顺序不同,也可以构成重载。

不过实际开发中,不太建议大量使用这种“只是顺序不同”的重载,容易让调用方混淆。

四、重载不能只靠返回值区分

下面这种写法是错误的:

public int getValue() {
    return 1;
}

public String getValue() {
    return "1";
}

虽然返回值不同,但是方法名和参数列表完全一样。

Java 调用方法时,不能只通过返回值来判断调用哪个方法。

比如:

getValue();

编译器不知道你想调用返回 int 的方法,还是返回 String 的方法。

所以:

方法重载不能只靠返回值类型区分。

五、什么是方法重写?

方法重写指的是:子类继承父类后,对父类中已有的方法进行重新实现。

示例:

public class Animal {

    public void eat() {
        System.out.println("动物吃东西");
    }
}

子类:

public class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        Animal animal = new Cat();
        animal.eat();
    }
}

输出:

猫吃鱼

虽然变量类型是 Animal,但是实际对象是 Cat,所以运行时调用的是子类重写后的方法。

这就是多态。

六、@Override 注解的作用

在重写方法时,建议加上:

@Override

比如:

@Override
public void eat() {
    System.out.println("猫吃鱼");
}

@Override 的作用是告诉编译器:

这个方法是重写父类方法。

如果方法名或参数写错,编译器会直接报错。

比如父类方法是:

public void eat() {
}

子类写成:

@Override
public void eats() {
}

这时会编译报错,因为父类中没有 eats 方法可以重写。

所以 @Override 可以帮助我们避免低级错误。

七、重写的规则

1. 方法名必须相同

父类:

public void eat() {
}

子类:

@Override
public void eat() {
}

方法名必须一样。

2. 参数列表必须相同

父类:

public void eat(String food) {
}

子类:

@Override
public void eat(String food) {
}

参数类型、个数、顺序都要一致。

如果参数不一样,那就是重载,不是重写。

3. 返回值类型要兼容

父类:

public Animal getAnimal() {
    return new Animal();
}

子类可以返回父类返回值的子类型:

@Override
public Cat getAnimal() {
    return new Cat();
}

这种叫协变返回类型。

但是不能返回完全不兼容的类型。

4. 访问权限不能变小

父类方法是 public

public void eat() {
}

子类重写时不能改成 private

@Override
private void eat() {
}

这是错误的。

访问权限可以变大或保持不变,不能变小。

权限大小大致是:

public > protected > 默认 > private

5. 抛出的异常不能更大

如果父类方法抛出的是:

public void method() throws IOException {
}

子类重写时可以不抛异常:

@Override
public void method() {
}

也可以抛出更小的异常。

但是不能抛出更大的受检异常,比如:

@Override
public void method() throws Exception {
}

这样不允许,因为 ExceptionIOException 更大。

八、构造方法能不能重写?

不能。

构造方法不能被继承,所以也不能被重写。

比如:

public class Animal {
    public Animal() {
    }
}

子类可以写自己的构造方法:

public class Cat extends Animal {
    public Cat() {
    }
}

但这不是重写。

构造方法只是用于创建当前类对象,不参与方法重写。

九、static 方法能不能重写?

严格来说,static 方法不能被重写。

它属于类,不属于对象。

比如:

public class Parent {
    public static void show() {
        System.out.println("Parent show");
    }
}

子类:

public class Child extends Parent {
    public static void show() {
        System.out.println("Child show");
    }
}

这不是重写,而是方法隐藏。

测试:

Parent p = new Child();
p.show();

输出:

Parent show

因为静态方法看的是引用类型,而不是实际对象类型。

所以面试时可以说:

静态方法可以被子类定义同名方法,但这不是重写,而是隐藏。

十、private 方法能不能重写?

不能。

private 方法只能在当前类内部访问,子类继承不到父类的 private 方法。

父类:

public class Parent {
    private void show() {
        System.out.println("Parent");
    }
}

子类:

public class Child extends Parent {
    private void show() {
        System.out.println("Child");
    }
}

这两个方法只是名字一样,不构成重写。

十一、重载和重写与多态的关系

1. 重载是编译期多态

重载方法在编译阶段就能确定调用哪个方法。

calculator.add(1, 2);
calculator.add(1.5, 2.5);

编译器根据参数类型决定调用哪个 add

所以重载属于编译期多态。

2. 重写是运行期多态

重写方法在运行时根据实际对象类型决定调用哪个方法。

Animal animal = new Cat();
animal.eat();

编译时变量类型是 Animal,但运行时实际对象是 Cat

所以调用的是 Cat 重写后的 eat 方法。

重写体现的是运行期多态。

十二、实际开发中的例子

1. 重载常见场景

比如工具类中提供多个参数版本:

public class DateUtils {

    public static String format(LocalDate date) {
        return date.toString();
    }

    public static String format(LocalDateTime dateTime) {
        return dateTime.toString();
    }

    public static String format(LocalDateTime dateTime, String pattern) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        return dateTime.format(formatter);
    }
}

这些方法都叫 format,但是参数不同。

调用方可以根据不同需求传不同参数。

2. 重写常见场景

比如所有支付方式都有 pay 方法:

public interface PayService {
    void pay(BigDecimal amount);
}

支付宝支付:

public class AliPayService implements PayService {

    @Override
    public void pay(BigDecimal amount) {
        System.out.println("支付宝支付:" + amount);
    }
}

微信支付:

public class WechatPayService implements PayService {

    @Override
    public void pay(BigDecimal amount) {
        System.out.println("微信支付:" + amount);
    }
}

调用方:

public class OrderService {

    private PayService payService;

    public OrderService(PayService payService) {
        this.payService = payService;
    }

    public void createOrder(BigDecimal amount) {
        payService.pay(amount);
    }
}

这里具体执行支付宝还是微信支付,取决于运行时传入的实现类对象。

这就是重写和多态在项目中的体现。

十三、面试标准回答

如果面试官问:

重载和重写有什么区别?

可以这样回答:

重载发生在同一个类中,方法名相同,但是参数列表必须不同,参数个数、类型或顺序不同都可以构成重载。重载和返回值无关,不能只靠返回值区分方法。重载属于编译期多态,编译器会根据传入参数决定调用哪个方法。

重写发生在父子类之间,子类对父类已有方法进行重新实现。重写要求方法名和参数列表相同,返回值类型要兼容,访问权限不能比父类更严格,抛出的异常不能比父类更大。重写体现的是运行期多态,程序运行时根据实际对象类型决定调用哪个方法。

十四、常见问题总结

1. 重载能不能只改返回值?

不能。

方法重载必须参数列表不同,只改返回值不行。

2. 重写能不能改参数?

不能。

参数一改,就不是重写,而是重载。

3. 重写时访问权限能不能变小?

不能。

子类重写方法的访问权限不能比父类更严格。

4. static 方法能不能重写?

不能真正重写,只能隐藏。

静态方法属于类,不属于对象。

5. private 方法能不能重写?

不能。

private 方法子类不可见,所以不能重写。

6. 构造方法能不能重写?

不能。

构造方法不能被继承,也就不能被重写。

十五、总结

这一篇主要学习了重载和重写的区别。

重载发生在同一个类中,要求方法名相同、参数列表不同,和返回值无关,属于编译期多态。

重写发生在父子类之间,要求方法名和参数列表相同,子类对父类方法重新实现,属于运行期多态。

面试回答时,不要只说“重载参数不同,重写方法一样”,还要补充返回值、访问权限、异常、staticprivate、构造方法这些细节。

下一篇我们继续学习 Java 异常体系。

更多推荐