在 Java 中,被 abstract 关键字修饰的类叫抽象类。
抽象类的定义格式如下:
abstract class 抽象类名称{
属性;
访问权限 返回值类型 方法名称(参数){
[return 返回值]
}
//在抽象方法中是没有方法体的
访问权限 abstract 返回值类型 方法名称(参数);
}
//源码
//Circles,triangles and squares are types of shape
public abstract class Shape {
private String name;
public abstract void draw();
public abstract void erase();
public abstract void calculateArea();
public abstract void calculatePerimeter();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//源码
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("Circle draw");
}
@Override
public void erase() {
System.out.println("Circle erase");
}
@Override
public void calculateArea() {
System.out.println("Circle calculateArea");
}
@Override
public void calculatePerimeter() {
System.out.println("Circle calculatePerimeter");
}
}
public class Triangle extends Shape {
@Override
public void draw() {
System.out.println("Triangle draw");
}
@Override
public void erase() {
System.out.println("Triangle erase");
}
@Override
public void calculateArea() {
System.out.println("Triangle calculateArea");
}
@Override
public void calculatePerimeter() {
System.out.println("Triangle calculatePerimeter");
}
}
public class Square extends Shape {
@Override
public void draw() {
System.out.println("Square draw");
}
@Override
public void erase() {
System.out.println("Square erase");
}
@Override
public void calculateArea() {
System.out.println("Square calculateArea");
}
@Override
public void calculatePerimeter() {
System.out.println("Square calculatePerimeter");
}
}
//源码
public class ShapeTest {
public static void main(String[] args) {
Shape [] shapes = {new Circle(), new Triangle(), new Square()};
for (Shape shape : shapes) {
shape.draw();
}
}
}
执行结果
Circle draw
Triangle draw
Square draw
由上可知,其实抽象类和普通类在使用上几乎没有任何区别。
在我看来,抽象类存在的意义主要有两点:
有时,为某些类创建对象是毫无意义的,比如上例中的 Shape(几何图形) 类,由于不知道这个 Shape 到底是 Circle(圆形)、Triangle(三角形)还是 Square(正方形),因此,如果此时为 Shape 创建了对象,那么在调用 Shape 对象的 calculateArea 方法计算面积时,Shape 对象是 不知道如何计算的。所以,此时为 Shape 类创建对象并没有什么意义。
既然都不能直接为抽象类创建对象,那留着它还有什么用呢?为子类创建模版,使抽象性更加明确。
在上例中,Circle、Triangle 和 Square 都是几何图形,它们都能绘制、擦除和计算面积、周长,因此,为了使抽象性更加明确,故将这些方法都放到了父类中。
此时,可能有人会说了:我用普通类和接口也可以实现同样的效果呀,为什么非要用抽象类呢?
1)为什么不用普通类的继承关系?
如果使用普通类的继承关系,那么就可以为父类创建对象,由《3.1 禁止创建其对象》分析可知:此时为父类(Shape)创建对象是没有任何意义的,故此处不用普通类。
2)为什么不用接口?
接口更强调功能,比如飞机能飞,小鸟能飞,超人能飞,此处显然强调的不是功能(can-do)。
综上可得:当直接为某个类创建对象显得毫无意义且需要为其子类创建模版时,就使用抽象类吧。
不是。
抽象类中可以有抽象方法,也可以没有抽象方法。当我们不想让用户直接为某个类创建对象时,就可以用 abstract 关键字修饰该类(此时,这个必须被继续,否则毫无用处)。
//源码
public abstract class Amphibian {
}
可以。
在继承方面,抽象类和普通类没什么区别,都可以继承普通类,也可以继承抽象类。在接口实现方面,普通类实现接口的时候,必须实现接口中全部的方法;抽象类在实现接口的时候,没有限制——可以实现接口中的方法也可以不实现。
//源码
public class Animal {
}
public interface Grow {
void grow();
}
public abstract class Amphibian extends Animal implements Grow{
}
有。
虽然不能直接为抽象类实例化,但抽象类是有构造方法的。因为子类在实例化的时候,需要调用父类的构造方法,而抽象类天生就是父类,所以抽象类有构造方法。
//源码
public class Animal {
public Animal() {
System.out.println("Animal Construct");
}
}
public abstract class Amphibian extends Animal implements Grow{
public Amphibian() {
System.out.println("Amphibian Construct");
}
}
public class Frog extends Amphibian {
public Frog() {
System.out.println("Frog Construct");
}
@Override
public void grow() {}
}
public class AnimalTest {
public static void main(String[] args) {
Animal animal = new Frog();
}
}
//执行结果
Animal Construct
Amphibian Construct
Frog Construct
因为如果为抽象类实例化之后,就可以用该对象调用该类的抽象方法,而此时抽象方法是没有方法体的,此时,编译器就不知道该做什么了。有人可能会说了,上面不是提到“抽象类可以没有抽象方法”吗,为什么没有定义抽象方法的抽象类也不能为其实例化呢?Java 遵守的是:宁可错杀一千,不可放过一个。所以,即使抽象类没有抽象方法,也不能直接为其实例化。
当在抽象类的构造方法里,直接调用抽象方法会发生什么呢?
//源码
public abstract class Shape {
public Shape(){
draw();
};
public abstract void draw();
public abstract void erase();
public abstract void calculateArea();
public abstract void calculatePerimeter();
}
public class Circle extends Shape {
private int radius = 47;
@Override
public void draw() {
System.out.println("Circle draw radius " + radius);
}
@Override
public void erase() {
}
@Override
public void calculateArea() {
}
@Override
public void calculatePerimeter() {
}
}
public class Triangle extends Shape {
private int width = 23, height = 47;
@Override
public void draw() {
System.out.println("Triangle draw width " + width + " height " + height);
}
@Override
public void erase() {
}
@Override
public void calculateArea() {
}
@Override
public void calculatePerimeter() {
}
}
public class Square extends Shape {
private int width = 47;
@Override
public void draw() {
System.out.println("Square draw width " + width);
}
@Override
public void erase() {
}
@Override
public void calculateArea() {
}
@Override
public void calculatePerimeter() {
}
}
public class ShapeTest {
public static void main(String[] args) {
Shape shapes[] = {new Circle(), new Triangle(), new Square()};
}
}
上面程序执行的结果无非就三种:
序号 | 执行结果 |
---|---|
1 | 程序直接报错 |
2 | Circle draw radius 47;Triangle draw width 23 height 47;Square draw width 47 |
3 | Circle draw radius 0;Triangle draw width 0 height 0;Square draw width 0 |
正确答案是:
//执行结果
Circle draw radius 0
Triangle draw width 0 height 0
Square draw width 0
如果是在普通方法里调用其他方法,相信很少有人会出错。上面的程序难就难在,直接在构造方法里面调用动态绑定方法。那到底为什么会出现上面的情况呢?
有一个概念大家必须明白:
如果一个方法被子类覆写了,那调用的时候可以调用被子类覆写的实现。(A dynamic bound method call, however, reaches “outward” into the inheritance hierarchy. It calls a mothod in a derived class.)
它和单继承一样,没什么为什么,游戏规则而已——Java 语言的开发者如此设计的。
明白上面的概念之后,再结合初始化的过程,自然就明白为什么会出现上面的情况了。以下是类初始化的过程:
抽象类的使用范围还是挺广的,接下来,我们就从设计模式的角度分析抽象类是如何使用的。
大家都知道,很多购物软件都有会员之说,如普通会员、银牌会员、金牌会员和钻石会员,在使用该软件进行消费的时候,会根据当前用户的具体身份(会员情况)进行不同的折扣处理。在设计模式中,有一种设计模式刚好与此对应——策略模式。
接下来,我们就看看如何在策略设计模式中应用抽象类。
策略模式的定义如下:
定义一系列算法,将每一个算法封装起来,并让它们可以互换。(Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.)
策略模式的结构图如下:
由上面的类图可知,策略模式主要包括三个角色:
了解了上述知识之后,不难分析出,之前提到的会员系统实例类图如下:
上面的 Discount 类既可以是抽象类也可以是接口,这样设计的好处是:
环境类(Goods)针对抽象策略类(Discount)进行编程,更加符合依赖倒转原则,替换和增加新算法较容易。
代码实现如下:
//源码
public abstract class Discount {
public abstract double discount(double price);
}
public class NormalDiscount extends Discount {
@Override
public double discount(double price) {
//普通会员无折扣
return price * 1;
}
}
public class SilverDiscount extends Discount {
@Override
public double discount(double price) {
//银牌会员享受九折折扣
return price * 0.9;
}
}
public class GoldDiscount extends Discount {
@Override
public double discount(double price) {
//金牌会员享受七折折扣
return price * 0.7;
}
}
public class DiamondDiscount extends Discount {
@Override
public double discount(double price) {
//钻石会员享受五折折扣
return price * 0.5;
}
}
public class Goods {
private double price;
private Discount discount;
public void setPrice(double p){
this.price = p;
}
public void setDiscount(Discount d){
this.discount = d;
}
public double getPrice(){
if(discount == null){
return this.price;
}else{
return discount.discount(this.price);
}
}
}
public class Cashier {
public static void main(String[] args) {
Goods goods = new Goods();
goods.setPrice(100D);
System.out.println("原始价:" + goods.getPrice());
System.out.println("-----------------------------");
Discount discount = new NormalDiscount();
goods.setDiscount(discount);
System.out.println("普通会员:" + goods.getPrice());
System.out.println("-----------------------------");
discount = new SilverDiscount();
goods.setDiscount(discount);
System.out.println("银牌会员:" + goods.getPrice());
System.out.println("-----------------------------");
discount = new DiamondDiscount();
goods.setDiscount(discount);
System.out.println("钻石会员:" + goods.getPrice());
System.out.println("-----------------------------");
}
}
//执行结果
原始价:100.0
-----------------------------
普通会员:100.0
-----------------------------
银牌会员:90.0
-----------------------------
钻石会员:50.0
-----------------------------
此时如果想增加新的会员类型(如 DiamondPLusDiscount),只需要增加新的子类即可,除了 Cashier 类之外,其他类无需做任何更改。
//源码
public class DiamondPlusDiscount extends Discount {
@Override
public double discount(double price) {
//钻石 Plus 会员享受三折折扣
return price * 0.3;
}
}
public class Cashier {
public static void main(String[] args) {
Goods goods = new Goods();
goods.setPrice(100D);
System.out.println("原始价:" + goods.getPrice());
System.out.println("-----------------------------");
Discount discount = new NormalDiscount();
goods.setDiscount(discount);
System.out.println("普通会员:" + goods.getPrice());
System.out.println("-----------------------------");
discount = new SilverDiscount();
goods.setDiscount(discount);
System.out.println("银牌会员:" + goods.getPrice());
System.out.println("-----------------------------");
discount = new DiamondDiscount();
goods.setDiscount(discount);
System.out.println("钻石会员:" + goods.getPrice());
System.out.println("-----------------------------");
discount = new DiamondPlusDiscount();
goods.setDiscount(discount);
System.out.println("钻石 Plus 会员:" + goods.getPrice());
System.out.println("-----------------------------");
}
}
//执行结果
原始价:100.0
-----------------------------
普通会员:100.0
-----------------------------
银牌会员:90.0
-----------------------------
钻石会员:50.0
-----------------------------
钻石 Plus 会员:30.0
-----------------------------