学习笔记9(继承)
作者:互联网
一、重写超类的方法
有时子类从超类继承一个方法,但超类的方法并不完全符合子类的要求。所以有时子类需要用更合适的方法来取代不合适的超类方法。这被称为方法重写。
比如在之前的例子中,GradedActivity类有一个方法叫setScore。但是有一个教授希望对分数进行进一步的加权,这是就需要重载setScore方法,设置加权的比例,并执行加权的操作,所以我们创建了一个新类:CurvedActivity
各字段及方法的作用和描述见下表:
需要注意的是,CurvedActivity类中的setScore方法与超类GradedActivity中的setScore方法具有相同的signature。当子类的对象调用setScore方法时,它会调用子类的方法,而不是超类的。
/**
This class computes a curved grade. It extends
the GradedActivity class.
*/
public class CurvedActivity extends GradedActivity
{
double rawScore; // Unadjusted score
double percentage; // Curve percentage
/**
The constructor sets the curve percentage.
@param percent The curve percentage.
*/
public CurvedActivity(double percent)
{
percentage = percent;
rawScore = 0.0;
}
/**
The setScore method overrides the superclass setScore method.
This version accepts the unadjusted score as an argument. That
score is multiplied by the curve percentage and the result is
sent as an argument to the superclass's setScore method.
@param s The unadjusted score.
*/
@Override
public void setScore(double s)
{
rawScore = s;
super.setScore(rawScore * percentage);
}
/**
The getRawScore method returns the raw score.
@return The value in the rawScore field.
*/
public double getRawScore()
{
return rawScore;
}
/**
The getPercentage method returns the curve
percentage.
@return The value in the percentage field.
*/
public double getPercentage()
{
return percentage;
}
注意,@Override注释出现在了setScore方法定义之前。此注释告诉Java编译器,setScore方法旨在重写超类中的一个方法。虽然@Override注释不是必需的,但还是建议使用它。如果该方法无法正确重写超类中的一个方法(比如参数类型不匹配),则编译器将显示一条错误消息,如果不使用@Override,当你希望重写的方法不能重写时,编译器会认为这本来就是两个方法,也就不会给予提示了。
接下来看这行语句:
super.setScore(rawScore * percentage);
super关键词指的是对象的超类。此语句调用超类中的setScore方法,并将表达式rawScore * percentage的结果作为参数传递。
超类的score字段是私有的,并且子类不能直接访问它。为了在超类的分数字段中存储一个值,子类必须调用超类的setScore方法。一个子类可以通过用超关键词和一个点(.)作为前缀来调用一个被重写的超类方法。
这里我们需要注意的不仅仅只是子类如何调用超类中被重写的方法,而是要想清楚为什么这里需要将结果储存到超类的字段中。并且思考子类重写的方法和原来超类的方法的关系,其实子类方法所做的只是改变了传入超类setScore方法中的参数的值,仅此而已。
注意:即使子类重写了超类中的方法,但是当超类对象仍然调用该方法时,仍然调用的是超类的方法,也就是重载之前的方法。
GradedActivity regularExam = new GradedActivity();
regularExam.setScore(85);
因为regularExam引用了一个GradedActivity对象,所以此代码调用了GradedActivity类(超类)中的setScore方法的版本。
ps:被final和static修饰的方法不能被重写。
二、重写和重载的区别
1、重写的方法拥有完全一样的signature,包括方法名以及参数数量,类型。而重载的方法只是方法名一样,方法接受的参数并不是一样的。
2、重写的方法只能出现再继承的关系中,而重载的方法不仅可以出现再继承关系中,还可以出现在没有继承关系的两个类中,甚至是同一个类中。当两个signature完全相同的方法出现在同一个类中的时候,在调用时编译器将不知道调用哪个方法,因此将报错。当不使用super关键字的时候,子类并不能调用超类中被重写的方法,但是子类却可以调用超类中被重载的方法,因为子类中重载的方法和超类中的方法参数不同,所以实际上是两个不同的方法。
三、object类
Java中的每个类,包括API中的类和自己创建的类,直接或间接地继承自一个名为Object的类。
当一个类不继承别的类的时候,java就自动将这个类划归为Object类的子类。
eg:
public class MyClass
{
(Member Declarations . . .)
}
虽然这个类没有关键字extend,但是实际上是这样的:
public class MyClass extends Object
{
(Member Declarations . . .)
}
在这个类中,有两个非常重要的方法:toString方法和equal方法。现在你就明白为什么不论什么对象,都可以使用toString方法和equal方法,因为它们都是Object类的子类,自然可以调用超类中的方法。
四、多态
1、多态简介
GradedActivity类也是FinalExam类的超类。由于超类和子类之间的“is-a”关系,FinalExam类的对象不仅仅是FinalExam的对象。它也是一个GradedActivity对象。((A final exam is a graded activity.)由于这种关系,我们可以使用GradedActivity变量来引用FinalExam对象。
GradedActivity exam = new FinalExam(50, 7);
此语句将exam声明为GradedActivity变量。它将创建一个FinalExam对象,并将该对象的地址存储在exam变量中。此语句是完全合法的,不会导致错误消息,因为FinalExam对象也是一个GradedActivity对象。
多态性一词是指使用多种形式的能力。在Java中,引用变量是多态的,因为它可以引用不同于自身类的对象,只要这些类是其类的子类(GradedActivity可引用FinalExam)。以下所有声明都是合法的,因为FinalExam、PassFailActivity和PassFailExam继承自GradedActivity:
GradedActivity exam1 = new FinalExam(50, 7);
GradedActivity exam2 = new PassFailActivity(70);
GradedActivity exam3 = new PassFailExam(100, 10, 70);
虽然GradedActivity变量可以引用继承自GradedActivity的任何类的对象,但这个变量对这些对象的处理方法是受限的。
eg:GradedActivity有三种方法:setScore, getScore, 和getGrade。所以,GradedActivity变量只能用于调用这三种方法,而不管变量引用的对象类型如何。
GradedActivity exam = new PassFailExam(100, 10, 70);
System.out.println(exam.getScore()); // This works.
System.out.println(exam.getGrade()); // This works.
System.out.println(exam.getPointsEach()); // ERROR! Won't work.
比如getPointsEach是PassFailExam的方法,但是这里将PassFailExam的地址赋给了一个GradedActivity变量,那么这个变量就只能调用GradedActivity中的方法。
2、多态与动态绑定
当我们在使用多态时,一个潜在的问题是:如果子类中重载了超类中的方法,那么当我们在调用改方法时,会调用哪个方法呢?
看下面这个例子:
GradedActivity exam = new PassFailActivity(60);
exam.setScore(70);
System.out.println(exam.getGrade());
调用方法时,找到应该调用的方法被叫做绑定(binding),当我们使用多态时,java采取了一种称为动态绑定的方法(Dynamic Binding),这意味着JVM将在程序运行的时候由对象的类型决定调用哪种方法,而不是变量的类型。在这个例子中,显然就会调用 PassFailActivity类中的getGrade方法,而不是GradedActivity类中的方法。所以说程序会打印P/F,而不是A/B/C/D。
下面这个程序展示了多态的应用:
/**
This program demonstrates polymorphic behavior.
*/
public class Polymorphic
{
public static void main(String[] args)
{
// Create an array of GradedActivity references.
GradedActivity[] tests = new GradedActivity[3];
// The first test is a regular exam with a
// numeric score of 75.
tests[0] = new GradedActivity();
tests[0].setScore(95);
// The second test is a pass/fail test. The
// student missed 5 out of 20 questions, and
// the minimum passing grade is 60.
tests[1] = new PassFailExam(20, 5, 60);
// The third test is the final exam. There were
// 50 questions and the student missed 7.
tests[2] = new FinalExam(50, 7);
// Display the grades.
for (int i = 0; i < tests.length; i++)
{
System.out.println("Test " + (i + 1) + ": " +
"score " + tests[i].getScore() +
", grade " + tests[i].getGrade());
}
}
}
得到的结果如下:
Test 1: score 95.0, grade A Test 2: score 75.0, grade P Test 3: score 86.0, grade B
3、 “is-a”关系不能反过来用
“is-a”关系不能反过来用,因为显然,子类会有一些父类并不具备的特性,比如“a graded activity is a final exam”就是错的,并不是所有的评级活动都是期末考试。所以将GradedActivity类对象赋值给FinalExam类变量就是错的:
GradedActivity activity = new GradedActivity();
FinalExam exam = activity; // ERROR!
而使用强制转换的方法虽然可以通过编译,但是在最终运行的时候,还是会报错。
GradedActivity activity = new GradedActivity();
FinalExam exam = (FinalExam) activity; // Will compile but not run.
4、instanceof操作符
在java中,instanceof操作符可以确定一个对象是不是某个类的实例。instanceof操作符的语法如下:
refVar instanceof ClassName
refVar是变量的名字,而ClassName是类的名字。如果这个变量所对应的对象是这个类的成员,那么就返回ture,反之则返回false。
GradedActivity activity = new GradedActivity();
if (activity instanceof GradedActivity)
System.out.println("Yes, activity is a GradedActivity.");
else
System.out.println("No, activity is not a GradedActivity.");
程序会返回“Yes, activity is a GradedActivity.”
同样的,instanceof也会遵守“is a”关系。如果一个对象是比较的那个类的子类,instanceof操作符也会返回ture:
FinalExam exam = new FinalExam(20, 2);
if (exam instanceof GradedActivity)
System.out.println("Yes, exam is a GradedActivity.");
else
System.out.println("No, exam is not a GradedActivity.");
程序会返回“Yes, exam is a GradedActivity.”
五、抽象类和抽象方法
1、基础概念
抽象方法是一种出现在超类中,但希望在子类中重写的方法。抽象方法只有方法头,而没有方法体,方法体后有分号,示例如下:
public abstract void setValue(int value);
如果在子类中抽象方法没有被重写,那么程序将会报错。抽象方法的作用就是保证子类中会有这个方法。
当一个类有抽象方法的时候,这个类就不能被实例化了。抽象方法一般出现在抽象类中,而抽象类自己不会实例化。同抽象方法一样,抽象类存在的意义就是成为其他子类的超类,或者说是给子类提供一个模板,抽象类就是由继承它的子类抽象出来的。
比如,想象一家制造飞机的工厂。该工厂不生产通用型飞机,只生产三种特定类型的飞机:两种螺旋桨驱动的飞机和一种喷气式飞机。确定飞机“大纲”的软件可能会使用一个名为“飞机”的抽象类。该类的成员代表了所有飞机的共同特征。此外,该软件还为工厂制造的三种特定飞机型号提供了各自所需的类。这些类都扩展了之前抽象的飞机类,它们的成员代表了每种类型的飞机的独特特征。飞机类从未被实例化,而是被用作其他类的超类。
当一个类变成抽象类的时候,你只需要在类定义前加上abstract:
AccessSpecifier abstract class ClassName
抽象类中会有抽象方法,只有抽象方法需要在子类中重写,并不是所有方法都需要被重写。
2、例子
下面这个例子储存了学生的基础信息,但是对每个特定的学生来说,储存的信息不一定是完整的个人信息,这只是一个大框架而已。
/**
The Student class is an abstract class that holds
general data about a student. Classes representing
specific types of students should inherit from
this class.
*/
public abstract class Student
{
private String name; // Student name
private String idNumber; // Student ID
private int yearAdmitted; // Year admitted
/**
The constructor sets the student's name,
ID number, and year admitted.
@param n The student's name.
@param id The student's ID number.
@param year The year the student was admitted.
*/
public Student(String n, String id, int year)
{
name = n;
idNumber = id;
yearAdmitted = year;
}
/**
The toString method returns a String containing
the student's data.
@return A reference to a String.
*/
public String toString()
{
String str;
str = "Name: " + name
+ "\nID Number: " + idNumber
+ "\nYear Admitted: " + yearAdmitted;
return str;
}
/**
The getRemainingHours method is abstract.
It must be overridden in a subclass.
@return The hours remaining for the student.
*/
public abstract int getRemainingHours();
}
在这个类中,有一个抽象方法getRemainingHours,它的作用是返回该学生所剩的学时。对于不同专业的学生,所剩学时的计算方法各不相同,所以需要在子类中再去重写,所以这里使用了抽象方法。
下面是cs专业学生特有的子类:
/**
This class holds data for a computer science student.
*/
public class CompSciStudent extends Student
{
// Required hours
private final int MATH_HOURS = 20; // Math hours
private final int CS_HOURS = 40; // Comp sci hours
private final int GEN_ED_HOURS = 60; // Gen ed hours
// Hours taken
private int mathHours; // Math hours taken
private int csHours; // Comp sci hours taken
private int genEdHours; // General ed hours taken
/**
The Constructor sets the student's name,
ID number, and the year admitted.
@param n The student's name.
@param id The student's ID number.
@param year The year the student was admitted.
*/
public CompSciStudent(String n, String id, int year)
{
super(n, id, year);
}
/**
The setMathHours method sets the number of
math hours taken.
@param math The math hours taken.
*/
public void setMathHours(int math)
{
mathHours = math;
}
/**
The setCsHours method sets the number of
computer science hours taken.
@param cs The computer science hours taken.
*/
public void setCsHours(int cs)
{
csHours = cs;
}
/**
The setGenEdHours method sets the number of
general ed hours taken.
@param genEd The general ed hours taken.
*/
public void setGenEdHours(int genEd)
{
genEdHours = genEd;
}
/**
The getRemainingHours method returns the
the number of hours remaining to be taken.
@return The hours remaining for the student.
*/
@Override
public int getRemainingHours()
{
int reqHours, // Total required hours
remainingHours; // Remaining hours
// Calculate the required hours.
reqHours = MATH_HOURS + CS_HOURS + GEN_ED_HOURS;
// Calculate the remaining hours.
remainingHours = reqHours − (mathHours + csHours
+ genEdHours);
return remainingHours;
}
/**
The toString method returns a string containing
the student's data.
@return A reference to a String.
*/
@Override
public String toString()
{
String str;
str = super.toString() +
"\nMajor: Computer Science" +
"\nMath Hours Taken: " + mathHours +
"\nComputer Science Hours Taken: " + csHours +
"\nGeneral Ed Hours Taken: " + genEdHours;
return str;
}
}
子类里的getRemainingHours方法是根据CS学生所学的课程而特别定制的,对于其他专业的学生这个getRemainingHours方法显然就不适用了。
3、抽象类的UML图
抽象类的UML图和正常类的UML图类似,只是类的名字要求用斜体写出,下面是例子:
标签:GradedActivity,继承,子类,笔记,学习,超类,方法,public,setScore 来源: https://blog.csdn.net/qq_52315213/article/details/120476074