3

Overriding:嘿嘿,就不告诉你这 11 条规则

 3 years ago
source link: http://developer.51cto.com/art/202101/643204.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

哎呀妈呀,等老久了吧!关于 Java 方面的文章终于来了,快快快,扶寡人起来,还能再举——鼎(明眼人都能看的出来,我受大秦帝国之纵横天下这部剧的影响了)。

说回正题。重写(Overriding)算是 Java 中一个非常重要的概念,理解重写到底是什么对每个 Java 程序员来说都至关重要,这篇文章就来给大家说说重写过程中应当遵守的 11 条规则。

01、什么是重写?

重写带来了一种非常重要的能力,可以让子类重新实现从父类那继承过来的方法。在下面这幅图中,Animal 是父类,Dog 是子类,Dog 重新实现了 move() 方法用来和父类进行区分,毕竟狗狗跑起来还是比较有特色的。

重写的方法和被重写的方法,不仅方法名相同,参数也相同(也就说是方法签名相同),只不过,方法体有所不同。

02、哪些方法可以被重写?

规则一:只能重写继承过来的方法。

因为重写是在子类重新实现从父类继承过来的方法时发生的,所以只能重写继承过来的方法,这很好理解。这就意味着,只能重写那些被 public、protected 或者 default 修饰的方法,private 修饰的方法无法被重写。

Animal 类有 move()、eat() 和 sleep() 三个方法:

public class Animal { public void move() { } protected void eat() { } void sleep(){ }}

Dog 类来重写这三个方法:

public class Dog extends Animal { public void move() { } protected void eat() { } void sleep(){ }}

OK,完全没有问题。但如果父类中的方法是 private 的,就行不通了。

public class Animal { private void move() { }}

此时,Dog 类中的 move() 方法就不再是一个重写方法了,因为父类的 move() 方法是 private 的,对子类并不可见。

public class Dog extends Animal { public void move() { }}

03、哪些方法不能被重写?

规则二:final、static 的方法不能被重写。

一个方法是 final 的就意味着它无法被子类继承到,所以就没办法重写。

public class Animal { final void move() { }}

由于父类 Animal 中的 move() 是 final 的,所以子类在尝试重写该方法的时候就出现编译错误了!

同样的,如果一个方法是 static 的,也不允许重写,因为静态方法可用于父类以及子类的所有实例。

public class Animal { final void move() { }}

重写的目的在于根据对象的类型不同而表现出多态,而静态方法不需要创建对象就可以使用。没有了对象,重写所需要的“对象的类型”也就没有存在的意义了。

04、重写方法的要求

规则三:重写的方法必须有相同的参数列表。

public class Animal { void eat(String food) { }}

Dog 类中的 eat() 方法保持了父类方法 eat() 的同一个调调,都有一个参数——String 类型的 food。

public class Dog extends Animal { public void eat(String food) { }}

一旦子类没有按照这个规则来,比如说增加了一个参数:

public class Dog extends Animal { public void eat(String food, int amount) { }}

这就不再是重写的范畴了,当然也不是重载的范畴,因为重载考虑的是同一个类。

规则四:重写的方法必须返回相同的类型。

父类没有返回类型:

public class Animal { void eat(String food) { }}

子类尝试返回 String:

public class Dog extends Animal { public String eat(String food) { return null; }}

于是就编译出错了(返回类型不兼容)。

规则五:重写的方法不能使用限制等级更严格的权限修饰符。

可以这样来理解:

如果被重写的方法是 default,那么重写的方法可以是 default、protected 或者 public。

如果被重写的方法是 protected,那么重写的方法只能是 protected 或者 public。

如果被重写的方法是 public, 那么重写的方法就只能是 public。

举个例子,父类中的方法是 protected:

public class Animal { protected void eat() { }}

子类中的方法可以是 public:

public class Dog extends Animal { public void eat() { }}

