上一章,我们谈到了如何将现实中的概念映射为程序中的概念,我们谈了类以及类之间的组合,现实中的概念间还有一种非常重要的关系,就是分类。分类有个根,然后向下不断细化,形成一个层次分类体系。这种例子是非常多的:
1)在自然世界中,生物有动物和植物,动物有不同的科目,食肉动物、食草动物、杂食动物等,食肉动物有狼、狗、虎等,这些又分为不同的品种 。
2)打开电商网站,在显著位置一般都有分类列表,比如家用电器、服装,服装有女装、男装,男装有衬衫、牛仔裤等 …
计算机程序经常使用类之间的继承关系来表示对象之间的分类关系。在继承关系中,有父类和子类,比如动物类Animal和狗类Dog,Animal是父类,Dog是子类。父类也叫基类,子类也叫派生类,父类子类是相对的,一个类B可能是类A的子类,是类C的父类。
之所以叫继承,是因为子类继承了父类的属性和行为,父类有的属性和行为子类都有。但子类可以增加子类特有的属性和行为,某些父类有的行为,子类的实现方式可能与父类也不完全一样。
使用继承一方面可以复用代码,公共的属性和行为可以放到父类中,而子类只需要关注子类特有的就可以了;另一方面,不同子类的对象可以更为方便的被统一处理。
本问详细学习继承。我们先介绍继承的基本概念,然后详述继承的一些细节,理解了继承的用法之后,我们谈论继承的注意事项,解释为什么说继承是把双刃剑,以及如何正确地使用继承。
#一.基础概念
##1.根父类Object
在Java中,所有类都有一个父类,即使没有声明父类,也有一个隐含的父类,这个父类叫Object。Object没有定义属性,但定义了一些方法,如下图所示:
本节我们会介绍toString()方法,其他方法我们会在后续章节中逐步介绍。toString()方法的目的是返回一个对象的文本描述,这个方法可以直接被所有类使用。
比如说,对于我们之前介绍的Point类,可以这样使用toString方法:
Point p = new Point(2,3);
System.out.println(p.toString());
输出类似这样:
Point@76f9aa66
这是什么意思呢?@之前是类名,@之后的内容是什么呢?我们来看下toString的代码:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
getClass().getName()返回当前对象的类名,hashCode()返回一个对象的哈希值,哈希我们会在后续章节中介绍,这里可以理解为是一个整数,这个整数默认情况下,通常是对象的内存地址值,Integer.toHexString(hashCode())返回这个哈希值的shiliu进制表示。
为什么要这么写呢?写类名是可以理解的,表示对象的类型,而写哈希值则是不得已的,因为Object类并不知道具体对象的属性,不知道怎么用文本描述,但又需要区分不同对象,只能是写一个哈希值。
但子类是知道自己的属性的,子类可以重写父类的方法,以反映自己的不同实现。所谓重写,就是定义和父类一样的方法,并重新实现。
##2.方法重写
上一节,我们介绍了一些图形处理类,其中有Point类,这次我们重写了toString()方法。代码如下(Point.class):
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
@Override
public String toString() {
return "(" + x + "," + y + ")";
}
}
toString方法前面有一个 @Override,这表示toString这个方法是重写的父类的方法(当然,也可以是父类的父类),重写后的方法返回Point的x和y坐标的值。重写后,将调用子类的实现。比如,如下代码的输出就变成了:(2,3)
Point p = new Point(2,3);
System.out.println(p.toString());
##3.图形类继承体系
接下来,我们以一些图形处理中的例子来进一步解释,先来看幅图:
这都是一些基本的图形,图形有线、正方形、三角形、圆形等,图形有不同的颜色。接下来,我们定义以下类来说明关于继承的一些概念:
- 父类Shape,表示图形。
- 类Circle,表示圆。
- 类Line,表示直线。
- 类ArrowLine,表示带箭头的直线,
1.图形
所有图形(Shape)都有一个表示颜色的属性,有一个表示绘制的方法,代码如下(Shape.class):
public class Shape {
private static final String DEFAULT_COLOR = "black";
private String color;
public Shape() {
this(DEFAULT_COLOR);
}
public Shape(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void draw(){
System.out.println("draw shape");
}
}
以上代码基本没什么可解释的,实例变量color表示颜色,draw方法表示绘制,我们不会写实际的绘制代码,主要是演示继承关系。
2.圆
圆(Circle)继承自Shape,但包括了额外的属性,中心点和半径,以及额外的方法area,用于计算面积,另外,重写了draw方法,代码如下:
public class Circle extends Shape {
//中心点
private Point center;
//半径
private double r;
public Circle(Point center, double r) {
this.center = center;
this.r = r;
}
@Override
public void draw() {
System.out.println("draw circle at "
+ center.toString() + " with r " + r
+ ", using color : " + getColor());
}
public double area() {
return Math.PI * r * r;
}
}
说明:
- Java使用extends关键字标明继承关系,一个类最多只能有一个父类;
2)子类不能直接访问父类的私有属性和方法,比如,在Circle中,不能直接访问shape的私有实例变量color;
3)除了私有的外,子类继承了父类的其他属性和方法,比如,在Circle的draw方法中,可以直接调用getColor()方法。
我们来验证一下。代码如下(chapter_4Activity.class):
Point center = new Point(2,3);
//创建圆,赋值给circle
Circle circle = new Circle(center,2);
//调用draw方法,会执行Circle的draw方法
circle.draw();
//输出圆面积
System.out.println(circle.area());
程序的输出为:
draw circle at (2,3) with r 2.0, using color : black
12.566370614359172
这里比较奇怪的是,co