小结
- 子类可以覆写父类的方法(Override),覆写在子类中改变了父类方法的行为
- Java的方法调用总是作用于运行时期对象的实际类型,这种行为称为多态
- final修饰符有多重作用:
- final修饰的方法可以阻止被覆写
- final修饰的class可以阻止被继承
- final修饰的field必须在创建对象时初始化,随后不可修改
覆写(Override):在继承关系中,子类如果定义了一个与方法签名(方法的名称和参数类型)完全相同的方法
class Person{//例如我们在父类中定义了run()方法
public void run(){
System.out.println("Person.run");
}
}
class Student extedns Person{//在子类Student中,覆写了这个run()方法
public void run(){
System.out.println("Student.run");
}
}
Override和Overload不同的是,如果方法签名不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override。注意:方法名相同,方法参数相同,但方法返回值不同,也是不同方法。在java程序中,出现这种情况,编译器会报错。
如果子类覆写了父类的方法,一个实际类型为Student,引用类型为Person的变量,调用其run()方法,实际上调用的方法是Student的run()方法,因此可得出结论:Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型,这个非常重要的特性在面向对象编程中称之为多态(Polymorphic)
//多态
public class Main {
public static void main(String[] args){
Person p = new Student();
p.run();//输出的是Student.run
}
}
class Person{//例如我们在父类中定义了run()方法
public void run(){
System.out.println("Person.run");
}
}
class Student extends Person{//在子类Student中,覆写了这个run()方法
@Override//让编译器帮助检查是否进行了正确的覆写。不小心写错了方法签名,编译器会报错
public void run(){
System.out.println("Student.run");
}
}
多态
针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法
多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。这种不确定性的方法调用,作用是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码
//多态
public class Main {
public static void main(String[] args){
//给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税
Income incomes[] = new Income[]{
new Income(3000),//普通收入
new Salary(7500),//工资
new Allowance(15000)//津贴
};
//利用多态,totalTax()方法只需要和Income打交道,不需要知道salary和Allowance的存在
//如果需要新增稿费收入,只需要从Income派生,然后正确覆写getTax()方法即可
System.out.println(totalTax(incomes));
}
public static double totalTax(Income...incomes){//定义了一个可变参数incomes,相当于数组类型
double total = 0;
for(Income income:incomes){//遍历数组incomes
total+=income.getTax();//访问引用类型变量income指向的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);//调用父类的构造方法Income(double)
}
@Override//让编译器检查是否进行了正确的覆写
public double getTax(){//覆写父类的方法
if(income <= 5000){//此处income==this.income
return 0;
}
return (income-5000)*0.2;
}
}
class Allowance extends Income{//继承
public Allowance(double income){
super(income);//调用父类的构造方法Income(double)
}
@Override//让编译器检查是否进行了正确的覆写
public double getTax(){//覆写父类的方法
return 0;
}
}
覆写Object方法
因为所有的class最终都继承自Object,而Object定义了几个重要的方法:
- toString():把instance输出为String
- equals():判断两个instance是否逻辑相等
- hashCode():计算一个instance的哈希值
覆写这几个方法
class Person{
protected String name;
@Override
public String toString(){
return "Person:name="+name;
}
//比较是否相等
public boolean equals(Object o){
//当且仅当o为Person类型
if(o instanceof Person){
Person p = (Person)o;//强制类型转换
return this.name.equals(p.name);
}
return false;
}
//计算hash
@Override
public int hashCode(){
return this.name.hashCode();
}
}
调用Super
在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过Super来调用。
class Person{
protected String name;
public String hello(){
return"hello,"+name;//构造方法
}
}
class 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;//构造方法
}
}
class Student extends Person{
@Override//compile error:不允许覆写
public String hello(){
}
}
对一个类的实例字段,同样可以用final修饰。在构造方法中初始化final字段,对final字段重新赋值会报错
//覆写Object方法
public class Main {
public static void main(String[] args){
Person p = new Person("cheng");
p.name = "cheng";//当试图修改时,compile error!
}
}
class Person{
public final String name;//用final修饰的字段在初始化后不能被修改
public Person(String name){
this.name = name;
}
}
public class Test {
public static void main(String[] args){
show(new Cat());
show(new Dog());
Animal a = new Cat();//向上转型
a.eat();
Cat c = (Cat)a;//向下转型
c.work();
}
public static void show(Animal a){
a.eat();
if(a instanceof Cat){//instanceof用来判断一个对象是否是一个类的实例
Cat c = (Cat)a;
c.work();
}
else if(a instanceof Dog){
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal{
abstract void eat();
}
class Cat extends Animal{
public void eat(){
System.out.println("吃鱼");
}
public void work(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("吃肉");
}
public void work(){
System.out.println("看家");
}
}
//Employee.java
public class Employee {
private String name;
private String address;
private int number;
public Employee(String name,String address,int number){
System.out.println("构造函数");
this.address = address;
this.name = name;
this.number = number;
}
public void mailCheck(){
System.out.println("把支票寄给: "+this.name+" "+this.address);
//当类中的成员变量和局部变量重名时,使用this表示成员变量
}
public String toString(){
return name+" "+address+" "+number;
}
public String getName(){
return name;
}
public String getAddress(){
return address;
}
public int getNumber(){
return number;
}
public void setAddress(String newAddress){
address = newAddress;
}
}
//Salary.java
public class Salary extends Employee{
private double salary;//全年工资
public Salary(String name,String address,int number,double salary){
super(name,address,number);
setSalary(salary);
}
public void mailCheck(){
System.out.println("Salary的mailCheck方法");
System.out.println("邮寄支票给: "+getName()+", 工资为:"+salary);
}
public void setSalary(double newSalary){
if(newSalary>=0.0)
salary = newSalary;
}
public double getSalary(){
return salary;
}
public double computeSalary(){
System.out.println("计算工资付给: "+getName());
return salary/52;
}
}
//VirtualDemo.java
public class VirtualDemo {
public static void main(String[] args){
Salary a = new Salary("成希乐","北京",3,500000.00);
Employee b = new Salary("lele","成都",4,30000.00);
System.out.println("使用Salary的引用调用mailCheck----------");
a.mailCheck();;
System.out.println("使用Employee的引用调用mailCheck--------");
b.mailCheck();
}
}
/*
构造函数
构造函数
使用Salary的引用调用mailCheck----------
Salary的mailCheck方法
邮寄支票给: 成希乐, 工资为:500000.0
使用Employee的引用调用mailCheck--------
Salary的mailCheck方法
邮寄支票给: lele, 工资为:30000.0
*/