在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。
例如,在Person
类中,我们定义了run()
方法:
1 2 3 4 5 class Person { public void run () { System.out.println("Person.run" ); } }
在子类Student
中,覆写这个run()
方法:
1 2 3 4 5 6 class Student extends Person { @Override public void run () { System.out.println("Student.run" ); } }
Override和Overload不同的是,如果方法签名不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override
。
注意:方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。在Java程序中,出现这种情况,编译器会报错。
1 2 3 4 5 6 7 8 9 10 class Person { public void run () { … } } class Student extends Person { public void run (String s) { … } public int run () { … } }
加上@Override
可以让编译器帮助检查是否进行了正确的覆写。希望进行覆写,但是不小心写错了方法签名,编译器会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Main { public static void main (String[] args) { } } class Person { public void run () {} } public class Student extends Person { @Override public void run (String s) {} }
但是@Override
不是必需的。
在上一节中,我们已经知道,引用变量的声明类型可能与其实际类型不符,例如:
1 Person p = new Student();
现在,我们考虑一种情况,如果子类覆写了父类的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Main { public static void main (String[] args) { Person p = new Student(); p.run(); } } class Person { public void run () { System.out.println("Person.run" ); } } class Student extends Person { @Override public void run () { System.out.println("Student.run" ); } }
那么,一个实际类型为Student
,引用类型为Person
的变量,调用其run()
方法,调用的是Person
还是Student
的run()
方法?
运行一下上面的代码就可以知道,实际上调用的方法是Student
的run()
方法。因此可得出结论:
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。
这个非常重要的特性在面向对象编程中称之为多态。它的英文拼写非常复杂:Polymorphic。
多态 多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。例如:
1 2 Person p = new Student(); p.run();
有童鞋会问,从上面的代码一看就明白,肯定调用的是Student
的run()
方法啊。
但是,假设我们编写这样一个方法:
1 2 3 4 public void runTwice (Person p) { p.run(); p.run(); }
它传入的参数类型是Person
,我们是无法知道传入的参数实际类型究竟是Person
,还是Student
,还是Person
的其他子类,因此,也无法确定调用的是不是Person
类定义的run()
方法。
所以,多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。这种不确定性的方法调用,究竟有什么作用?
我们还是来举栗子。
假设我们定义一种收入,需要给它报税,那么先定义一个Income
类:
1 2 3 4 5 6 class Income { protected double income; public double getTax () { return income * 0.1 ; } }
对于工资收入,可以减去一个基数,那么我们可以从Income
派生出SalaryIncome
,并覆写getTax()
:
1 2 3 4 5 6 7 8 9 class Salary extends Income { @Override public double getTax () { if (income <= 5000 ) { return 0 ; } return (income - 5000 ) * 0.2 ; } }
如果你享受国务院特殊津贴,那么按照规定,可以全部免税:
1 2 3 4 5 6 class StateCouncilSpecialAllowance extends Income { @Override public double getTax () { return 0 ; } }
现在,我们要编写一个报税的财务软件,对于一个人的所有收入进行报税,可以这么写:
1 2 3 4 5 6 7 public double totalTax (Income... incomes) { double total = 0 ; for (Income income: incomes) { total = total + income.getTax(); } return total; }
来试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class Main { public static void main (String[] args) { Income[] incomes = new Income[] { new Income(3000 ), new Salary(7500 ), new StateCouncilSpecialAllowance(15000 ) }; System.out.println(totalTax(incomes)); } public static double totalTax (Income... incomes) { double total = 0 ; for (Income income: incomes) { total = total + income.getTax(); } return total; } } class Income { protected double income; public Income (double income) { this .income = income; } public double getTax () { return income * 0.1 ; } } class Salary extends Income { public Salary (double income) { super (income); } @Override public double getTax () { if (income <= 5000 ) { return 0 ; } return (income - 5000 ) * 0.2 ; } } class StateCouncilSpecialAllowance extends Income { public StateCouncilSpecialAllowance (double income) { super (income); } @Override public double getTax () { return 0 ; } }
观察totalTax()
方法:利用多态,totalTax()
方法只需要和Income
打交道,它完全不需要知道Salary
和StateCouncilSpecialAllowance
的存在,就可以正确计算出总的税。如果我们要新增一种稿费收入,只需要从Income
派生,然后正确覆写getTax()
方法就可以。把新的类型传入totalTax()
,不需要修改任何代码。
可见,多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。
覆写Object方法 因为所有的class
最终都继承自Object
,而Object
定义了几个重要的方法:
toString()
:把instance输出为String
;
equals()
:判断两个instance是否逻辑相等;
hashCode()
:计算一个instance的哈希值。
在必要的情况下,我们可以覆写Object
的这几个方法。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Person { ... @Override public String toString () { return "Person:name=" + name; } @Override public boolean equals (Object o) { if (o instanceof Person) { Person p = (Person) o; return this .name.equals(p.name); } return false ; } @Override public int hashCode () { return this .name.hashCode(); } }
调用super 在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super
来调用。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { protected String name; public String hello () { return "Hello, " + name; } } Student extends Person { @Override public String hello () { return super .hello() + "!" ; } }
final 继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final
。用final
修饰的方法不能被Override
:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Person { protected String name; public final String hello () { return "Hello, " + name; } } Student extends Person { @Override public String hello () { } }
如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final
。用final
修饰的类不能被继承:
1 2 3 4 5 6 7 final class Person { protected String name; } Student extends Person { }
对于一个类的实例字段,同样可以用final
修饰。用final
修饰的字段在初始化后不能被修改。例如:
1 2 3 class Person { public final String name = "Unamed" ; }
对final
字段重新赋值会报错:
1 2 Person p = new Person(); p.name = "New Name" ;
可以在构造方法中初始化final字段:
1 2 3 4 5 6 class Person { public final String name; public Person (String name) { this .name = name; } }
这种方法更为常用,因为可以保证实例一旦创建,其final
字段就不可修改。
练习 给一个有工资收入和稿费收入的小伙伴算税。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Income { protected double income; public Income (double income) { this .income = income; } public double getTax () { return income * 0.1 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class SalaryIncome extends Income { public SalaryIncome (double income) { super (income); } @Override public double getTax () { if (income <= 5000 ) { return 0 ; } return (income - 5000 ) * 0.2 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class RoyaltyIncome extends Income { public RoyaltyIncome (double income) { super (income); } @Override public double getTax () { return income * 0.2 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Main { public static void main (String[] args) { Income[] incomes = new Income[] { new Income(3000 ), new SalaryIncome(7500 ), new RoyaltyIncome(12000 ) }; System.out.println(totalTax(incomes)); } public static double totalTax (Income... incomes) { double total = 0 ; for (Income income : incomes){ total += income.getTax(); } return total; } }
小结
子类可以覆写父类的方法(Override),覆写在子类中改变了父类方法的行为;
Java的方法调用总是作用于运行期对象的实际类型,这种行为称为多态;
final
修饰符有多种作用:
final
修饰的方法可以阻止被覆写;
final
修饰的class可以阻止被继承;
final
修饰的field必须在创建对象时初始化,随后不可修改。