Java中的继承详解
继承的基本知识
继承是面向对象程序设计中的三个基本原则1之一。
在Java语言中,被继承的类被称为超类,继承类被称为子类2。
比如狗类是动物类,牧羊犬类又是狗类。于是我们可以说狗类继承了动物类,而牧羊犬类就继承了狗类。于是狗类就是动物类的子类(或派生类),动物类就是狗类的父类(或基类)。
实现继承将需要使用extends关键字。
如下:
// A simple class hierarchy
class TwoDShape{
double length;
double width;
void show(){
System.out.println("length:" + length + " width:" + width);
}
}
class Triangle extends TwoDShape{
String style;
double area(){
return length * width;
}
void showStyle() {
System.out.println("style:" + style);
}
}
class Shapes {
public static void main(String[] args) {
Triangle triangle = new Triangle();
triangle.length = 2;
triangle.width = 1;
triangle.style = "Rectangle";
triangle.showStyle();
triangle.show();
}
}
输出结果如下:
style:Rectangle
length:2.0 width:1.0
需要注意的是:在Java中不存在多继承,与C++等支持多继承语言不同,所以在转换代码的过程中需要注意,而Java想要实现多继承可以使用implements(实现)接口,在后续博客中将会解释接口知识。
继承的优点:如果有两个类相似,那么它们会有许多重复的代码,导致后果就是代码量大,后期的维护性不高。通过继承就可以解决这个问题,将两段代码中相同的部分提取出来组成一个父类,实现代码的复用。
继承的特点:
- 子类拥有父类除 private 以外的所有属性和方法
- 子类可以拥有自己的属性和方法
- 子类可以重写实现父类的方法
- Java 中的继承是单继承,一个类只有一个父类
成员访问与继承
由类的成员变量具有访问限制可知,private可以防止未经过授权而进行修改,在继承中,也不会超越private的访问权限,如果将上述程序中的length,width声明为private,则会存在错误,Triangle类将会无法直接使用length和width。对此,程序员通常使用访问器方法来进行访问私有成员。
// A simple class hierarchy
class TwoDShape{
private double length;
private double width;
void show(){
System.out.println("length:" + length + " width:" + width);
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
}
class Triangle extends TwoDShape{
private String style;
double area(){
return getLength() * getWidth();
}
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
void showStyle() {
System.out.println("style:" + style);
}
}
class Shapes {
public static void main(String[] args) {
Triangle triangle = new Triangle();
triangle.setLength(2);
triangle.setWidth(1);
triangle.setStyle("Rectangle");
triangle.showStyle();
triangle.show();
}
}
输出结果如下:
style:Rectangle
length:2.0 width:1.0
构造函数与继承
如果超类与子类都存在构造函数,那么是由哪个构造函数来构造子类对象呢?答案是超类构造函数构造超类部分,子类构造函数构造子类部分。
// A simple class hierarchy
class TwoDShape{
private double length;
private double width;
void show(){
System.out.println("length:" + length + " width:" + width);
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
}
class Triangle extends TwoDShape{
private String style;
Triangle(String style, double length, double width){
setLength(length);
setWidth(width);
this.style = style;
}
double area(){
return getLength() * getWidth();
}
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
void showStyle() {
System.out.println("style:" + style);
}
}
class Shapes {
public static void main(String[] args) {
Triangle triangle = new Triangle("Rectangle", 2, 1);
triangle.showStyle();
triangle.show();
}
}
输出结果如下:
style:Rectangle
length:2.0 width:1.0
super关键字
super关键字在子类内部使用,代表父类对象。
使用super调用超类构造函数
// A simple class hierarchy
class TwoDShape{
private double length;
private double width;
TwoDShape(double length, double width) {
this.length = length;
this.width = width;
}
void show(){
System.out.println("length:" + length + " width:" + width);
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
}
class Triangle extends TwoDShape{
private String style;
Triangle(String style, double length, double width){
super(length, width);
this.style = style;
}
double area(){
return getLength() * getWidth();
}
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
void showStyle() {
System.out.println("style:" + style);
}
}
class Shapes {
public static void main(String[] args) {
Triangle triangle = new Triangle("Rectangle", 2, 1);
triangle.showStyle();
triangle.show();
}
}
输出结果如下:
style:Rectangle
length:2.0 width:1.0
子类构造方法需要调用父类的构造方法时,在子类的构造方法体里最前面的位置:super()
使用super访问超类成员
super访问超类成员的用法与this相似,只不过super引用的是子类的超类。
这种用法多用于被子类覆盖的同名成员和同名方法。
创建多级层次结构
如果存在三个类,分别为A,B,C,而它们的关系为A是B的超类,B是C的超类,这样就形成了一个简单的多级层次结构,此时,C将继承A和B所有的成员。
// A simple class hierarchy
class TwoDShape{
private double length;
private double width;
TwoDShape(double length, double width) {
this.length = length;
this.width = width;
}
void show(){
System.out.println("length:" + length + " width:" + width);
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
}
class Triangle extends TwoDShape{
private String style;
Triangle(String style, double length, double width){
super(length, width);
this.style = style;
}
double area(){
return getLength() * getWidth();
}
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
void showStyle() {
System.out.println("style:" + style);
}
}
class ColorTriangle extends Triangle{
private String color;
ColorTriangle(String color, String style, double length, double width) {
super(style, length, width);
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
void showColor() {
System.out.println("Color:" + color);
}
}
class Shapes {
public static void main(String[] args) {
ColorTriangle triangle = new ColorTriangle("Red", "Rectangle", 2, 1);
triangle.showColor();
triangle.showStyle();
triangle.show();
}
}
输出结果如下:
Color:Red
style:Rectangle
length:2.0 width:1.0
怎样调用构造函数
对于多级层级结构来说,就会存在有构造函数使用的先后顺序的问题。
因为super()必须是构造函数中的第一条语句,所以根据递归的思想,将会先调用第一个超类的构造函数,依次下来,最后构造最后一个子类构造函数。
超类引用与子类对象
Java是强类型语言,在类型方面十分严格,因此在超类对象与子类对象中如果出现需要转换时,该是如何操作的呢?
以上面的Shapes.java程序为例,ColorTriangle是Triangle类的子类。
ColorTriangle t1 = new ColorTriangle("Red", "Rectangle", 2, 1);
Triangle t2 = new Triangle("Rectangle", 2, 1);
t1 = t2; //ERROR
t2 = t1; //OK
对于这一小段代码可以简单的这样理解,子类的成员中包含了超类的所有成员,因此超类引用可以引用子类对象。
所以,在构造中也可以直接使用子类对象来进行super()构造。
Triangle(Triangle t1){
super(t1);
style = t1.style;
}
方法重写
在类层次结构中,当子类中的方法与超类中方法有相同的签名和返回类型,就成称子类中的方法重写(override)了超类的方法。此时在子类中调用被重写的方法时,将会使用子类定义的方法,而超类的方法被覆盖。
class Animal {
public void bark() {
System.out.println("动物叫!");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("汪!汪!汪!");
}
}
public class test{
public static void main(String args[]){
Animal a = new Animal();
Dog d = new Dog();
Animal b = new Dog();
a.bark();
d.bark();
b.bark();
}
}
输出结果如下:
动物叫!
汪!汪!汪!
汪!汪!汪!
方法重写也使得Java能够实现动态方法分配,而动态方法分配这个机制是Java实现运行时多态性的机制。
抽象类
在定义类时,前面加上abstract关键字修饰的类叫抽象类。 抽象类中有抽象方法,这种方法是不完整的,仅有声明而没有方法体。抽象方法声明语法如下:
abstract void f(); //f()方法是抽象方法
使用抽象类的主要情况:
- 在某些情况下,某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法。也就是说抽象类是约束子类必须要实现哪些方法,而并不关注方法如何去实现。
- 从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免了子类设计的随意性。
所以由上可知,抽象类是限制规定子类必须实现某些方法,但不关注实现细节。
抽象类的规则:
- 用 abstract 修饰符定义抽象类
- 用 abstract 修饰符定义抽象方法,只用声明,不需要实现
- 包含抽象方法的类就是抽象类
- 抽象类中可以包含普通的方法,也可以没有抽象方法
- 抽象类的对象不能直接创建,通常是定义引用变量指向子类对象。
abstract class TwoDShape{
private double length;
private double width;
TwoDShape(double length, double width) {
this.length = length;
this.width = width;
}
void show(){
System.out.println("length:" + length + " width:" + width);
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
abstract double area();
}
class Triangle extends TwoDShape{
private String style;
Triangle(String style, double length, double width){
super(length, width);
this.style = style;
}
double area(){
return getLength() * getWidth();
}
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
void showStyle() {
System.out.println("style:" + style);
}
}
class Shapes {
public static void main(String[] args) {
Triangle triangle = new Triangle("Rectangle", 2, 1);
triangle.showStyle();
triangle.show();
}
}
输出结果如下:
style:Rectangle
length:2.0 width:1.0
final关键字
final关键字可以修饰类、方法、属性和变量
- final 修饰类,则该类不允许被继承,为最终类
- final 修饰方法,则该方法不允许被覆盖(重写)
- final 修饰属性:则该类的属性不会进行隐式的初始化(类的初始化属性必须有值)或在构造方法中赋值(但只能选其一)
- final 修饰变量,则该变量的值只能赋一次值,即常量
Object类
Java中定义了一个特殊的类Object类,它是所有类的隐式超类,换而言之,其他的类都是Object类的子类。
方法 | 目的 |
---|---|
clone() | 创建并返回此对象的一个副本。 |
toString() | 返回该对象的字符串表示。 |
notify() | 唤醒在此对象监视器上等待的单个线程。 |
notifyAll() | 唤醒在此对象监视器上等待的所有线程。 |
registerNatives() | 私有方法 |
getClass() | 返回此 Object 的运行类。 |
hashCode() | 用于获取对象的哈希值。 |
equals(Object obj) | 用于确认两个对象是否“相同”。 |
wait(long timeout) | 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或 者超过指定的时间量前,导致当前线程等待。 |
wait(long timeout, int nanos) | 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。 |
wait() | 用于让当前线程失去操作权限,当前线程进入等待序列 |
finalize() | 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。 |