写在前面:
转载自:知乎 飞爱学习 作者:一个敲代码的汽车人
原文链接: https://zhuanlan.zhihu.com/p/139033198
这篇主要讲的是继承extends和覆盖override。
不管是继承、覆盖还是多态,甚至类、抽象类、接口,都是在体现一种工程化实现的设计思想。
如果能从做工程、多方协作的设计师的视角去看待这些概念,你会更容易理解为什么要创造出来这些概念,为什么用这些概念或方法就能达到工程代码的易扩展、易维护的目的。
1 继承(Inheritance)
1.1 案例
咱们稍微加一点难度。
现在要求设计一个面积计算器,计算的对象不仅仅是矩形,还包括平行四边形。输入仍然为高和宽(假定都是规范的数值输入),输出为面积。
1.2 代码
为了简化代码,前面已经实现过的内容(比如封装)就不再展开写了。
class Rectangle{ //定义矩形类
public double height;
public double width;
public Rectangle(double height,double width) {
this.height=height;
this.width=width;
}
public void calcuArea() {
System.out.println("矩形的面积为:"+height*width);
}
}
class Parallelogram{ //定义平行四边形类
public double height;
public double width;
public Parallelogram(double height,double width) {
this.height=height;
this.width=width;
}
public void calcuArea() {
System.out.println("平行四边形的面积为:"+height*width);
}
}
public class Test {
public static void main(String[] args) {
Rectangle rec=new Rectangle(1, 2);
rec.calcuArea();
Parallelogram par=new Parallelogram(1, 3);
par.calcuArea();
}
}
1.3 代码分析
思考:以上代码写起来挺简单,但是有个问题,就是代码重复率太高!这才两个类,要是有成百上千个这种类,不仅写得累,后期维护也累。
有没有什么偷懒的办法呢?当然有啦,就是后面要介绍的继承。
1.4 改进版代码
class Quadrangle{ //定义父类--四边形类
public double height;
public double width;
public String name;
public Quadrangle(double height,double width,String name) {
this.height=height;
this.width=width;
this.name=name;
}
public void calcuArea() {
System.out.println(name+"面积为:"+height*width);
}
}
class Rectangle extends Quadrangle{ //定义子类--矩形类
public Rectangle(double height,double width,String name) {
super(height,width,name); //super指调用父类的构造方法
}
}
class Parallelogram extends Quadrangle{ //定义子类--平行四边形类
public Parallelogram(double height,double width,String name) {
super(height,width,name);
}
}
public class Test {
public static void main(String[] args) {
Rectangle rec=new Rectangle(1, 2,"矩形");
rec.calcuArea();
Parallelogram par=new Parallelogram(1, 3,"平行四边形");
par.calcuArea();
}
}
1.5 改进版代码分析
诶?咋一看,好像并没有省事哦?
那是因为我们这里只有两个子类,如果换成实现成百上千个这种子类,差距就会拉开了。咱们来分析一下这是如何实现的。
以上代码包含以下概念:
继承(Inheritance):即基于已有的类(称之为父类或超类)来创建新的类(称之为子类),顾名思义,子类将继承父类所有的成员变量及方法。继承一般应用于类与类较相似的情况下,比如本案例中,矩形类与平行四边形类的成员变量和方法高度相似,可以提取两者的共同代码,构造一个四边形类作为父类,从而避免了重复代码,也方便了后期功能的扩展及维护。
注意:
- JAVA中类只能单继承(即只能继承一个父类),但不仅限于一个层次,Object是所有类的终极父类,是万物之源。
- 子类继承父类时需要用到关键字“extends”。
- 子类能继承父类所有的成员变量和方法(除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
- 若父类定义了构造函数,子类需要用super调用父类的构造方法,且必须位于子类构造方法中的第一行。
- 子类继承了父类,不代表只能用父类的东西,还可以定义自己的成员变量及方法,甚至于可以改写父类的方法(即后文讲到的覆盖)。
2 覆盖(Override)和多态(Polymorphism)
2.1 案例
咱们稍微再加一点难度。
现在要求设计一个面积计算器,计算的对象不仅仅是矩形和平行四边形,还包括梯形。矩形和平行四边形的输入为高和宽,梯形的输入为高、上底长和下底长(假定都是规范的数值输入),输出都为面积。
规定:矩形和平行四边形的面积计算公式为宽x高;梯形的面积计算公式为(上底+下底)x 高/2
2.2 代码
思考:我们仍然可以采用继承来实现,但是梯形的面积计算方法与矩形和平行四边形不同,如何以最简洁的方法实现代码?
具体代码如下:
class Quadrangle{ //定义父类--四边形类
public double height;
public double width;
public String name;
public Quadrangle(double height,double width,String name) {
this.height=height;
this.width=width;
this.name=name;
}
public void calcuArea() {
System.out.println(name+"面积为:"+height*width);
}
}
class Rectangle extends Quadrangle{ //定义子类--矩形类
public Rectangle(double height,double width,String name) {
super(height,width,name);
}
}
class Parallelogram extends Quadrangle{ //定义子类--平行四边形类
public Parallelogram(double height,double width,String name) {
super(height,width,name);
}
}
class Trapezoid extends Quadrangle{ //定义子类--梯形类
public double width_up; //自定义成员变量--上底宽
public Trapezoid(double height,double width_up,double width_down,String name) {
super(height,width_down, name);
this.width_up=width_up;
}
@Override
public void calcuArea() { //覆盖父类的面积计算方法
System.out.println(name+"面积为:"+height*(width_up+width)/2);
}
}
public class Test {
public static void main(String[] args) {
Rectangle rec=new Rectangle(1, 2,"矩形");
rec.calcuArea();
Parallelogram par=new Parallelogram(1, 3,"平行四边形");
par.calcuArea();
Trapezoid tra=new Trapezoid(1, 1, 2,"梯形");
tra.calcuArea();
}
}
2.3 代码分析
以上代码用到了以下概念:
覆盖(Override):指在继承中,父类的有些方法在子类中不适用,子类重新定义的手段。在本案例中,梯形类对calcuArea方法实现了覆盖。
注意:
- 若子类中被“覆盖”方法的参数类型不同,返回类型不一致,这不是覆盖,而是重载。覆盖要求参数类型必须一样,且返回类型必须兼容。总之,子类对象得保证能够执行父类的一切。
- 不能降低覆盖方法的存取权限,如public变成private。
- 若不希望父类的某个方法被子类覆盖,可以用final修饰该方法。甚至可以扩展到将类用final修饰,则其中所有的方法均不可覆盖,但不影响成员变量的赋值。
思考:Test类中的语句块有点啰嗦,同样是初始化加调用面积计算方法,三个对象实现了三次,那如果有成百上千个类岂不是要累死,这能否优化呢?
2.4 改进版代码
对Test类进行优化可以得到如下代码:
public class Test {
public static void main(String[] args) {
//创建对象
ArrayList<Quadrangle> quadrangles = new ArrayList<Quadrangle>();
quadrangles.add(new Rectangle(1, 2,"矩形"));
quadrangles.add(new Parallelogram(1, 3,"平行四边形"));
quadrangles.add(new Trapezoid(1, 1, 2,"梯形"));
//循环执行各个对象的面积计算方法
for (Quadrangle qua : quadrangles) {
qua.calcuArea();
}
}
}
2.5 改进版代码分析
以上代码用到了以下概念:
多态(Polymorphism):指一个对象变量(如代码中的qua)可以指示多种实际类型的现象。由于矩形类、平行四边形类和梯形类都是继承于四边形父类,所以其方法名一致,可以通过一个父类的对象变量来实现子类的自动匹配,从而简化了代码。
多态的优缺点
- 优点:可以提高可维护性(多态前提所保证的),提高代码的可扩展性
- 缺点:无法直接访问子类特有的成员。
思考:这个缺点怎么解决呢?比如在上述代码中,无法通过qua.width_up获取只有梯形的才有的成员变量。
解决方法:可以通过instanceof判断对象变量的实际类型以及对象类型转换实现相应的操作,代码如下:
public class Test {
public static void main(String[] args) {
//创建对象
ArrayList<Quadrangle> quadrangles = new ArrayList<Quadrangle>();
quadrangles.add(new Rectangle(1, 2,"矩形"));
quadrangles.add(new Parallelogram(1, 3,"平行四边形"));
quadrangles.add(new Trapezoid(1, 1, 2,"梯形"));
//循环执行各个对象的面积计算方法
for (Quadrangle qua : quadrangles) {
if (qua instanceof Trapezoid) { //判断对象类型
Trapezoid tra=(Trapezoid)qua; //对象类型转换
//输出梯形类的上底宽
System.out.println("梯形的上底宽为:"+tra.width_up);
}
qua.calcuArea();
}
}
}
3 参考文献
[1]《Head First Java(第二版·中文版)》
[2]《Java核心技术·卷 I(原书第11版)》
[3] 菜鸟教程:https://www.runoob.com/java/j...
[4] 速学堂:https://www.sxt.cn/Java_jQuer...