override 和 overload,分别翻译为覆写和重载,是 Java 多态(Polymorphism)的两种代表类型。下面详细分析一下 override 和 overload 的使用方式。

1. override

override 出现在继承关系中,子类覆写父类的实例方法,为它提供专门的实现。举个例子:

public class Animal {
    public void bark() {
        System.out.println("动物叫声");
    }
}

public class Cat extends Animal {
    @Override
    public void bark() {
        System.out.println("喵喵");
    }
}

    public static void main(String[] args) {
        // 向上转型
				Animal cat = new Cat();
        cat.bark(); // 打印 喵喵
    }

override 的绑定发生在运行期,属于动态绑定,由实例对象决定调用哪个方法,动态绑定是多态性得以实现的重要因素。这里有几点需要注意的地方:

  • 子类的方法访问权限只能相同或变大。比如父类方法是 protected,子类可以是 public,不能是 private。
  • 抛出异常和返回值只能变小,它们能够转型成父类对象。比如父类方法返回值 Number,子类可以返回 Integer,不能返回 Object。
  • 方法签名必须完全相同。方法签名包括方法名称和参数列表,是 JVM 标识方法的唯一索引,不包括返回值,其中参数列表分为类型和个数。
  • 覆写方法必须要加上 @Override 注解,为了使编译器自动检查覆写是否满足规则。

覆写只能用于类的不被 final 和 private 修饰的实例方法,不能用于静态方法,如果父类和子类中存在同名的静态方法,那么两者都可以被正常调用。

2. overload

overload 出现在同一个类中,多个方法具有相同名字、不同的参数。举个例子:

public class Cat {
    public void bark() {
        System.out.println("喵喵");
    }

    public void bark(int num) {
        for (int i = 1; i <= num; i++) {
            System.out.println("第 " + i + " 声喵喵");
        }
    }
}

overload 的绑定发生在编译期,属于静态绑定,由方法签名决定调用哪个方法。需要注意的是,方法签名包括方法名称、参数类型和个数,不包括返回值。

当重载的方法参数比较复杂时,JVM 选择合适的目标方法的顺序如下:

  • 精确匹配
  • 如果是基本数据类型,自动转换成更大表示范围的基本类型
  • 通过自动拆箱与装箱
  • 通过子类向上转型继承路线依次匹配
  • 通过可变参数匹配

下面通过例子说明。

  1. 基本数据类型优先于包装数据类型。
    public void mo(int a) {
        System.out.println("primary int type");
    }

    public void mo(Integer a) {
        System.out.println("wrapper int type");
    }

    Cat cat = new Cat();
    cat.mo(1); // 打印 primary int type
  1. 更大范围的基本数据类型优先于包装数据类型。
    public void mo(long a) {
        System.out.println("primary long type");
    }

    public void mo(Integer a) {
        System.out.println("wrapper int type");
    }
    
    Cat cat = new Cat();
    cat.mo(1); // 打印 primary long type
  1. null 可以匹配任何类对象,从最底层子类依次向上查找。
    public void mo(Integer a) {
        System.out.println("wrapper int type");
    }

    public void mo(Object a) {
        System.out.println("object type");
    }

//  如果包含 String 参数,那么编译器不知道匹配哪个,直接报错。
//  public void mo(String s) {
//      System.out.println("string type");
//  }

    Cat cat = new Cat();
    cat.mo(null); // 打印 wrapper int type
  1. 自动装箱和拆箱优先于可变参数。
    public void mo(Integer a, Integer b){
        System.out.println("double wrapper int type");
    }

    public void mo(int... a) {
        System.out.println("primary int array type");
    }
    
    Cat cat = new Cat();
    cat.mo(1, 2); // 打印 double wrapper int type

...

重载的参数类型应该简洁些,这样才能提高代码的可读性。

3. 总结

override 和 overload 有很多不同之处:

  • override 属于动态绑定,在运行期通过实例对象决定调用的方法;overload 属于静态绑定,在编译期通过方法签名决定调用的方法。
  • override 基于继承关系,需要父类和子类参与;overload 出现在同一个类中,通过方法签名区分。
  • static、final、private 方法不能被 override,但是可以被 overload。
  • override 关注方法返回值,overload 不关注返回值。
  • override 的方法参数必须一样,overload 的方法参数必须不同。
  • overload 的性能比 override 更好些,应为它是静态绑定。

参考: