Java学习day026 继承(定义子类、覆盖方法、子类构造器) - Go语言中文社区

Java学习day026 继承(定义子类、覆盖方法、子类构造器)


使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。

day026  继承(定义子类、覆盖方法、子类构造器)

前面学习了类和对象的概念,从今天开始学习面向对象程序设计的另外一个基本概念:继承(inheritance)。利用继承,人们可以基于已存在的类构造一个新类。继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域,以满足新的需求。这是Java程序设计中的一项核心技术。

先来学习类、超类、子类的有关关知识。

回忆一下之前的Employee类。假设你在某个公司工作,这个公司中经理的待遇与普通雇员的待遇存在着一些差异。不过,他们之间也存在着很多相同的地方,例如,他们都领取薪水。只是普通雇员在完成本职任务之后仅领取薪水,而经理在完成了预期的业绩之后还能得到奖金。这种情形就需要使用继承。这是因为需要为经理定义一个新类Manager,以便增加一些新功能。但可以重用Employee类中已经编写的部分代码,并将其中的所有域保留下来。从理论上讲,在Manager与Employee之间存在着明显的“is-a”(是)关系,每个经理都是一名雇员:“is-a”关系是继承的一个明显特征。

在下面的例子中,假设公司里只有两类人:一些人一直是员工,另一些人一直是经理。


1.定义子类

下面是由继承Employee类来定义Manager类的格式,关键字extends表示继承。

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

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

尽管Employee类是一个超类,但并不是因为它优于子类或者拥有比子类更多的功能。实际上恰恰相反,子类比超类拥有的功能更加丰富。例如,读过Manager类的源代码之后就会发现,Manager类比超类Employee封装了更多的数据,拥有更多的功能。

在Manager类中,增加了一个用于存储奖金信息的域,以及一个用于设置这个域的新方法:

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

这里定义的方法和域并没有什么特别之处。如果有一个Manager对象,就可以使用setBonus方法。

Manager boss = ...;
boss.setBomus(5000);

当然,由于setBonus方法不是在Employee类中定义的,所以属于Employee类的对象不能使用它。

然而,尽管在Manager类中没有显式地定义getName和getHireDay等方法,但属于Manager类的对象却可以使用它们,这是因为Manager类自动地继承了超类Employee中的这些方法。

同样,从超类中还继承了name、salary和hireDay这3个域。这样一来,每个Manager类对象就包含了4个域:name、salary、hireDay和bonus。

在通过扩展超类定义子类的时候,仅需要指出子类与超类的不同之处。因此在设计类的时候,应该将通用的方法放在超类中,而将具有特殊用途的方法放在子类中,这种将通用的功能放到超类的做法,在面向对象程序设计中十分普遍。


2.覆盖方法

然而,超类中的有些方法对子类Manager并不一定适用。具体来说,Manager类中的getSalary方法应该返回薪水和奖金的总和。为此,需要提供一个新的方法来覆盖(override)超类中的这个方法:

public class Manager extends Employee
{
    ...
    public double getSalary()
    {
        ...
    }
    ...
}

应该如何实现这个方法呢?乍看起来似乎很简单,只要返回salary和bonus域的总和就可以了:

public double getSalary()
{
    return salary+bonus;//won't work
}

然而,这个方法并不能运行。这是因为Manager类的getSalary方法不能够直接地访问超类的私有域。也就是说,尽管每个Manager对象都拥有一个名为salary的域,但在Manager类的getSalary方法中并不能够直接地访问salary域。只有Employee类的方法才能够访问私有部分。如果Manager类的方法一定要访问私有域,就必须借助于公有的接口,Employee类中的公有方法getSalary正是这样一个接口。现在,再试一下。将对salary域的访问替换成调用getSalary方法。

public double getSalary()
{
    double baseSalary = getSalary();//still won't work
    return baseSalary + bonus;
}

上面这段代码仍然不能运行。问题出现在调用getSalary的语句上,这是因为Manager类也有一个getSalary方法(就是正在实现的这个方法),所以这条语句将会导致无限次地调用自己,直到整个程序崩溃为止。

这里需要指出:我们希望调用超类Employee中的getSalary方法,而不是当前类的这个方法。为此,可以使用特定的关键字super解决这个问题:

super.getSalary()

上述语句调用的是Employee类中的getSalary方法。下面是Manager类中getSalary方法的正确书写格式:

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

