一、继承的概念
当一个对象是另一个对象特殊化之后得到的,我们就说他们俩之间有“is a”的关系(is a relationship),比如一个足球运动员是运动员特殊化之后得到的。
当这种关系存在于OOP的时候时,意味着特殊化的对象有初始的,抽象的对象的所有特征,在面向对象编程中,继承用于创建类之间的“is a”关系,这允许我们通过创建一个特殊化的另一个类来扩展一个类的功能。
继承中有子类(subclass)和超类(superclass),超类是一般化的类,子类是特殊化的类,子类从超类继承字段和方法,而不需要重写任何一个方法。此外,可以在子类中添加新的字段和方法到子类中,这就是使其成为特殊化的超类的原因。
(superclasses are also called base classes, and subclasses are also called derived classes)
二、例子
大多数教师为学生的成绩打分。一个成绩得到一个数字分数,如70、85、90等,以及一个字母分数,如A、B、C、D或F。UML图展示如下
注意这个类并没有显式的构造器,所有Java将使用默认构造器,这将在之后讨论。
/**
A class that holds a grade for a graded activity.
*/
public class GradedActivity
{
private double score; // Numeric score
/**
The setScore method sets the score field.
@param s The value to store in score.
*/
public void setScore(double s)
{
score = s;
}
/**
The getScore method returns the score.
@return The value stored in the score field.
*/
public double getScore()
{
return score;
}
/**
The getGrade method returns a letter grade
determined from the score field.
@return The letter grade.
*/
public char getGrade()
{
char letterGrade;
if (score >= 90)
letterGrade = 'A';
else if (score >= 80)
letterGrade = 'B';
else if (score >= 70)
letterGrade = 'C';
else if (score >= 60)
letterGrade = 'D';
else
letterGrade = 'F';
return letterGrade;
}
}
接下来这个类用来存储另外一次考试的成绩。
/**
This class determines the grade for a final exam.
*/
public class FinalExam extends GradedActivity
{
private int numQuestions; // Number of questions
private double pointsEach; // Points for each question
private int numMissed; // Questions missed
/**
The constructor sets the number of questions on the
exam and the number of questions missed.
@param questions The number of questions.
@param missed The number of questions missed.
*/
public FinalExam(int questions, int missed)
{
double numericScore; // To hold a numeric score
// Set the numQuestions and numMissed fields.
numQuestions = questions;
numMissed = missed;
// Calculate the points for each question and
// the numeric score for this exam.
pointsEach = 100.0 / questions;
numericScore = 100.0 − (missed * pointsEach);
// Call the inherited setScore method to
// set the numeric score.
setScore(numericScore);
}
/**
The getPointsEach method returns the number of
points each question is worth.
@return The value in the pointsEach field.
*/
public double getPointsEach()
{
return pointsEach;
}
/**
The getNumMissed method returns the number of
questions missed.
@return The value in the numMissed field.
*/
public int getNumMissed()
{
return numMissed;
}
}
这个类的头使用了关键字extends,表明这个类会继承另一个类(超类),extends后跟着的就是超类的名称。
超类和子类的字段和方法的关系如下表。
请注意,GradedActivity的score字段没有列出在FinalExam的成员中。这是因为score字段是私有的。子类不能访问超类的私有成员,所以严格意义上来说,它们不会被继承。当创建子类的一个对象时,超类的私有成员存在于内存中,但是只有超类中的方法才能访问它们。
还注意到,超类的构造函数没有列在FinalExam的成员中。超类的构造函数不继承是显然的,因为它们的目的是构造超类的对象,而不是子类对象。之后我们将更详细地讨论超类构造函数是如何操作。
FinalExam exam = new FinalExam(questions, missed);
当在内存中创建FinalExam对象时,它不仅在FinalExam声明成员,而且在GradedActivity中声明非私有成员。当一个子类扩展到一个超类时,该超类的公共成员将成为该子类的公共成员。
三、继承中的UML图
在UML图中显示继承时,将两个类用一根有方向的箭头联系起来,箭头指向超类。
四、继承中的构造器
在继承中,超类的构造器永远先于子类构造器执行,这是一个不变的规则。下面的程序演示了最简单情况时,这个规则的运用。
public class SuperClass1
{
/**
Constructor
*/
public SuperClass1()
{
System.out.println("This is the " +
"superclass constructor.");
}
}
public class SubClass1 extends SuperClass1
{
/**
Constructor
*/
public SubClass1()
{
System.out.println("This is the " +
"subclass constructor.");
}
}
当我们执行这个程序的时候:
/**
This program demonstrates the order in which
superclass and subclass constructors are called.
*/
public class ConstructorDemo1
{
public static void main(String[] args)
{
SubClass1 obj = new SubClass1();
}
}
得到结果如下:
is the superclass constructor.This is the subclass constructor.
如果一个超类有有一个默认的构造函数,或者有一个无参构造函数(简单的情况),那么该构造函数将在一个子类构造函数执行之前被自动调用。
如果一个类没有默认构造函数或无参构造函数,或者这个类有好几个重载的构造函数那该怎么办呢?这时候我们在写子类的构造函数的时候,就需要显式调用超类的构造函数了,这里我们使用关键字super来调用超类的构造函数并传参。
eg:
public class SubClass2 extends SuperClass2
{
/**
Constructor
*/
public SubClass2()
{
super(10);
System.out.println("This is the " +
"subclass constructor.");
}
}
在调用超类的构造函数时,有以下几点注意:
调用超类构造函数的super语句只能只写入子类的构造函数中,不能在其他类或子类的其他方法调用超类构造函数。
调用超类构造函数的super语句必须是子类构造函数中的第一个语句,这是因为该超类的构造函数必须在该子类的构造函数中的代码执行之前执行。
如果子类构造函数没有显式地调用超类构造函数,Java在子类构造函数中的代码执行之前,将自动调用超类的默认构造函数或无arg构造函数,相当于将“super();”放在开头。
下面看这个例子是怎么在子类里使用构造函数的:
public Cube(double len, double w, double h)
{
// Call the superclass constructor.
super(len, w);
// Set the height.
height = h;
}
cube的构造器接受三个参数,其中的两个被用作调用超类的构造函数,而剩下的那个参数正是子类比超类多的字段。
如果超类没有默认的构造函数,也没有无参数构造函数,那么子类必须显式调用超类中有的构造函数之一。如果没有,则在编译子类时将导致错误。
五、protected
除了private 和 public,java还提供了第三种访问表示符号(access specifications)——protected. 类的protected成员可以通过同一类的方法或其子类的方法直接访问,protected成员还可以通过与和protected成员的类在同一包中的其他类的方法来访问。
eg:
/**
A class that holds a grade for a graded activity.
*/
public class GradedActivity2
{
protected double score; // Numeric score
}
......
其子类中的一个方法直接访问score字段,如果score有小数部分且小数部分大于0.5,那么就将向前进一位变成整数(注意这里进位的方法,非常巧妙)
private void adjustScore()
{
double fraction;
// Get the fractional part of the score.
fraction = score − (int) score;
// If the fractional part is .5 or greater,
// round the score up to the next whole number.
if (fraction >= 0.5)
score = score + (1.0 − fraction);
}
受保护的类成员可以用#符号在UML图中表示。
尽管使用protected而不是private可能在某些情况下更简单,但还是应该避免这种做法,因为protected字段所在的类的子类或在同一包中的任何类都具有不受限制的访问权限。最好的方法依旧是将所有字段私有,然后提供访问这些字段的公共方法。
在没有访问表示符的情况下,java默认一个字段可以被字段所在类的包中的所有类访问,但对不在同一个包中的类,即使是这个字段所在类的子类,也无法访问这个字段。
public class Circle
{
double radius;
int centerX, centerY;
(Method definitions follow . . .)
}
在此类中,radius、centerX和centerY字段没有被授予访问限定符,因此编译器授予它们包访问权限。与Circle类在同一包中的任何方法都可以直接访问这些成员。
java中不同情况下对字段的访问权限如下表所示:
六、继承链
有时候一个超类又是另一个超类的子类,如下图所示:
下面我们沿用之前的例子来说明这种复杂的情况。
考虑PassFailActivity类,它继承自GradedActivity类。该类旨在确定当通过考核时返回字母“P”,没有通过时返回字母“F”。
ps:GradedActivity类见本节最开始。
/**
This class holds a numeric score and determines
whether the score is passing or failing.
*/
public class PassFailActivity extends GradedActivity
{
private double minPassingScore; // Minimum passing score
/**
The constructor sets the minimum passing score.
@param mps The minimum passing score.
*/
public PassFailActivity(double mps)
{
minPassingScore = mps;
}
/**
The getGrade method returns a letter grade
determined from the score field. This
method overrides the superclass method.
@return The letter grade.
*/
@Override
public char getGrade()
{
char letterGrade;
if (super.getScore() >= minPassingScore)
letterGrade = 'P';
else
letterGrade = 'F';
return letterGrade;
}
}
PassFailActivity类是顺位在中间的子类,注意getGrade方法重载了,GradedActivity类中也有一个getGrade方法,而 PassFailActivity类中这个getGrade方法中的“super.getGrade”就是调用GradedActivity类中已经被重载的方法的意思。更多关于重载的知识将在后面讨论。
现在假设我们希望用另一个更细化的类来扩展这个类。PassFailExam类决定了考试的通过或失败。它有考试中的问题总数((numQuestions)、每个问题的分值(pointsEach)以及学生遗漏的问题数(numMissed)。
public class PassFailExam extends PassFailActivity {
private int numQuestions; // Number of questions
private double pointsEach; // Points for each question
private int numMissed; // Number of questions missed
/**
* The constructor sets the number of questions, the
* number of questions missed, and the minimum passing
* score.
*
* @param questions The number of questions.
* @param missed The number of questions missed.
* @param minPassing The minimum passing score.
*/
public PassFailExam(int questions, int missed,
double minPassing) {
// Call the superclass constructor.
super(minPassing);
// Declare a local variable for the score.
double numericScore;
// Set the numQuestions and numMissed fields.
numQuestions = questions;
numMissed = missed;
// Calculate the points for each question and
// the numeric score for this exam.
pointsEach = 100.0 / questions;
numericScore = 100.0 −(missed * pointsEach);
// Call the superclass's setScore method to
// set the numeric score.
setScore(numericScore);
}
/**
* The getPointsEach method returns the number of
* points each question is worth.
*
* @return The value in the pointsEach field.
*/
public double getPointsEach() {
return pointsEach;
}
/**
* The getNumMissed method returns the number of
* questions missed.
*
* @return The value in the numMissed field.
*/
public int getNumMissed() {
return numMissed;
}
}
}
这是继承链中顺位最靠下的类。我们还需要一个主函数。
public class PassFailExamDemo
{
public static void main(String[] args)
{
int questions; // Number of questions
int missed; // Number of questions missed
double minPassing; // Minimum passing score
// Create a Scanner object for keyboard input.
Scanner keyboard = new Scanner(System.in);
// Get the number of questions on the exam.
System.out.print("How many questions are " +
"on the exam? ");
questions = keyboard.nextInt();
// Get the number of questions missed.
System.out.print("How many questions did " +
"the student miss? ");
missed = keyboard.nextInt();
// Get the minimum passing score.
System.out.print("What is the minimum " +
"passing score? ");
minPassing = keyboard.nextDouble();
// Create a PassFailExam object.
PassFailExam exam =
new PassFailExam(questions, missed, minPassing);
// Display the points for each question.
System.out.println("Each question counts " +
exam.getPointsEach() + " points.");
// Display the exam score.
System.out.println("The exam score is " +
exam.getScore());
// Display the exam grade.
System.out.println("The exam grade is " +
exam.getGrade());
}
}
在主函数中,新建了PassFailExam类的对象,将一些基本的参数赋给构造器。PassFailExam类的构造器先调用超类构造器,然后初始化PassFailExam类特有的一些字段,在初始化完成后,将最后转化出的得分传给setScore方法(我们在主函数中没有看到程序使用setScore方法,就是因为在这里,当主函数创建PassFailExam对象的时候,已经自动调用setScore方法了),这个方法是超类的超类(GradedActivity类)中的方法,但是这里并不是通过继承关系找到该方法的,而是因为这个方法本来就是公共的,所以不论是不是在继承链中,都可以调用。setScore方法将分数赋给score字段,主函数调用GradedActivity类中的getScore方法返回成绩,调用PassFailActivity类中的getGrade方法返回成绩的等级。