5.1 类、超类和子类

声明:这章的内容可能会引用上章的有关例子,使用例子时会直接引用不再做过多描述,有不懂的地方请查阅之前的学习笔记。
之前有构造一个雇员Employee类,实际公司雇员中还包含经理等一些其他特殊职位的雇员,他们的一些情况信息会有所不同。例如薪金的算法。假设雇员只有固定的基本薪金,而经理有相应的奖金制度。这样二者绝大多数情况一样仅存在少数的不同,若要为经理重新创建一个新类显然是不实际的。为了解决这样的问题就引入了本章的内容——继承。

1、继承用关键字extends表示正在构造的新类派生于已存在的类,写法如下:
public Manager extends Employee {
//添加域和方法
}
已存在的类被称为超类(superclass)、基类(baseclass)或父类(parentclass);新类被称为子类(subcalss)、派生类(derivedclass)或孩子类(childclass)

在Java中所有的继承都是公有继承(所谓的公有继承就是不改变超类成员在子类中的访问权限。即超类中的public成员,private成员或protect成员在子类中仍为原有访问权限)

2、在Manager类中增加一个存放奖金的域并且设置一个操作这个域的方法:
class Manager extends Empolyee {
...
public void setBonus(double aBonuds) {
bonus = aBonuds;
}
private double bonus;
}
如果有个Manager对象就可以使用setBouns方法:
Manager boss = new Manager(...);
boss.setBonus(5000);

3、因为setBouns方法不是在Employee类中定义的,所以Employee类不能使用此方法。但是在Manager类中虽然没有显式地定义getName和getHireDay方法但是属于Manager类的对象可以使用它们,因为Manager类自动继承了Employee类的方法。当然除了继承方法还继承了超类的所有域。所以在扩展子类时只需指明与超类的不同之处即可。
因此,在设计类时通用类应该放在超类中,特殊类放在相应的子类中。避免重复书写代码。

4、在超类中有些方法并不完全适用与子。例如,Manager类中的getSalary方法需要返回薪金和奖金总和。这就需要提供一个新的方法来重写(override)超类中的方法:(注意重写的方法仅在相应的子类中有效,不会改变超类中的方法)
calss Manager extends Employee {
...
public double getSalary() {
...
}
}问题是如何实现这个方法呢?

1)只要返回salary和bonus域的总和就行了。(实际是不可行的)
public doublie getSalary() {
return salary + bonus;//不可行
}
因为Manager类的getSalary方法不可以直接访问超类的私有域。

2)试图调用Employee类的getSalary方法(想法是对的,但是下面操作有误)
public double getSalary() {
double baseSalary = getSalary; //不可行
return baseSalary + bonus;
}
因为Manager类中也有一个getSalary方法(就是正在实现的方法),这样书写会导致程序无限制调用自己直至程序崩溃!

3)我们想调用的是超类中的getSalary方法,而不是当前类中的同名方法,就要通过关键字super实现:
public double getSalary() {
double baseSalary = super.getSalary();
return baseSalary + bonus;
}

5、关键字this和super的异同:
1)super与this的引用是不同概念,super不是一个对象的引用,不能将super赋给一个对象变量,super只是一个指示编译器调用超类方法的关键字。

2)this有两个用途:(1)引用隐式参数;(2)调用该类的其他构造器。
super也是两个用途:(1)调用超类方法;(2)调用超类构造器。
二者使用方式一致:调用另一个构造器的语句必须放在这个构造器的第一句代码中。构造参数即可以传递给本类(this)的其他构造器,也可以传递给超类(super)的构造器。

6、子类可以增加域、方法或重写方法,但是绝对不能删除继承的任何域和方法!!!

7、用super关键字调用超类中的构造器:
public Manager(String aName, double aSalary, int year, int month, int day) {
super(aName, aSalary, year, month, day);
bonus = 0;
}
语句super(aName, aSalary, year, month, day);是调用超类Employee中含有aName, aSalary, year, month, day参数的构造器。

因为子类中的构造器不能访问超类中的私有域,所以用super调用超类中的构造器对这部分数据初始化。

8、源代码:例5-1,展示Employee对象与Manager对象在计算薪金上的区别:
ManagerTest.java:
package com.vincent.corejava.managertest;

public class ManagerTest {
	public static void main(String[] args) {
		Manager boss = new Manager("vincent", 10000, 1991, 7, 15);
		boss.setBonus(8000);
	
	
	Employee[] staff = new Employee[3];
	staff[0] = boss;
	staff[1] = new Employee("huangchang", 5000, 1991, 10, 1);
	staff[2] = new Employee("wulin", 6000, 1991, 8, 8);
	
	for (Employee e : staff)
		System.out.println("name: " + e.getName()
				+ ", salary: " + e.getSalary());
	}
}

Manager.java:
package com.vincent.corejava.managertest;

public class Manager extends Employee {

	private double bonus;

	public Manager(String aName, double aSalary, int year, int month, int day) {
		super(aName, aSalary, year, month, day);
		bonus = 0;
	}
	
	public double getSalary() {
		double baseSalary = super.getSalary();
		return baseSalary + bonus;
	}
	
	public void setBonus(double aBonus) {
		bonus = aBonus;
	}

}

Employee.java:
package com.vincent.corejava.managertest;

