初学JAVA时,总会对一些概念只知其一;不知其二,相互混淆,不明其设计的用意,如类、对象、重载、封装、继承、多态、覆盖、抽象类、接口概念。为便于理解和巩固,本文将基于一个案例及其变形,展示各个概念的定义、设计用意、使用规范和注意事项。html
长文警告,建议先收藏后阅读!java
要求设计一个矩形的面积计算器,输入为矩形的高(height)和宽(width),输出为矩形的面积(area)。安全
对JAVA的语法有最基本的了解后能够写出以下代码:ide
class Rectangle{ //建立矩形类 public double height; //定义类的成员变量 public double width; public Rectangle() { //定义无参构造器--可省略 } public void calcuArea() { //定义类的方法--面积计算 System.out.println("面积为:"+height*width); } } public class Test{ public static void main(String[] args) { Rectangle rec=new Rectangle(); //建立矩形对象--调用构造器 rec.height=1; //高度赋值 rec.width=2; //宽度赋值 rec.calcuArea(); //调用面积计算方法 } }
以上代码包含如下概念:
一、类(Class):类是构造对象的模板或蓝图,其由成员变量和方法构成,前者记录数据,后者记录数据的操做过程。
二、对象(Object):对象是类的实例化,一个类能够有多个对象。
三、构造器(Constructor):构造器是一种特殊的方法,用于对象实例化时的初始化操做。模块化
注意:函数
思考:以上代码虽然实现了基本功能,但其功能不够完善,好比当用户输入的高度和宽度为字符串时,代码就会报错,那怎么才能在不给用户输入添加麻烦的状况下实现功能呢?优化
要求设计一个矩形的面积计算器,输入为矩形的高和宽(数字或者字符串输入,假定用户输入的字符串都是数值型字符串),输出为矩形的面积。this
class Rectangle{ public double height; public double width; //定义无参构造器--可省略 public Rectangle() { } //定义有参构造器1 public Rectangle(double height,double width) { this.height=height; //this指代当前对象,用于区分方法中的形参 this.weight=width; } //定义有参构造器2 public Rectangle(String height,String width) { this.height=Double.valueOf(height); this.weight=Double.valueOf(width); } public void calcuArea() { System.out.println("面积为:"+height*width); } } public class Test{ public static void main(String[] args) { Rectangle rec=new Rectangle(1,2); //建立矩形对象--调用构造器1 rec.calcuArea(); //调用面积计算方法 Rectangle rec1=new Rectangle("1","2"); //建立矩形对象--调用构造器2 rec1.calcuArea(); //调用面积计算方法 } }
以上代码包含如下概念:
重载(Overload):指类中多个方法具备相同的名字,但参数类型或返回类型不一样的现象。重载的设定是为了方便用户操做,以相同的方法名实现特定的功能,同时匹配不一样的参数类型以知足功能的扩展性和需求的多样性。在上面的代码中咱们实现了构造器方法的重载,完美解决了用户输入多样性的问题,但重载并不局限于构造器方法,它能够适用于类中的任何方法。spa
注意:设计
思考:以上代码虽然解决了以前提出的问题,可是仍存在一个巨大的安全隐患,即用户能够直接经过“rec.height”和“rec.width”对矩形的高和宽赋值,这会致使两个咱们不肯意看到的情景。一是,当用户输入很是规数值(好比-1)时,计算的结果是没有意义的;二是,后期更改程序时,难以调试,好比咱们后期要将height的变量类型改成String类型,那么就必须更改每一处height赋值的地方(如代码1.2)。那么,如何改进呢?
要求设计一个矩形的面积计算器,输入为矩形的高和宽(数值输入),且数值都应大于零,输出为矩形的面积。同时要避免2.3中所述的问题。
class Rectangle{ private double height; //私有化实例字段 private double width; public void setHeight(double height) { //定义更改器方法 if (height<0) { this.height =0; } else { this.height = height; } } public void setWidth(double width) { if (width<0) { this.width =0; } else { this.width = width; } } public double getHeight() { //定义访问器方法 return height; } public double getWidth() { return width; } public void calcuArea() { System.out.println("面积为:"+height*width); } } public class Test{ public static void main(String[] args) { Rectangle rec=new Rectangle(); rec.setHeight(-1); //设定高 rec.setWidth(2); //设定宽 rec.calcuArea(); //读取高和宽 System.out.println("高为:"+rec.getHeight()+" 宽为:"+rec.getWidth()); } }
以上代码包含如下概念:
封装(Encapsulation):经过私有化类的成员变量,并建立相应的公有化的更改器(即设定成员变量的独立方法,如setHeight)和访问器(即读取成员变量的独立方法,如getHeight)实现对成员变量的封装。
设计用意:
我们稍微加一点难度。
如今要求设计一个面积计算器,计算的对象不只仅是矩形,还包括平行四边形。输入仍然为高和宽(假定都是规范的数值输入),输出为面积。
为了简化代码,前面已经实现过的内容(好比封装)就再也不展开写了。
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(); } }
思考:以上代码写起来挺简单,可是有个问题,就是代码重复率过高!这才两个类,要是有成百上千个这种类,不只写的累死,后期维护也得累死。
有没有什么偷懒的办法呢?固然有啦,就是后面要介绍的继承。
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(); } }
诶?咋一看,好像并无省事哦?
那是由于咱们这里只有两个子类,若是换成实现成百上千个这种子类,差距就会拉开了。我们来分析一下这是如何实现的。
以上代码包含如下概念:
继承(Inheritance):即基于已有的类(称之为父类或超类)来建立新的类(称之为子类),顾名思义,子类将继承父类全部的成员变量及方法。继承通常应用于类与类较类似的状况下,好比本案例中,矩形类与平行四边形类的成员变量和方法高度类似,能够提取二者的共同代码,构造一个四边形类做为父类,从而避免了重复代码,也方便了后期功能的扩展及维护。
注意:
我们稍微再加一点难度。
如今要求设计一个面积计算器,计算的对象不只仅是矩形和平行四边形,还包括梯形。矩形和平行四边形的输入为高和宽,梯形的输入为高、上底长和下底长(假定都是规范的数值输入),输出都为面积。
规定:矩形和平行四边形的面积计算公式为宽x高;梯形的面积计算公式为(上底+下底)x 高/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(); } }
以上代码用到了如下概念:
覆盖(Override):指在继承中,父类的有些方法在子类中不适用,子类从新定义的手段。在本案例中,梯形类对calcuArea方法实现了覆盖。
注意:
思考:Test类中的语句块有点啰嗦,一样是初始化加调用面积计算方法,三个对象实现了三次,那若是有成百上千个类岂不是要累死,这可否优化呢?
对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(); } } }
以上代码用到了如下概念:
多态(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(); } } }
我们再加一点难度。
如今要求设计一个面积计算器,计算的对象包括平行四边形、梯形和圆。平行四边形的输入为高和宽,梯形的输入为高、上底长和下底长,圆的输入为直径(假定都是规范的数值输入),全部的输出均为面积。
规定:平行四边形的面积计算公式为宽x高;梯形的面积计算公式为(上底+下底)x 高/2;圆的面积计算公式为圆周率x半径的平方。
那么,基于继承,怎样设计最好?
思考:将可继承的方法体(即有具体内容的方法)放在父类中以免子类中重复代码的出现是继承的一大优点,但其并不是是万能的。好比在这个案例中,三个面积计算公式都不同,很难抽取出共同的方法体,但咱们又但愿子方法中都有面积计算方法且尽量避免重复代码的出现,怎么办呢?
聪明如你,确定想到了能够用刚才学到的多态知识实现,代码以下:
class Geometry { //定义几何图形类 public double height; public String name; public final double PI=3.1415; public Geometry(double height,String name) { this.height=height; this.name=name; } public void calcuArea(Geometry geo) { //定义面积计算方法,用到多态 if (geo instanceof Parallelogram) { Parallelogram par=(Parallelogram)geo; System.out.println(par.name+"面积为:"+par.height*par.width); } else if (geo instanceof Trapezoid) { Trapezoid tra=(Trapezoid)geo; System.out.println(tra.name+"面积为:"+height*(tra.width_up+tra.width_down)/2); } else if (geo instanceof Cycle) { Cycle cyc=(Cycle)geo; System.out.println(name+"面积为:"+PI*Math.pow(cyc.height/2,2)); } } } class Parallelogram extends Geometry{ //定义平行四边形类 public double width; public Parallelogram(double height,double width,String name) { super(height,name); this.width=width; } } class Trapezoid extends Geometry{ //定义梯形类 public double width_up; public double width_down; public Trapezoid(double height,double width_up,double width_down,String name) { super(height, name); this.width_up=width_up; this.width_down=width_down; } } class Cycle extends Geometry{ //定义圆形类 public Cycle(double diameter,String name) { super(diameter,name); } } public class Test { public static void main(String[] args) { //建立对象 ArrayList<Geometry> geometries = new ArrayList<Geometry>(); geometries.add(new Parallelogram(1, 3,"平行四边形")); geometries.add(new Trapezoid(1, 1, 2,"梯形")); geometries.add(new Cycle(2,"圆形")); //循环执行各个对象的面积计算方法 for (Geometry geo : geometries) { geo.calcuArea(geo); } } }
思考:以上代码确实实现了咱们的需求,完成了继承,避免了重复代码的出现,可是总感受哪里不对劲。仔细观察能够发现,每增长一个新的子类,咱们就必须得在父类方法中作相应的修改,才能使新增子类也具有面积计算方法。做为一个堂堂正正的父类怎么能跟着子类的需求而变化呢?那这个父类岂不是很没“面子”?
因此,在后期功能拓展时,如何才能避免对上层结构的改动呢?
思考:既然在父类中难以提取通用的方法体,那咱们可不能够只声明方法,而不具体实现呢?固然能够呀,咱们能够用到前文提到的“覆盖”,实现子类方法的定义(如代码5.2),这样就避免了对父类的修改。
可是,这个实例化后的父类(好比Geometry类)是什么呢?有意义吗?没有意义的话怎么才能避免其被实例化呢?
解决方法见代码:
abstract class Geometry { //定义抽象类 public double height; public Geometry(double height) { this.height=height; } abstract public void calcuArea(); //定义抽象方法 } class Parallelogram extends Geometry{ public double width; public Parallelogram(double height,double width) { super(height); this.width=width; } @Override public void calcuArea() { System.out.println("平行四边形面积为:"+height*width); } } class Trapezoid extends Geometry{ public double width_up; public double width_down; public Trapezoid(double height,double width_up,double width_down) { super(height); this.width_up=width_up; this.width_down=width_down; } @Override public void calcuArea() { System.out.println("梯形面积为:"+height*(width_up+width_down)/2); } } class Cycle extends Geometry{ public final double PI=3.1415; public Cycle(double diameter) { super(diameter); } @Override public void calcuArea() { System.out.println("圆形面积为:"+PI*(height/2)*(height/2)); } } public class Test { public static void main(String[] args) { ArrayList<Geometry> geometry = new ArrayList<Geometry>(); geometry.add(new Parallelogram(1, 2)); geometry.add(new Trapezoid(1, 1, 2)); geometry.add(new Cycle(2)); for (Geometry geo : geometry) { geo.calcuArea(); } } }
任何封闭的几何图形都应该具备面积计算方法,但方法不一,难以提取出相同的实现代码,因此将其抽象。
以上代码用到了如下概念:
抽象方法(Abstrac Method):指使用abstract修饰的方法,没有方法体,只有声明。定义的是一种“规范”,就是告诉子类必需要给抽象方法提供具体的实现。
抽象类(Abstrac Class):指包含抽象方法的类。经过abstract方法定义规范,而后要求子类必须定义具体实现。抽象类每每用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不一样,可是本质上相同的具体概念的抽象。经过抽象类,咱们就能够作到严格限制子类的设计,使子类之间更加通用。同时,经过在抽象类中定义封装的更改器和访问器,减小了子类的代码重复。
抽象的意义:
到这里,你有可能会有个疑问,既然子类都得经过覆盖实现本身的面积计算方法,为何咱们执意要用继承呢?
由于,咱们须要用继承来提供一个规范,规范咱们的成员变量和方法(即便没有具体的实现,也要有一致的方法名),没有规范,多态就无从谈起。若是说继承的基本用意是实现代码的复用性,那抽象就是继承的升华,它追求更高的精神境界,即契约(或规范)。抽象要求全部使用它的“用户”(即子类)都必须签定一份合约,这份合约规定子类必须实现抽象类所规定的全部方法。抽象方法意义在于就算没法实现出方法的内容,但还能够定义出一组子型共同的协议。
注意:
我们再加一点难度。
在案例6.1的基础上增长功能,要求轴对称图形输出其绕垂直中心轴旋转获得的几何体的体积,体积计算方法定义为calcuVolume。
那么,考虑到代码的复用性和功能的扩展性,怎样设计最优呢?
思考:平行四边形不是轴对称图形,没法获得旋转的几何体;梯形旋转后能够获得圆台;圆形旋转后能够获得球。分析三个图形的特色能够获得如图所示的结构:
为此,能够想到以下两种方法:
方法一:将calcuVolume方法直接定义在具备轴对称性的子类中。
方法二:将calcuVolume方法定义在Geometry抽象类中。
那么,如何才能优雅地解决这个问题呢?
详见以下代码:
abstract class Geometry { //定义抽象类 public double height; public Geometry(double height) { this.height=height; } public abstract void calcuArea(); //定义抽象方法 } interface AxialSymmetry{ public static final double PI=3.1415; public abstract void calcuVolume(); } class Parallelogram extends Geometry{ public double width; public Parallelogram(double height,double width) { super(height); this.width=width; } @Override public void calcuArea() { System.out.println("平行四边形的面积为:"+height*width); } } class Trapezoid extends Geometry implements AxialSymmetry{ public double width_up; public double width_down; public Trapezoid(double height,double width_up,double width_down) { super(height); this.width_up=width_up; this.width_down=width_down; } @Override public void calcuArea() { System.out.println("梯形的面积为:"+height*(width_up+width_down)/2); } @Override public void calcuVolume() { System.out.println("圆台的体积为:"+PI*height*(Math.pow(width_up/2,2)+Math.pow(width_down/2,2)+width_up*width_down/4)/3); } } class Cycle extends Geometry implements AxialSymmetry{ final double PI=3.1415; public Cycle(double diameter) { super(diameter); } @Override public void calcuArea() { System.out.println("圆形的面积为:"+PI*(height/2)*(height/2)); } @Override public void calcuVolume() { System.out.println("球的体积为:"+PI*Math.pow(height/2,3)*4/3); } } public class Test { public static void main(String[] args) { //实现类的多态 ArrayList<Geometry> geometry = new ArrayList<Geometry>(); geometry.add(new Parallelogram(1, 2)); geometry.add(new Trapezoid(1, 1, 2)); geometry.add(new Cycle(2)); for (Geometry geo : geometry) { geo.calcuArea(); } //实现接口的多态 ArrayList<AxialSymmetry> axialSymmetry = new ArrayList<AxialSymmetry>(); axialSymmetry.add(new Trapezoid(1, 1, 2)); axialSymmetry.add(new Cycle(2)); for (AxialSymmetry axi : axialSymmetry) { axi.calcuVolume(); } } }
以上代码用到了如下概念:
接口(Interface):接口不是类,而是对但愿符合这个接口的类的一组需求。能够说接口是比抽象更抽象的概念。抽象类还提供某些具体实现,而接口不提供任何实现,接口中全部方法都是抽象方法。接口是彻底面向规范的,规定了一批类具备的公共方法规范。接口的意义在于全面地、专业地实现了规范和具体实现的分离,便于实现模块化设计。
类与接口的关系:
类与接口的区别:
抽象类与接口的区别:
a.成员变量
b.方法
c.构造方法
注意:
好了,概念讲完了。我们来作个练习,把以上全部概念都用起来!
要求:尽可能使用本所介绍的知识,设计一个几何图形计算器,能计算图形的面积以及轴对称图形旋转后的体积。
结合本文所学的知识,编写以下代码:
//定义几何图形的接口 interface Geometry { public static final double PI=3.1415; public abstract void calcuArea(); } //定义轴对称图形的接口 interface AxialSymmetry{ public abstract void calcuVolume(); } //定义四边形抽象类 abstract class Quadrangle implements Geometry{ private double height; private double width; public void setHeight(double height) { this.height = height; } public void setWidth(double width) { this.width = width; } public double getHeight() { return height; } public double getWidth() { return width; } public Quadrangle(double height, double width) { setHeight(height); setWidth(width); } } //定义三边形抽象类 abstract class Triangle implements Geometry{ private double height; private double width; private String name; public void setHeight(double height) { this.height = height; } public void setWidth(double width) { this.width = width; } public void setName(String name) { this.name = name; } public double getHeight() { return height; } public double getWidth() { return width; } public String getName() { return name; } public Triangle(double height, double width) { setHeight(height); setWidth(width); } @Override public void calcuArea() { System.out.println(getName()+"的面积为:"+getHeight()*getWidth()/2); } } //定义矩形类 class Rectangle extends Quadrangle implements AxialSymmetry{ public Rectangle(double height, double width) { super(height, width); } @Override public void calcuArea() { System.out.println("矩形的面积为:"+getHeight()*getWidth()); } @Override public void calcuVolume() { System.out.println("矩形旋转获得圆柱体体积为:"+getHeight()*Math.pow(getWidth()/2, 2)*PI); } } // 定义平行四边形类 class Parallelogram extends Quadrangle{ public Parallelogram(double height, double width) { super(height, width); } @Override public void calcuArea() { System.out.println("平行四边形的面积为:"+getHeight()*getWidth()); } } //定义梯形类 class Trapezoid extends Quadrangle implements AxialSymmetry{ private double width_up; public void setWidth_up(double width_up) { this.width_up = width_up; } public double getWidth_up() { return width_up; } public Trapezoid(double height, double width_up, double width) { super(height,width); setWidth_up(width_up); } @Override public void calcuArea() { System.out.println("梯形的面积为:"+getHeight()*(getWidth_up()+getWidth())/2); } @Override public void calcuVolume() { System.out.println("梯形旋转获得的圆台体积为:"+PI*getHeight()*(Math.pow(getWidth_up()/2,2)+Math.pow(getWidth()/2,2)+getWidth_up()*getWidth()/4)/3); } } //定义等腰三角形类 class IsoscelesTriangle extends Triangle implements AxialSymmetry{ public IsoscelesTriangle(double height, double width) { super(height, width); setName("等腰三角形"); } @Override public void calcuVolume() { System.out.println("等腰三角形旋转获得的圆锥体体积为:"+getHeight()*(PI*Math.pow(getWidth()/2, 2))/3); } } //定义非等腰三角形类 class NotIsoscelesTriangle extends Triangle{ public NotIsoscelesTriangle(double height, double width) { super(height, width); setName("非等腰三角形"); } } public class Test { public static void main(String[] args) { // 实例化 ArrayList<Object> objects=new ArrayList<Object>(); objects.add(new Rectangle(1, 2)); objects.add(new Parallelogram(1, 2)); objects.add(new Trapezoid(1, 1, 2)); objects.add(new IsoscelesTriangle(1, 2)); objects.add(new NotIsoscelesTriangle(1, 2)); // 多态 for (Object obj : objects) { if (obj instanceof Geometry) { Geometry geo=(Geometry)obj; geo.calcuArea(); } if (obj instanceof AxialSymmetry) { AxialSymmetry axi=(AxialSymmetry) obj; axi.calcuVolume(); } } } }
注意!以上代码必定不是最优方案,只是为了练习本文所学知识,所以,仅作参考。关于程序的模式设计问题之后再聊。
层次结构以下:
【1】《Head First Java(第二版·中文版)》
【2】《Java核心技术·卷 I(原书第11版)》
【3】菜鸟教程:https://www.runoob.com/java/j...
【4】速学堂:https://www.sxt.cn/Java_jQuer...