Java面向对象——抽象方法和抽象类
1.引出抽象方法设计
我们知道,图形都有对应算法来求出面积,如下方代码所示,我们创建Graph.java代表图形类,图形类中提供计算面积的getArea()方法,因为每种具体图形的面积计算公式都不一样,所以我们让Graph类中的getArea()方法只提供一个返回值,由子类进行覆盖。创建圆形类、矩形类代表不同的图形,它们都继承自图形类,并各自覆盖getArea()方法。
/**
* 图形类
*/
public class Graph {
public Double getArea(){
return 0.0;
}
}
/**
* 圆类 继承自 图形类Graph
*/
public class Circle extends Graph{
private Double r;
/**
* 提供有参构造
* @param r 圆的半径
*/
public Circle(Double r){
this.r = r;
}
/**
* 计算圆形面积
* @return
*/
@Override
public Double getArea() {
return 3.14 * r * r;
}
}
/**
* 矩形 继承自 图形类Graph
*/
public class Rectangle extends Graph{
/**
* 长方形的长
*/
private Double length;
/**
* 长方形的宽
*/
private Double width;
public Rectangle(Double length, Double width){
this.length = length;
this.width = width;
}
/**
* 计算矩形面积
* @return
*/
@Override
public Double getArea() {
return length * width;
}
}
我们分析以上代码,会发现存在两个问题:
-
1.每一种图形都有面积,所以在Graph类中定义求面积的方法getArea()没问题。但是,不同的具体图形求面积的算法是不一样的,也就是说,每一种图形的子类都必须去覆盖getArea()方法,如果不覆盖,应该在语法上给出提示(报错) 。比如说我们新创建一个三角形类也继承自Graph类,没有覆盖getArea()方法,此时求三角形面积会调用父类的getArea()方法,返回值为0.0,肯定是不对的,因为三角形是有具体的不同于其他图形的求面积的公式。
-
2.在图形类中定义了getArea方法,该方法不应该存在方法体,因为不同图形子类求面积算法不一样,父类真不知道该怎么写,所以应该提供无方法体。
解决上述问题,我们可以将Graph类中的getArea()方法定义为抽象方法.
2.抽象方法
我们先看一下抽象方法的概念和格式:
抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,也就是没有方法体。
具体格式如下:
访问权限 abstract 返回值类型 方法名(形参列表);
public abstract void getArea();
按照上述格式,我们将Graph类进行相应的改造:
/**
* 图形类
*/
public abstract class Graph {
/**
* 抽象方法
* @return
*/
public abstract Double getArea();
}
可以看到,Graph类也被abstract修饰,是因为Java中有明确要求,一旦类中包含了abstract方法,那类该类必须声明为abstract类。 如果Graph类不用abstract修饰,IDEA或者java文件编译时会有相应报错提示,阻止程序运行:
Class ‘Graph’ must either be declared abstract or implement abstract method ‘getArea()’ in ‘Graph’
因为之前的Circle类和Rectangle类都已经覆盖了getArea()方法,我们不需要再有相应改动.新创建一个三角形类Triangle.java,让其继承自Graph类,如果Triangle类不覆盖getArea()方法,IDEA中或者java文件编译时会有相应报错提示,阻止程序运行:
Class ‘Triangle’ must either be declared abstract or implement abstract method ‘getArea()’ in ‘Graph’
必须要强制覆盖父类的抽象方法
public class Triangle extends Graph{
private Integer a;
private Integer b;
private Integer c;
Triangle(Integer a, Integer b, Integer c)
{
this.a = a ;
this.b = b;
this.c = c;
}
/**
* 根据海伦公式求三角形面积
* @return
*/
@Override
public Double getArea()
{
Double p = (a + b + c)/2.0;
return Math.sqrt(p *(p - a ) * (p - b ) * (p - c ));
}
}
我们在之前提到的两个问题都得到了解决. 我们进行下总结:
使用 abstract 修饰且没有方法体的方法,称为抽象方法.
特点:
- 1 使用抽象abstract修饰,方法没有方法体,留给子类去实现/覆盖.
- 2 抽象方法的修饰符不能是private和final以及static,因为被这些修饰符修饰,子类无法对抽象方法进行覆盖.
- 3 抽象方法必须定义在抽象类或接口中 (接口中定义的方法都是公共的抽象方法,默认使用public abstract来修饰方法,但一般的,我们在接口中定义方法,不喜欢使用修饰符).
3.抽象类
被abstract关键字修饰后,Graph类变为抽象类,所以说:
抽象类主要指不能具体实例化的类并且使用abstract关键字修饰。
通过上述例子我们知道父类Graph类只是定义了getArea()方法,具体的功能还得子类来实现,抽象类必须有子类才有意义,才能完成自己未尽的功能。
需要注意的是,抽象类不能创建实例,即使创建出抽象类对象,调用抽象方法,根本没有方法体,没有任何意义!
对于抽象类来讲,普通类有的成员(方法、字段、构造方法),抽象类都有。 抽象类的构造方法不能全都定义为私有的(private修饰),否则不能有子类(创建子类对象前先调用父类构造方法)。
/**
* 图形类
*/
public abstract class Graph {
/**
* 抽象类的成员变量
*/
public String name;
/**
* 抽象方法
* @return
*/
public abstract Double getArea();
/**
* 抽象类的普通方法(非抽象方法)
*/
public void print(){
System.out.println("抽象类也可以具备普通方法-----");
}
/**
* 抽象类的构造方法
*/
public Graph() {
}
}
总结下抽象类的特点:
-
1.不能创建实例即不能new一个抽象类对象,即使创建出抽象类对象,调用抽象方法,根本没有方法体。
-
2.可以不包含抽象方法,若一旦包含,该类必须作为抽象类,抽象类可以包含普通方法(留给子类调用的,抽象类是有构造器的子类构造器必须先调用父类构造器。
-
3.若子类没有实现/覆盖父类所有的抽象方法,那么子类也得作为抽象类(抽象派生类)。
-
4.构造方法不能都定义成私有的,否则不能有子类(创建子类对象前先调用父类构造方法)。
-
5.抽象类不能使用final修饰,因为必须有子类,抽象方法才能得以实现。
-
6.是不完整的类,需作为父类(必须要有子类),功能才能得以实现。
抽象类中可以不存在抽象方法如此这样没有太大的意义,但是可以防止外界创建对象,所以我们会发现有些工具类没有抽象方法但是也使用abstract来修饰。
抽象类和普通类对比:
- 普通类有的成员(方法字段,构造器),抽象类都有。
- 抽象类不能创建对象,抽象类中可以包含抽象方法。
4.抽象类的实际意义
• 抽象类的实际意义不在于创建对象而在于被继承。
• 当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式。