import java.util.Date;
import java.util.GregorianCalendar;

public class Employee {
	private String name;
	private double salary;
	private Date hireDay;

	public Employee(String aName, double aSalary, int year, int month, int day) {
		name = aName;
		salary = aSalary;
		GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
		hireDay = calendar.getTime();
	}
	
	public String getName() {
		return name;
	}
	
	public double getSalary() {
		return salary;
	}
	
	public Date getHireDay() {
		return hireDay;
	}
	
	public void raiseSalary (double byPercent) {
		double raise = salary * byPercent / 100;
		salary += raise;
	}
}


运行结果:
name: vincent, salary: 18000.0
name: huangchang, salary: 5000.0
name: wulin, salary: 6000.0

注意:代码中e.getSalary()能够确定执行哪个getSalary方法。虽然e被声明在Employee类中,实际e可以引用Employee对象也可以引用Manager对象。一个对象变量(例如变量e)可以引用多种实际类型的现象被称为多态。在运行中自动选择调用哪个方法的现象称为动态绑定。

5.1.3 动态绑定
弄清调用过程是很重要的,下面是调用的详细过程:
1)编译器查看对象的声明类型和方法名。此时,编译器获取了所有可能被调用的候选方法。
2)编译器查看调用方法时提供的参数类型。此时,编译器获取了需要调用的方法名和参数类型。
3)如果是private、static、final方法或者构造器,编译器可以很准确的知道调用哪个方法,这种调用方式称为静态绑定。
对应的是,依赖于隐式参数的实际类型,并在运行过程中实现动态绑定。
4)当程序运行并采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类最合适的那个类方法。
像这样每次调用方法都要进行搜索,时间开销很大,所以JVM预先为每个类创建好了方法表,表中列出了所有方法的签名和实际调用方法。这样需要调用方法时只需查找这个表就行了。

在运行时,调用e.getSalary()方法的过程:
1)JVM提取e的实际类型的方法。
2)JVM搜索定义getSalary签名的类。此时JVM已经知道该调用哪个方法了。
3)JVM调用方法。

5.1.4 组织继承:final类和方法
不允许扩展的类称为final类。定义类时使用final修饰符表明这个类是final类。声明格式:
final class Executive extends Manager {
...
}

类中的方法也能被声明final。这样做就表明子类不能重写这个方法(final类中的所有方法自成为final方法)。例如:
class Employee {
public final String getName() {
return name;
}
...
}
注释:域也可以声明为final。对final域来说,构造对象之后就不允许改变他的值。如果将一个类声明为final类,只是他的方法会自动变成final方法,域不会改变。

5.1.6 抽象类
1、祖先类更为通用和抽象,它往往用来作为派生其他类的超类,而不作为要实现特定的实例类。抽象类使关键字abstract修饰:
abstract class Person {
....
}

2、包含一个以上抽象方法的类要将类声明为抽象类。当然,抽象类除了包含抽象方法外,还可以有具体数据和具体方法。例如:Person类还保存着姓名和返回姓名的方法:
abstract class Person {
public Person(String aName) {
name = aName;
}
public abstract String getDescription();
public String getName() {
return name;
}
private name;
}
提示:很多程序员认为抽象类中不能包含具体方法。建议尽量将通用的域和方法(不管是不是抽象的)放在超类中。
类即使不含抽象方法也可以定义为抽象类。抽象类不能被实例化(即一个被声明为abstract的类不能new出相应的对象)。

3、源代码:例5-2中定义了抽象超类Person和两个具体子类Employee和Student。
PersonTest.java:
package com.vincent.corejava.persontest;

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

}

Person.java:
package com.vincent.corejava.persontest;

abstract class Person {
	private String name;

	public Person(String aName) {
		name = aName;
	}
	
	public abstract String getDescription();
	
	public String getName() {
		return name;
	}
}

Employee.java:
package com.vincent.corejava.persontest;

import java.util.Date;
import java.util.GregorianCalendar;

public class Employee extends Person {

	private double salary;
	private Date hireDay;

	public Employee(String aName, double aSalary, int year, int month, int day) {
		super(aName);
		salary = aSalary;
		GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
		hireDay = calendar.getTime();
	}
	
	public double getSalary() {
		return salary;
	}
	
	public Date getHireDay() {
		return hireDay;
	}

	@Override
	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;
	} 

}

Student.java:
package com.vincent.corejava.persontest;

public class Student extends Person {
	private String major;

	public Student(String aName, String aMajor) {
		super(aName);
		major = aMajor;
	}

	@Override
	public String getDescription() {
		return "a student majoring in " + major;
	}
}


运行结果:
Harry,an employee with a salary of $5000.00
Vincent,a student majoring in computer science

5.1.7 受保护的访问
1、建议将域标记为private,将方法标记为public。如果希望超类中的某些方法被子类访问,或允许子类的方法访问超类的某个域,就需要将这些方法或域标记为protected。注意:要谨慎使用protected属性,滥用可能导致破坏数据封装原则!!!
相比而言将方法标记为protecetd要实际的多,如果要限制某个方法的使用,就可以将它声明为protected

2、归纳下Java中用于控制访问权限的修饰符:
1)private——仅本类可访问
2)public——所有类均可访问
3)protected——本包和所有子类可访问
4) 默认(即没有任何修饰符标识)——本包可访问
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值