有些人认为super与this引用是类似的概念,实际上,这样比较并不太恰当。这是因为super不是一个对象的引用,不能将super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。

正像前面所看到的那样,在子类中可以增加域、增加方法或覆盖超类的方法,然而绝对不能删除继承的任何域和方法。


3.子类构造器

在例子的最后,我们来提供一个构造器。

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

这里的关键字super具有不同的含义。语句

super(n,s,year,month,day);

是“调用超类Employee中含有n、s、yearmonth和day参数的构造器”的简写形式。

由于Manager类的构造器不能访问Employee类的私有域,所以必须利用Employee类的构造器对这部分私有域进行初始化,我们可以通过super实现对超类构造器的调用。使用super调用构造器的语句必须是子类构造器的第一条语句。

如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器’则Java编译器将报告错误。

关键字this有两个用途:一是引用隐式参数,二是调用该类其他的构造器,同样,super关键字也有两个用途:一是调用超类的方法,二是调用超类的构造器。在调用构造器的时候,这两个关键字的使用方式很相似。调用构造器的语句只能作为另一个构造器的第一条语句出现。构造参数既可以传递给本类(this)的其他构造器,也可以传递给超类(super)的构造器。

重新定义Manager对象的getSalary方法之后,奖金就会自动地添加到经理的薪水中。下面给出一个例子,其功能为创建一个新经理,并设置他的奖金:

Manager boss = new Manager ("Carl Cracher",80000,1987,12,15);
		boss.setBonus(5000);

下面定义一个包含3个雇员的数组:

Employee[] staff = new Employee[3];

将经理和雇员都放到数组中:

staff[0] = boss;
staff[1] = new Employee("Harry Hacker",50000,1989,10,1);
staff[2] = new Employee("Tommy Tester",40000,1990,3,15);

输出每个人的薪水:

for (Employee e : staff)
		{
			System.out.println("name="+e.getName()+",salary="+e.getSalary());
		}

运行这条循环语句将会输出下列数据:

Carl Cracker 85000.0
Harry Hacker 50000.0
Tommy Tester 40000.0

这里的staff[1]和staff[2]仅输出了基本薪水,这是因为它们对应的是Employee对象,而staff[0]对应的是Manager对象,它的getSalary方法将奖金与基本薪水加在了一起。需要提到的是,

e.getSalary()

调用能够确定应该执行哪个getSalary方法。请注意,尽管这里将e声明为Employee类型,但实际上e既可以引用Employee类型的对象,也可以引用Manager类型的对象。

当e引用Employee对象时,e.getSalary()调用的是Employee类中的getSalary方法;当e引用Manager对象时,e.getSalary()调用的是Manager类中的getSalary方法。虚拟机知道e实际引用的对象类型,因此能够正确地调用相应的方法。

一个对象变量(例如,变量e)可以指示多种实际类型的现象被称为多态(polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamicbinding)。

在Java中,不需要将方法声明为虚拟方法。动态绑定是默认的处理方式。如果不希望让一个方法具有虚拟特征,可以将它标记为final。


用下面的代码测试一下:

//package inheritance;

/**
 *@author  zzehao
 */

public class ManagerTest 
{
	public static void main(String[] args) 
	{
		//construct a Manager object
		Manager boss = new Manager ("Carl Cracher",80000,1987,12,15);
		boss.setBonus(5000);

		//fill the staff array with Manager and Employee objects
		Employee[] staff = new Employee[3];

		staff[0] = boss;
		staff[1] = new Employee("Harry Hacker",50000,1989,10,1);
		staff[2] = new Employee("Tommy Tester",40000,1990,3,15);

		//print out information about all Employee objects
		for (Employee e : staff)
		{
			System.out.println("name="+e.getName()+",salary="+e.getSalary());
		}
	}
}
//package inheritance;

/**
 *@author  zzehao
 */

 import java.time.*;

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

	public Employee(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 inheritance;

/**
 *@author  zzehao
 */

public class Manager extends Employee
{
	private double bonus;

	/**
	 *@param name the employee's name
	 *@param salary the salary
	 *@param year the hire year
	 *@param month the hire month
	 *@param day the hire day
	 */

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

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

	public void setBonus(double b)
	{
		bonus = b;
	}
}

运行的结果:


 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Zzehao11/article/details/105279709
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