前言
第 4 章讨论了类的构成以及用类创建对象,主要体现了面向对象编程的一个重要特点——数据的封装。这一章节讲面向对象编程的另一个特点——类的继承、和继承有关的多态以及接口、内部类等重要内容。
基本知识
一、子类与父类
继承是一种由已知的类创建新类的机制。创建的新类继承一般类的状态和行为,并根据需要增加它自己的新的状态和行为。由继承得到的类称为子类,被继承的类称为父类(超类)。
Java 语言不支持多重继承,即子类只能有一个父类。
在类的声明中,通过使用关键字 extends 来声明一个类的子类。如:
class Students extends People
{ ...
}
其中,把 Students 声明为 People 类的子类,People 是 Students 的父类。
如果一个类的声明中没有使用 extends 关键字,这个类被系统默认为是 Object 的子类。Object 是 java.lang 包中的类。
二、子类的继承性
所谓子类继承父类的成员变量,就是说好像这些成员变量是由子类本身直接声明的一样,可以被子类中自己声明的任何实例方法操作调用。一个子类继承的成员应当是这个类的完全意义的成员。
1.子类和父类在同一包中的继承性
(1)如果子类和父类在同一个包中,则子类自然地继承了其父类中不是 private 的成员变量或方法,作为自己的成员变量或方法。
(2)继承的成员变量或方法的访问权限保持不变。
例(该例体现了子类和父类在同一包中的继承性):
class Father
{ private int money;
int weight;
int getWeight()
{ return weight;
}
protected void setWeight()
{ weight=w;
}
}
class Son extends Father
{ String hand;
public void setHand(String hand)
{ this.hand=hand;
}
String getHand()
{ return hand;
}
}
class Grandson extends Son
{ String foot;
public void setFoot(String foot)
{ this.foot=foot;
}
String getFoot()
{ return foot;
}
}
public class Example5_1
{ public static void main(String[] args)
{ Son son=new Son();
Grandson grandson=new Grandson();
son.setWeight(67);
son.setHand("一双大手");
grandson.setWeight(25);
grandson.setHand("一双小手");
grandson.setFoot("一双小脚");
System.out.println("儿子重量:"+son.getWeight());
System.out.println("孙子重量:"+grandson.getWeight());
System.out.println("儿子的手:"+son.getHand());
System.out.println("孙子的手:"+grandson.getHand());
System.out.println("孙子的脚:"+grandson.getFoot());
}
}
运行结果:
D:\3000>java Example5_1
儿子重量:67
孙子重量:25
儿子的手:一双大手
孙子的手:一双小手
孙子的脚:一双小脚
2.子类和父类不在同一包中的继承性
(1)当子类和父类不在同一包中时,子类能继承父类的 protected、public 成员变量或方法作为子类的成员变量或方法,不能继承父类的友好变量或友好方法。
(2)继承的成员变量或方法访问权限保持不变。
3.protected 的进一步说明
一个类 A 中的 protected 成员变量和方法可以被它的直接子类和间接子类继承。
(1)如果 A 的某一子类在其本身中创建了一个对象,那么该对象总是可以通过 “.” 运算符访问继承的或自己定义的 protected 变量和 protected 方法。
(2)如果在另外一个例如 Other 类中用 D 类创建了一个对象 object,该对象通过 “.” 运算符访问 protected 变量和 protected 方法的权限如下:
①如果 object 要访问子类 D 中声明的 protected 成员变量或方法,只要 Other 类和 D 类在同一个包中即可。
②如果子类 D 的对象的 protected 成员变量或方法从父类继承而来,则需要一直追溯到该 protected 成员变量或方法的 “祖先类”,如果 Other 类和该 “祖先类” 在同一包中,则 object 对象可以访问继承的 protected 变量或方法。
三、子类对象的构造过程
当用子类的构造方法创建一个子类的对象时,该构造方法总是先调用父类的某个构造方法。
当用子类创建对象时,父类的成员变量也都分配了内存空间,但只将其中一部分作为子类对象的成员变量。
例如父类中的 private 成员变量尽管分配了内存空间,但不作为子类对象的成员,当然它们也不是父类某个对象的成员,因为根本没有使用父类创建任何对象。
子类中还有一部分方法是从父类继承的,这部分方法却可以操作这部分未继承的变量。如下例:
class A
{ private int x;
void setX(int x)
{ this.x=x;
}
int getX()
{ return x;
}
}
class B extends A
{ double y=12;
public void setY(int y)
{ this.x=y+x; //非法,子类没有继承x
}
public double getY()
{ return y;
}
}
public class E
{ public static void main(String[] args)
{ B b=new B();
b.setX(888);
System.out.println("子类对象未继承的x的值是:"+b.getX());
b.y=12.678;
System.out.println("子类对象的实例变量y的值是:"+b.getY());
}
}
四、成员变量的隐藏和方法的重写
1.对于子类可以从父类继承的成员变量,只要子类中声明定义的成员变量和父类中的成员变量同名,子类就隐藏了继承的成员变量,即子类对象以及子类自己声明定义的方法操作。
2.子类通过方法的重写可以隐藏继承的方法,可以把父类的状态和行为改变为自身的状态和行为。
下面的例子中子类重写了父类的方法 f
class A
{ float f(int x,int y)
{ return x+y;
}
public void g()
{ System.out.println("I am a student");
}
}
class B extends A
{ float f(int x,int y) //方法重写,隐藏A中原有的f方法,但保留g方法
{ return x+y;
}
}
public class E
{ public static void main(String[] args)
{ B b=new B();
float result=b.f(3,5); //b调用重写的方法
System.out.println(result);
b.g(); //b调用继承的方法
}
}
重写父类的方法时,不可以降低方法的访问权限。下面的代码中,子类重写父类的方法 f,该方法在父类中的访问权限是 protected 级别,子类重写时不允许级别低于 protected,如:
class A
{ protected float f(float x,float y)
{ return x-y;
}
}
class B extends A
{ float f(float x,float y) //非法,因为降低了访问级别
{ return x+y;
}
}
class C extends A
{ public float f(float x,float y) //合法,没有降低访问级别
{ return x*y;
}
}
附:访问权限关键字等级比较:
public > protected > 友好的 > private