如果子类中的方法用了更严格的权限修饰符,编译器就报错了。

规则六:重写后的方法不能抛出比父类中更高级别的异常。

举例来说,如果父类中的方法抛出的是 IOException,那么子类中重写的方法不能抛出 Exception,可以是 IOException 的子类或者不抛出任何异常。这条规则只适用于可检查的异常。

可检查(checked)异常必须在源代码中显式地进行捕获处理,不检查(unchecked)异常就是所谓的运行时异常,比如说 NullPointerException、ArrayIndexOutOfBoundsException 之类的,不会在编译器强制要求。

父类抛出 IOException:

public class Animal { protected void eat() throws IOException { }}

子类抛出 FileNotFoundException 是可以满足重写的规则的,因为 FileNotFoundException 是 IOException 的子类。

public class Dog extends Animal { public void eat() throws FileNotFoundException { }}

如果子类抛出了一个新的异常,并且是一个 checked 异常:

public class Dog extends Animal { public void eat() throws FileNotFoundException, InterruptedException { }}

那编译器就会提示错误:

Error:(9, 16) java: com.itwanger.overriding.Dog中的eat()无法覆盖com.itwanger.overriding.Animal中的eat() 被覆盖的方法未抛出java.lang.InterruptedException

但如果子类抛出的是一个 unchecked 异常,那就没有冲突:

public class Dog extends Animal { public void eat() throws FileNotFoundException, IllegalArgumentException { }}

如果子类抛出的是一个更高级别的异常:

public class Dog extends Animal { public void eat() throws Exception { }}

编译器同样会提示错误,因为 Exception 是 IOException 的父类。

Error:(9, 16) java: com.itwanger.overriding.Dog中的eat()无法覆盖com.itwanger.overriding.Animal中的eat() 被覆盖的方法未抛出java.lang.Exception

05、如何调用被重写的方法?

规则七:可以在子类中通过 super 关键字来调用父类中被重写的方法。

子类继承父类的方法而不是重新实现是很常见的一种做法,在这种情况下,可以按照下面的形式调用父类的方法:

super.overriddenMethodName();

来看例子。

public class Animal { protected void eat() { }}

子类重写了 eat() 方法,然后在子类的 eat() 方法中,可以在方法体的第一行通过 super.eat() 调用父类的方法,然后再增加属于自己的代码。

public class Dog extends Animal { public void eat() { super.eat(); // Dog-eat }}

06、重写和构造方法

规则八:构造方法不能被重写。

因为构造方法很特殊,而且子类的构造方法不能和父类的构造方法同名(类名不同),所以构造方法和重写之间没有任何关系。

07、重写和抽象方法

规则九:如果一个类继承了抽象类,抽象类中的抽象方法必须在子类中被重写。

先来看这样一个接口类:

public interface Animal { void move();}

接口中的方法默认都是抽象方法,通过反编译是可以看得到的:

public interface Animal{ public abstract void move();}

如果一个抽象类实现了 Animal 接口,move() 方法不是必须被重写的:

public abstract class AbstractDog implements Animal { protected abstract void bark();}

但如果一个类继承了抽象类 AbstractDog,那么 Animal 接口中的 move() 方法和抽象类 AbstractDog 中的抽象方法 bark() 都必须被重写:

public class BullDog extends AbstractDog { public void move() {} protected void bark() {}}

08、重写和 synchronized 方法

规则十:synchronized 关键字对重写规则没有任何影响。

synchronized 关键字用于在多线程环境中获取和释放监听对象,因此它对重写规则没有任何影响,这就意味着 synchronized 方法可以去重写一个非同步方法。

09、重写和 strictfp 方法

规则十一:strictfp 关键字对重写规则没有任何影响。

如果你想让浮点运算更加精确,而且不会因为硬件平台的不同导致执行的结果不一致的话,可以在方法上添加 strictfp 关键字。因此 strictfp 关键和重写规则无关。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK