CoreJava读书笔记--继承(一)--类、超类和子类

类、超类和子类

可以回顾一下上一章中Employee类。某个公司中,经理和普通雇员的待遇存在差异,但是却也有很多相同的地方,例如都领取薪水,但普通雇员只有薪水,而经理还有业绩奖金。那么我们可以想到,经理也是一个雇员,但却比普通雇员多了某些属性。这种情况就是“is-a”的关系,就可以用到继承。

(一)定义子类

下面是Manager继承Employee的格式,使用extends关键字。

public class Manager extends Employee{
    //添加域和方法
}

关键字extends表明正在构造的新类派生于一个已存在的类,这个已存在的类可以称为超类(superclass),基类(base class)或父类(parent class);新类称为子类(subclass),派生类(derived class)或孩子类(child class)。

尽管Employee是超类,但并不是因为它优于子类或者比子类拥有更多的功能,恰恰相反,子类比超类拥有的功能更加丰富。

在Manager类中,增加一个用于存储奖金的域,并且添加一个用于设置这个域的方法。

public class Manager extends Employee{
    private double bonus;
    ...
    public void setBonus(double bonus){
        this.bonus = bonus;
    }
}

Manager类的setBonus方法,Employee类的对象是不能使用的。但是由于Manager类继承于Employee类,所以事实上Manager类除了bonus域还有另外三个域:name,salary,hireDay,所以Manager类对象就有4个域:name,salary,bonus,hireDaty。那么在方法上也是同理,Manager类对象可以使用getName和getHireDay方法。

综上所述,我们在设计超类时,就应该将通用的方法放在超类中,而将有特殊用途的方法放在子类中。

(二)覆盖方法

在Java中使用继承的时候,超类中的有些方法对子类并不一定适用。例如:Manager类中的getSalary方法应该返回的是salary和bonus的总和,所以此时我们需要提供一个新的方法来覆盖(override)超类中的这个方法。

public double getSalary() {
    return bonus+salary;
}

乍看起来可以这么写,只要返回bonus和salary的总和,但是此方法却不能运行。因为Manager类的getSalary方法不能直接访问超类中的私有域。也就是说因为Employee类的salary域是私有的,任何其他类对象都不能访问另一个类的私有域。那么是不是可以这样写呢?

public double getSalary() {
    double baseSalary = getSalary();
    retrun baseSalary+bonus;
}

事实证明,上面这段代码也不能运行。因为Manager类自己也有一个getSalary方法,也就意味着Manager类的getSalary方法中还在调用自己,所以这条语句将会导致无限次地调用自己,直到整个程序崩溃为止。那么如何使我们调用的getSalary方法是Employee类的,而不是自己的。为此,我们需要使用super关键字。

public double getSalary() {
    double baseSalary = super.getSalary();
    return baseSalary+bonus;
}

在子类中,可以增加域,增加方法或者覆盖超类的方法,但绝不能删除继承的域或方法。

(三)子类构造器

我们知道构造器的作用是用来初始化对象,那么在继承中,我们也需要为子类添加构造器。由于子类不能访问超类的私有域,所以我们需要使用超类的构造器来对这部分私有域来进行初始化。同样可以说使用super关键字来调用超类的构造器。

注意:如果子类没有显示的调用超类的构造器,那么子类将自动调用超类的无参构造器。如果超类没有无参构造器,并且在子类中又没有显示地调用其他构造器,那么Java编译器将会报错。

public Manager(String name,double salary,int year,int month,int day){
    super(name,salary,year,month,day);
    bonus=0;
}

super关键字有两个用途:①调用超类的方法,②调用超类的构造器(在调用构造器时,只能作为另一个构造器的第一条语句出现。)

package CoreJava;

import java.time.LocalDate;

public class Emp {
	private String name;
	private double salary;
	private LocalDate hireDay;
	
	public Emp(String name,double salary,int year,int month,int day) {
		this.name=name;
		this.salary = salary;
		hireDay = LocalDate.of(year, month, day);
	}

	public String getName() {
		return name;
	}

	public double getSalary() {
		return salary;
	}

	public LocalDate getHireDay() {
		return hireDay;
	}
	
	public void raiseSalary(double byPercent) {
		double raise = salary*byPercent/100;
		salary+=raise;
	}
}
package CoreJava;

public class Manager extends Emp {
	private double bonus;
	
	public Manager(String name,double salary,int year,int month,int day) {
		super(name,salary,year,month,day);//super出现在构造器的第一句
		bonus=0;
	}
	public double getSalary() {
		double baseSalary = super.getSalary();
		return bonus+baseSalary;
	}
	public void setBonus(double b) {
		bonus = b;
	}
}
//super出现在构造器的第一句
		bonus=0;
	}
	public double getSalary() {
		double baseSalary = super.getSalary();
		return bonus+baseSalary;
	}
	public void setBonus(double b) {
		bonus = b;
	}
}
package CoreJava;

public class ManagerTest{
	public static void main(String[] args) {
		Manager boss = new Manager("Carl Cracker",80000,1987,12,15);
		boss.setBonus(5000);
		
		Emp[] staff = new Emp[3];
		staff[0] = boss;
		staff[1] = new Emp("Harry Hacker",50000,1989,10,1);
		staff[2] = new Emp("Tommy Tester",40000,1990,3,15);
		
		for(Emp e : staff) {
			System.out.println("name="+e.getName()+",salary = "+e.getSalary());
		}
	}
}

(四)继承层次

继承并不仅限于一个层次。由一个超类派生出来的所有类的集合被称为继承层次。在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链。

通常一个祖先类可以拥有多个子孙继承链。

(五)多态

有一个规则可以用来判断是否应该设计为继承关系,就是“is-a”规则。它表明子类的每个对象也是超类的对象。例如:每个经理都是雇员,但不是每个雇员都是经理。

在Java中,对象变量是多态的。一个超类变量既可以引用该类的对象,也可以引用该类任何一个子类的对象。在上个例子中,staff[0]和boss引用同一个对象,但是编译器将staff[0]看成是Emp类对象,也就是说可以这样调用:

boss.setBonus(5000);//OK

但是不能这样调用:

staff[0].setBonus(5000);//Error 因为staff[0]声明的类型是Emp,而setBonus不是Emp类的方法

但是不能将一个超类的引用赋给子类变量。原因很简单,不是每个雇员都是经理。

(六)阻止继承:final类和方法

有时候我们希望某个类不能被继承。那么我们可以使用final修饰符来修饰这个类。

public final class Executive{
    ...
}

类中特定的方法也可以被声明为final,如果这样做,那么子类就不能覆盖这个方法(final类中所有的方法都自动成为final方法)。

public class Employee{
    ...
    public final String getName(){
       return name;
    }
}

(七)强制类型转换

我们在基本类型数据中见到过强制类型转换,是指大转到小,这样带来的结果可能是丢失精度。在对象变量也可以使用强制类型转换,但是应该满足以下条件:

①只能在继承层次类进行类型转换

②在将超类转换成子类之前,应该使用instanceof进行检查

在一般情况下,需要使用强制类型转换去调用某个方法时,就应该考虑超类的设计是否合理。应该尽量少用类型转换和instanceof运算符

(八)抽象类

如果自下而上在类的继承层次中上移,位于上层的类更具有通用性,甚至可能更加抽象。从某个角度看,祖先类应该更加通用,我们可以只把它作为派生其他类的基类,而不作为想使用的特定的实例类。

抽象类使用abstract关键字,抽象类中有抽象方法,但是还可以包含具体数据和具体方法。我们建议尽量将通用的域和方法(不管是否是抽象的)放在超类(不管是否是抽象类)中。

注意:①抽象类中可以没有抽象方法,但是抽象方法一定要在抽象类中。

          ②抽象类不能被实例化。也就是说,如果一个类声明为abstract,就不能创建这个类的对象。

package abstractClasses;

public abstract class Person {
	public abstract String getDescription();
	private String name;
	
	public Person(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
}

package abstractClasses;

import java.time.LocalDate;

public class Employee  extends Person{
	private double salary;
	private LocalDate hireDay;
	
	public Employee(String name,double salary,int year,int month,int day) {
		super(name);
		this.salary=salary;
		hireDay = LocalDate.of(year, month, day);
	}

	public double getSalary() {
		return salary;
	}

	public LocalDate getHireDay() {
		return hireDay;
	}
	
	public String getDescription() {
		return String.format("an employee with a salary of $%.2f", salary);
	}
	
	public void raiseSalary(double byPercent) {
		double raise = salary*byPercent/100;
		salary += raise;
	}
}
package abstractClasses;

public class Student extends Person {
	private String major;
	
	public Student(String name,String major) {
		super(name);
		this.major = major;
	}
	
	public String getDescription() {
		return "a student majoring in "+major;
	}
}
package abstractClasses;

public class PersonTest {
	public static void main(String[] args) {
		Person[] people = new Person[2];
		people[0] = new Employee("Harry Hacker",50000,1987,10,1);
		people[1] = new Student("Maria Morris","computer science");
		for(Person p : people) {
			System.out.println(p.getName()+","+p.getDescription());
		}
	}
}

CoreJava读书笔记--继承(一)--类、超类和子类_第1张图片

(九)受保护的访问

一般情况下,我们建议将域标记为private,将方法标记为public,在继承中,就算子类也不能访问超类的私有域。然而,在有些时候,我们希望超类中的某些方法允许被子类访问,或者允许子类的方法访问超类的某个域。为此,可以将这些方法或域声明为protected。

Java中用于控制可见性的4个访问修饰符:

①仅对本类可见——private

②对所有类可见——public

③对本包和所有子类可见——protected

④对本包可见——默认,不需要修饰符

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(读书笔记)