子类与父类
求职者在介绍自己的基本情况时不必“从头说起”,例如,不必介绍自己所具有的人的一般属性等,因为人们已经知道求职者肯定是一个人,已经具有了人的一般属性,求职者只要介绍自己独有的属性就可以了。同样的,当我们装备编写一个类的时候,发现某个类有我们所需的成员变量和方法,如果我们想复用这个类中的成员变量和方法,即在所编写的类中不用声明成员变量就相当于有了这个成员变量,不用定义方法就相当于有了这个方法,那我们可以将编写的类定义为这个类的子类,子类可以让我们不必一切“从头做起”。
子类
在类的声明中,通过使用关键字extends来定义一个类的子类,格式如下
class 子类名extends 父类名{
···
}
例如
class Student extends People{
···
}
这里把Student类定义为People类的子类,People类是Student类的父类(超类)。
子类的继承性
类可以有两种重要的成员:成员变量和方法。子类的成员中有一部分是子类自己声明,定义的,另一部分是从他的父类继承。那么,什么叫继承呢?所谓子类继承父类的成员变量作为自己的一个成员变量,就好像它们是在子类中直接声明一样,可以被子类中自己定义的任何方法实例操作,也就是说,一个子类继承的成员应当是这个类的完全意义的成员,如果子类中定义的实例方法不能操作父类的某个成员变量,该成员变量就没有子类继承;所谓子类继承父类的方法作为子类的一个方法,就像它们是在子类中直接定义一样,可以被子类中自己定义的任何实例方法调用。
如果子类和父类在同一个包中,那么,子类自然地继承其父类中不是private的成员变量,并且也自然地继承了父类中不是private的方法作为自己的方法,继承的成员变量或犯法的访问权限保持不变。
如果子类和父类不在同一个包中,父类中的private和友好访问权限的成员变量不会被子类继承,也就是说,子类只继承父类中的protected和public访问权限的成员变量作为子类的成员变量;同样,子类只继承父类中的protected和public访问权限的方法作为子类的方法。
例子1
public class People{
int age,leg=2,hand=2;
protected void showPeopleMess(){
System.out.printf("%d岁%d只脚%d只手\t",age,leg,hand);
}
}
public class Student extends People{
int number;
void tellNumber(){
System.out.printf("学号:%d\t",number);
}
int add(int x,int y){
return x+y;
}
}
public class UniverStudent extends Student{
int multi(int x,int y){
return x*y;
}
}
public class Example{
public static void main(String args[]){
Student zhang = new Student();
zhang.age = 17; //访问继承的成员变量
zhang.number = 100101;
zhang.showPeopleMess();//调用继承的方法
zhang.tellNumber();
int x=9,y=29;
System.out.print("会做加法:");
int result = zhang.add(x,y);
System.out.printf("%d+%d=%d\n",x,y,result);
UniverStudent geng = new UniverStudent();
geng.age = 21; //访问继承的成员变量
geng.number=6609; //访问继承的成员变量
geng.showPeopleMess(); //访问继承的方法
geng.tellNumber(); //访问继承的方法
System.out.print("会做加法:");
result = geng.add(x,y);
System.out.printf("%d+%d=%d\n",x,y,result);
System.out.print("会做乘法:");
result = geng.multi(x,y);
System.out.printf("%d*%d=%d\n",x,y,result);
}
}
运行结果
子类对象的特点
当用子类的构造方法创建一个子类的对象时,不仅子类中声明的成员变量被分配了内存,而且父类的成员变量也都分配了内存空间,但只将其中的一部分即子类继承的那部分成员变量,作为分配给子类对象的变量。也就是说,父类中的private成员变量尽管分配了内存空间,也不作为子类对象的变量,即子类不继承父类的私有成员变量。同样,如果子类和父类不在同一包中,尽管父类的友好成员变量分配了内存空间,但也不作为子类对象的变量,即如果子类和父类不在同一包中,子类不继承父类的友好成员变量。
下面例子中子类ChianPeople的对象调用继承的方法操作未被子类继承却分配了内存空间的变量
class People1{
private int averHeight = 166;
public int getAverHeight() {
return averHeight;
}
}
class ChinaPeople extends People1{
int height;
public void setHeight(int h) {
//height = h+averHeiht; //非法,子类没有继承averHeight
height = h;
}
public int getHight() {
return height;
}
}
public class Example1 {
public static void main(String args[]) {
ChinaPeople zhangsan = new ChinaPeople();
System.out.println("子类未继承的averageHeight的值是:"+zhangsan.getAverHeight());
zhangsan.setHeight(178);
System.out.println("子类对象的实例变量height的值是:"+zhangsan.getHight());
}
}
运行结果如图
关于instanceof运算符
instanceof是双目运算符,其左面的操作元是对象,右面的操作元是类。当左面的操作元是右面的类或其子类所创建的对象时,instanceof运算的结果是true,否则是false。
成员变量的隐藏
由于在编写子类时我们仍可以声明成员变量,这会出现一种特殊情况:所声明的成员变量的名字和从父类继承来的成员变量的名字相同(声明的类型可以不同),在这种情况下,子类就会隐藏所继承的成员变量
子类隐藏继承的成员变量的特点如下:
1.
子类对象以及子类自己定义的方法操作与父类同名的成员变量是指子类重新声明的这个成员变量
2.
子类对象仍可以调用从父类继承的方法操作被子类隐藏的成员变量,也就是说,子类继承的方法所操作的成员变量一定是被子类继承或隐藏的成员变量
例如
public class Goods {
public double weight;
public void oldSetWeight(double w) {
weight = w;
System.out.println("double型的weight="+weight);
}
public double oldGetPrice() {
double price = weight*10;
return price;
}
}
public class CheapGoods extends Goods{
public int weight;
public void newSetWeight(int w) {
weight = w;
System.out.println("int型的weight="+weight);
}
public double newGetPrice() {
double price = weight*10;
return price;
}
}
public class Example2 {
public static void main(String[] args) {
CheapGoods cheapGoods = new CheapGoods();
//cheapGoods.weight=198.98; 是非法的,因为子类对象的weight变量已经是int型
cheapGoods.newSetWeight(198);
System.out.println("对象cheapGoods的weight的值是"+cheapGoods.weight);
System.out.println("cheap用子类新增的优惠方法计算价格"+cheapGoods.newGetPrice());
cheapGoods.oldSetWeight(198.987); //子类对象调用继承的方法操作隐藏的double型变量weight
System.out.println("cheapGoods使用继承的方法(无优惠)计算价格"+cheapGoods.oldGetPrice());
}
}
运行结果如图
方法重写
子类通过重写可以隐藏已继承的方法(方法重写称为方法覆盖(method overriding))
-
重写的语法规则
如果子类可以继承父类的某个方法,那么子类就有权利重写这个方法。所谓方法重写,是指子类中定义一个方法,这个方法的类型和父类的方法的类型一致或者是父类的方法的类型的子类型(所谓子类型,是指如果父类的方法的类型是“类”,那么允许子类的重写方法的类型是“子类”),并且这个方法的名字,参数个数,参数的类型和父类的方法完全相同。子类如此定义的方法称作子类重写的方法。 -
重写的目的
子类通过方法的重写可以隐藏继承的方法,子类通过方法的重写可以把父类的状态和行为改变为自身的状态和行为。如果父类的方法f()可以被子类继承,子类就有权重写f(),一旦子类重写了父类的方法f(),就隐藏了继承的方法f(),那么子类对象调用方法f()一定调用的是重写方法f();如果子类没有重写,而是继承了父类的方法f(),那么子类创建的对象当然可以调用f()方法,只不过方法f()产生的行为和父类相同而已。
重写方法既可以操作继承的成员变量,调用继承的方法,也可以操作子类新声明的成员变量,调用新定义的其他方法,但无法操作被子类隐藏的成员变量和方法。如果子类想使用被隐藏的方法或成员变量,必须使用关键字super(稍后讲解super的用法)
例如
public class University {
void enterRule(double math,double english,double chinese) {
double total =math+english+chinese;
if(total>=180)
System.out.println(total+"分数达到大学录取线");
else
System.out.println(total+"分数未达到大学录取线");
}
}
public class ImportantUniversity extends University {
void enterRule(double math,double english,double chinese) {
double total =math+english+chinese;
if(total>=220)
System.out.println(total+"分数达到重点大学录取线");
else
System.out.println(total+"分数未达到重点大学录取线");
}
}
public class Example3 {
public static void main(String[] args) {
double math = 62,english = 76.5,chinese = 67;
ImportantUniversity univer = new ImportantUniversity();
univer.enterRule(math, english, chinese);//调用重写的方法
math = 91;
english = 82;
chinese = 86;
univer.enterRule(math, english, chinese);//调用重写的方法
}
}
运行结果如图
子类在重写可以继承的方法时,可以完全按照自己的意图编写新的方法体,以便体现重写方法的独特的行为。重写方法的类型可以是父类方法类型的子类型,既不必完全一致(JDK1.5版本之前的要求完全一致)。
例如
class A{
Object get() {
return null;//返回一个空对象
}
}
class B extends A{
Integer get() { //Integer是Object的子类(Object类是所有类的祖先类)
return new Integer(100); //返回一个Integer对象
}
}
public class Example4 {
public static void main(String[] args) {
B b = new B();
Integer t = b.get();
System.out.println(t.intValue());
}
}
重写父类的访问方法时不允许降低方法的访问权限,但可以提高访问权限(访问限制修饰符按访问权限从高到低的排列顺序是public,protected,友好的,private)
super关键字
子类一旦隐藏了继承的成员变量,那么子类创建的对象就不再拥有该变量,该变量将归关键字super所拥有。同样,子类一旦隐藏了继承的方法,那么子类创建的对象就不能调用被隐藏的方法,该方法的调用由关键字super负责。
class Sum{
int n;
float f() {
float sum = 0;
for(int i=1;i<=n;i++)
sum+=i;
return sum;
}
}
class Average extends Sum{
int n;
float f(){
float c;
super.n = n;
c = super.f();
return c/n;
}
float g() {
float c;
c = super.f();
return c/2;
}
}
public class Example5 {
public static void main(String[] args) {
Average aver = new Average();
aver.n = 100;
float resultOne = aver.f();
float resultTow = aver.g();
System.out.println("resultOne="+resultOne);
System.out.println("resultTow="+resultTow);
}
}
运行结果如图
当用子类的构造方法创建一个子类的对象时,子类的构造方法总是先调用父类的某个构造方法,也就是说,如果子类的构造方法没有明显的指明使用父类的哪个构造方法,子类就调用父类的不带参数的构造方法。
由于子类不继承父类的构造方法,因此,子类在其构造方法中需使用super来调用父类的构造方法,而且super必须是子类构造方法中的头一条语句,即如果在子类的构造方法中,没有明显的写出super关键字来调用父类的某个构造方法,那么默认的有:
super();
例如
class Student{
int number;String name;
Student() {
}
Student(int number,String name){
this.name = name;
this.number = number;
System.out.println("我的学号是"+number+"我的名字是"+name);
}
}
class UniverStudent2 extends Student{
boolean 婚否;
UniverStudent2(int number,String name,boolean b) {
super(number,name);
婚否 = b;
System.out.println("婚否="+婚否);
}
}
public class Example6 {
public static void main(String args[]) {
UniverStudent2 zhang = new UniverStudent2(9901,"小明",false);
}
}
运行结果如图
我们已经知道,如果类中定义了一个或多个构造方法,那么java不提供默认的构造方法(不带参数的构造方法),因此当在父类中定义多个构造方法时,应当包括一个不带参数的构造方法,以防子类省略super时出现错误。
final关键字
final可以修饰类,成员变量和方法中的局部变量。可以使用final将类声明为final类,final类不能被继承,既不能有子类,例如:
final class A{
...
}
如果用final修饰父类的一个方法,那么这个方法不允许子类重写,也就是说不允许子类隐藏可以继承的final方法(老老实实继承,不许做任何篡改)。
如果成员变量或局部变量被修饰为final,那它就是常量。由于常量在运行期间不允许再发生变化,所以常量在声明时没有默认值,这就要求程序在声明常量时必须指定该常量的值。
class A2{
final double PI = 3.1415926; //PI是常量
public double getArea(final double r) {
//r = r+1; //非法,不允许对final进行更新操作
return PI*r*r;
}
public final void speak() {
System.out.println("你好,How's everthing here ?");
}
}
public class Example7 {
public static void main(String[] args) {
A2 a = new A2();
System.out.println("面积:"+a.getArea(100));
a.speak();
}
}
对象的上转型对象
我们经常说“老虎是动物”“狗是动物”等,若动物类是老虎类的父类,这样说当然正确,因为人们习惯的称子类与父类的关系是“is-a”关系。但需要注意的是当说老虎是动物是,老虎将失去老虎独有的属性和功能,从人的思维方式上看,说“老虎是动物”属于上溯思维方式,下面讲解这种思维方式很类似的java语言上的上转型。
假设Animal类是Tiger的类的父类,当用子类创建一个对象,并把这个对象的引用放到父类的对象中时,例如:
Animal a;
a = new Tiger();
或
Animal a;
Tiger b = new Tiger();
a = b;
这时,称对象a是对象b的上转型对象(好比说“老虎是动物”)。
对象的上转型对象的实体是子类负责创建的,但上转型对象会失去原对象的一些属性和功能
- 上转型对象不能操作子类新增的成员变量(失掉了这部分属性),不能调用子类新增的方法(失掉了一些行为)
- 上转型对象可以访问子类继承或隐藏的成员变量,也可以调用子类继承的方法或子类重写的实例方法。上转型对象操作子类继承的方法或子类重写的实例方法,其作用等价于子类对象去调用这些方法。因此,如果子类重写了父类的某个实例方法后,当对象的上转型对象调用这个实例方法时一定是调用了子类重写的实例方法。
注:
1.不要将父类创建的对象和子类对象的上转型混淆。
2.可以将对象的上转型对象再强制转换到一个子类对象,这时,该子类对象又具备了子类所有的属性和功能。
3.不可以将父类创建的对象的引用赋值给子类声明的对象(不能说“人是美国人”)。
4.如果子类重写了父类的静态方法,那么子类对象的上转型对象不能调用子类重写的静态方法,只能调用父类的静态方法。
在下列的例子里,monkey是People类型对象的上转型对象
class 类人猿{
void crySpeak(String s) {
System.out.println(s);
}
}
class Peoplel extends 类人猿{
void computer(int a,int b) {
int c=a*b;
System.out.println(c);
}
void crySpeak(String s) {
System.out.println("***"+s+"***");
}
}
public class Example8 {
public static void main(String[] args) {
类人猿 monkey;
Peoplel geng = new Peoplel();
monkey = geng;//monkey是People对象geng的上转型对象
monkey.crySpeak("I love this game");//等同于geng.crySpeak("I love this game")
Peoplel people = (Peoplel)monkey;//把上转型对象强制转化为子类的对象
people.computer(10, 10);
}
}
运行结果如图
上述例子中,上转型对象monkey调用方法
monkey.crySpeak("I love this game");
得到的结果是***I love this game***而不是I love this game。因为monkey调用的是子类重写的方法crySpeak。需要注意的是:
monkey.computer(10,10)
是错误的,因为computer是子类新增的方法。
继承与多态
我们经常说“哺乳动物有很多种叫声”,例如,“吼”,“嚎”,“汪汪”,“喵喵”等,这就是叫声的多态。当一个类有很多子类时,并且这些子类都重写了父类中的某个方法,那么当把子类创建的对象的引用放到一个父类的对象中时,就得到了该对象的一个上转型对象,那么这个上转型对象在调用这个方法时就可能具有多种形态,因为不同的子类在重写父类的方法时可能产生不同的行为,例如,狗类的上转型对象调用“叫声”方法时产生的行为是“汪汪”,而猫类的上转型对象调用“叫声”方法时,产生的行为是“喵喵”,等等。
多态性就是指父类的某个方法被其子类重写时,可以各自产生自己的行为功能。
例如
class 动物{
void cry(){
}
}
class 狗 extends 动物{
void cry() {
System.out.println("汪汪...");
}
}
class 猫 extends 动物{
void cry() {
System.out.println("喵喵...");
}
}
public class Example9 {
public static void main(String[] args) {
动物 animal;
animal = new 狗();
animal.cry();
animal = new 猫();
animal.cry();
}
}
abstract类和abstract方法
关键字abstract修饰的类称为abstract类(抽象类),例如;
abstract class A{
...
}
用关键字abstract修饰的方法称为abstract方法(抽象方法),例如:
abstract int min(int x,int y);
对于abstract方法,只允许声明,不允许实现(没有方法体),而且不允许使用final和abstract同时修饰一个方法或类,也不允许使用static修饰abstract方法,即abstract方法必须是实例方法。
和普通类(非abstract类)相比,abstract类中可以拥有abstract方法(非abstract类中不可有abstract方法)也可以有非abstract方法。
下面的A类中的min()方法是abstract方法,max()方法是普通方法(非abstract方法)
abstract class A{
abstract int min(int x,int y);
int max(int x,int y){
return x>y?x:y;
}
}
abstract类中也可以没有abstract方法
对于abstract类,我们不能使用new运算符创建该类的对象。如果一个非抽象类是某个抽象类的子类,那么它必须重写父类的抽象方法,给出方法体,这就是为什么不允许同时使用final和abstract方法修饰一个方法或类的原因。
如果一个非abstract类是abstract类的子类,它必须重写父类的abstract方法,即去掉abstract方法的abstract修饰,并给出方法体。如果一个abstract类是abstract类的子类,它可以重写父类的abstract方法,也可以继承父类的abstract方法。
可以使用abstract类声明对象,尽管不能用new运算符创建该对象,但该对象可以成为其子类对象的上转型对象,那么该对象就可以调用子类重写的方法。