覆写
子类对象在寻找方法或成员变量的时候,先搜索子类独有空间,在搜索父类空间
方法名相同 参数列表一致
子类返回值类型应该更小(子类)或者相同
子类的抛出的异常小于等于父类的抛出的异常类
子类的访问权限比父类访问权限要大和相等
变量只能被隐藏(包括静态和非静态),不能被重写
可以用子类的静态变量隐藏父类的静态变量,也可以用子类的非静态变量隐藏父类的静态变量
静态方法(static)只能被隐藏,不能被重写
非静态方法可以被重写
不能用子类的静态方法隐藏父类中的非静态方法
不能用子类的非静态方法覆盖父类的静态方法
在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。
例如,在Person
类中,定义了run()
方法:
class Person {
public void run() {
System.out.println("Person.run");
}
}
在子类Student
中,覆写这个run()
方法:
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
Override和Overload不同的是,如果方法签名如果不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override
。
class Person {
public void run() { … }
}
class Student extends Person {
// 不是Override,因为参数不同:
public void run(String s) { … }
// 不是Override,因为返回值不同:
public int run() { … }
}
加上@Override
可以让编译器帮助检查是否进行了正确的覆写。希望进行覆写,但是不小心写错了方法签名,编译器会报错。
public class Main {
public static void main(String[] args) {
}
}
class Person {
public void run() {}
}
public class Student extends Person {
@Override // Compile error!
public void run(String s) {} //错误: 方法不会覆盖或实现超类型的方法
}
但是@Override
不是必需的。
因为继承中引用变量的声明类型可能与其实际类型不符,例如:
Person p = new Student();
如果子类覆写了父类的方法:
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run(); // Student.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()
方法,调用的是Student
的run()
方法,Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型,这个特性在面向对象编程中称之为多态
。
多态
多态的前提条件:
1、子类继承父类
2、子类重写父类方法
3、父类引用指向子类对象
当你用父类引用指向子类对象的时候,
- 1、成员变量不变,调用结果为父类的成员变量的值
- 2、成员方法改变,调用结果为子类的成员方法的结果
- 3、静态成员方法不变,调用的结果为父类的静态成员方法
多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。例如:
Person p = new Student();
p.run(); // 无法确定运行时究竟调用哪个run()方法
假设编写这样一个方法:
public void runTwice(Person p) {
p.run();
p.run();
}
它传入的参数类型是Person
,无法知道传入的参数实际类型究竟是Person
,还是Student
,还是Person
的其他子类,因此,也无法确定调用的是不是Person
类定义的run()
方法。
所以,多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。
用途:
假设定义一种收入,需要给它报税,那么先定义一个Income
类:
class Income {
protected double income;
public double getTax() {
return income * 0.1; // 税率10%
}
}
对于工资收入,可以减去一个基数,那么可以从Income
派生出SalaryIncome
,并覆写getTax()
:
class Salary extends Income {
@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}
}
如果享受国务院特殊津贴,那么按照规定,可以全部免税:
class StateCouncilSpecialAllowance extends Income {
@Override
public double getTax() {
return 0;
}
}
现在要编写一个报税的财务软件,对于一个人的所有收入进行报税,可以这么写:
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; // 税率10%
}
}
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的这几个方法。例如:
class Person {
...
// 显示更有意义的字符串:
@Override
public String toString() {
return "Person:name=" + name;
}
// 比较是否相等:
@Override
public boolean equals(Object o) {
// 当且仅当o为Person类型:
if (o instanceof Person) {
Person p = (Person) o;
// 并且name字段相同时,返回true:
return this.name.equals(p.name);
}
return false;
}
// 计算hash:
@Override
public int hashCode() {
return this.name.hashCode();
}
}
调用super
Super 可用来引用父类的成分,它有两种主要用法:
引用父类的成员(需要相应的访问权限):
- super.变量
- 或super.方法([参数列])
在子类构造方法中调用父类的构造方法:
- super([…]);//与this用法类似,应放在构造方法的第一行位置上。
在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super
来调用。例如:
class Person {
protected String name;
public String hello() {
return "Hello, " + name;
}
}
Student extends Person {
@Override
public String hello() {
// 调用父类的hello()方法:
return super.hello() + "!";
}
}
final
修饰变量,为常量,值不可变
修饰对象,值可变,引用不变
修饰方法,方法不可重写
修饰类,无子类,不可以被继承,更不可能被重写
继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final
。用final
修饰的方法不能被Override
:
class Person {
protected String name;
public final String hello() {
return "Hello, " + name;
}
}
Student extends Person {
// compile error: 不允许覆写
@Override
public String hello() {
}
}
如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final
。用final
修饰的类不能被继承:
final class Person {
protected String name;
}
// compile error: 不允许继承自Person
Student extends Person {
}
对于一个类的实例字段,同样可以用final
修饰。用final
修饰的字段在初始化后不能被修改。例如:
class Person {
public final String name = "Unamed";
}
对final
字段重新赋值会报错:
Person p = new Person();
p.name = "New Name"; // compile error!
可以在构造方法中初始化final
字段:
class Person {
public final String name;
public Person(String name) {
this.name = name;
}
}
这种方法更为常用,因为可以保证实例一旦创建,其final
字段就不可修改。
例计算带稿费的个税
public class Main {
public static void main(String[] args) {
Income[] incomes = new Income[] { new Income(3000), new SalaryIncome(7500), new RoyaltyIncome(12000) };
double total = 0;
for (Income income : incomes) { // for-each遍历数组的格式是for (数组类型 数组元素:数组)
total += income.getTax();
}
System.out.println(total);//2144.0
}
}
public class Income {
protected double income;
public Income(double income) {
this.income = income;
}
public double getTax() {
return income * 0.1; // 税率10%
}
}
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;
}
}
public class RoyaltyIncome extends Income {
public RoyaltyIncome(double income) {
super(income);
}
@Override
public double getTax() {
if(income<=4000) {
return (income - 800)*0.14;
}
return income * 0.8*0.14;
}
}