Java 抽象类你必须了解的一些知识

1.概述

在 Java 中,被 abstract 关键字修饰的类叫抽象类。

抽象类的定义格式如下:

abstract class 抽象类名称{
    属性;
    访问权限 返回值类型 方法名称(参数){
        [return 返回值]
    }
    //在抽象方法中是没有方法体的
    访问权限 abstract 返回值类型 方法名称(参数);
}
复制代码
2.抽象类的使用
2.1 创建抽象类
//源码
//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;
	}
	
}
复制代码
2.2 创建抽象类的子类
//源码
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");
	}

}
复制代码
2.3 测试抽象类
//源码
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
复制代码

由上可知,其实抽象类和普通类在使用上几乎没有任何区别。

3.抽象类存在的意义

在我看来,抽象类存在的意义主要有两点:

  • 禁止创建其对象
  • 为子类创建模版,使抽象性更加明确
3.1 禁止创建其对象

有时,为某些类创建对象是毫无意义的,比如上例中的 Shape(几何图形) 类,由于不知道这个 Shape 到底是 Circle(圆形)、Triangle(三角形)还是 Square(正方形),因此,如果此时为 Shape 创建了对象,那么在调用 Shape 对象的 calculateArea 方法计算面积时,Shape 对象是 不知道如何计算的。所以,此时为 Shape 类创建对象并没有什么意义。

3.2 为子类创建模版,使抽象性更加明确

既然都不能直接为抽象类创建对象,那留着它还有什么用呢?为子类创建模版,使抽象性更加明确

在上例中,Circle、Triangle 和 Square 都是几何图形,它们都能绘制、擦除和计算面积、周长,因此,为了使抽象性更加明确,故将这些方法都放到了父类中。

此时,可能有人会说了:我用普通类和接口也可以实现同样的效果呀,为什么非要用抽象类呢?

1)为什么不用普通类的继承关系?

如果使用普通类的继承关系,那么就可以为父类创建对象,由《3.1 禁止创建其对象》分析可知:此时为父类(Shape)创建对象是没有任何意义的,故此处不用普通类。

2)为什么不用接口?

接口更强调功能,比如飞机能飞,小鸟能飞,超人能飞,此处显然强调的不是功能(can-do)。

综上可得:当直接为某个类创建对象显得毫无意义且需要为其子类创建模版时,就使用抽象类吧。

4.抽象类中易混淆的概念
4.1 抽象类必须有抽象方法吗?

不是。

抽象类中可以有抽象方法,也可以没有抽象方法。当我们不想让用户直接为某个类创建对象时,就可以用 abstract 关键字修饰该类(此时,这个必须被继续,否则毫无用处)。

//源码
public abstract class Amphibian {

}
复制代码
4.2 抽象类可以继承普通类吗?

可以。

在继承方面,抽象类和普通类没什么区别,都可以继承普通类,也可以继承抽象类。在接口实现方面,普通类实现接口的时候,必须实现接口中全部的方法;抽象类在实现接口的时候,没有限制——可以实现接口中的方法也可以不实现。

//源码
public class Animal {
	
}

public interface Grow {
	
	void grow();

}

public abstract class Amphibian extends Animal implements Grow{

}

复制代码
4.3 抽象类有构造方法吗?

有。

虽然不能直接为抽象类实例化,但抽象类是有构造方法的。因为子类在实例化的时候,需要调用父类的构造方法,而抽象类天生就是父类,所以抽象类有构造方法。

//源码
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
复制代码
4.4 为什么不能直接为抽象类实例化(创建抽象类的对象)?

因为如果为抽象类实例化之后,就可以用该对象调用该类的抽象方法,而此时抽象方法是没有方法体的,此时,编译器就不知道该做什么了。有人可能会说了,上面不是提到“抽象类可以没有抽象方法”吗,为什么没有定义抽象方法的抽象类也不能为其实例化呢?Java 遵守的是:宁可错杀一千,不可放过一个。所以,即使抽象类没有抽象方法,也不能直接为其实例化。

4.5 直接在抽象类的构造方法里调用抽象方法会发生什么?

当在抽象类的构造方法里,直接调用抽象方法会发生什么呢?

//源码
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程序直接报错
2Circle draw radius 47;Triangle draw width 23 height 47;Square draw width 47
3Circle 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 语言的开发者如此设计的。

明白上面的概念之后,再结合初始化的过程,自然就明白为什么会出现上面的情况了。以下是类初始化的过程:

  1. 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零;
  2. 如本例演示的那样调用父类构造方法。此时,调用被覆写后的 draw 方法,由于步骤 1 的缘故,此时 radius、width 和 height 的值为 0;
  3. 按照声明的顺序调用成员的初始化方法;
  4. 调用子类的构造方法。
5. 抽象类实际应用

抽象类的使用范围还是挺广的,接下来,我们就从设计模式的角度分析抽象类是如何使用的。

大家都知道,很多购物软件都有会员之说,如普通会员、银牌会员、金牌会员和钻石会员,在使用该软件进行消费的时候,会根据当前用户的具体身份(会员情况)进行不同的折扣处理。在设计模式中,有一种设计模式刚好与此对应——策略模式。

接下来,我们就看看如何在策略设计模式中应用抽象类。

策略模式的定义如下:

定义一系列算法,将每一个算法封装起来,并让它们可以互换。(Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.)

策略模式的结构图如下:

由上面的类图可知,策略模式主要包括三个角色:

  1. Context(环境类)
    使用算法的类,它在解决某个问题时,可以采用多种策略。在此类中维护一个指向抽象策略类的引用,用于定义所采用的策略。
  2. Strategy(抽象策略类)
    抽象策略类为所支持的算法提供了方法声明,该类可以是抽象类也可以是接口。
  3. ConcreteStrategy(具体策略类)
    具体策略类继承或实现抽象策略类,是具体算法的定义处。

了解了上述知识之后,不难分析出,之前提到的会员系统实例类图如下:

上面的 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
-----------------------------
复制代码

参考文档
  1. 《Java 开发实战经典》
  2. 《Thinking in Java》
  3. 《设计模式》
  4. Java Tutorials
  5. Design Principles

转载于:https://juejin.im/post/5c075914e51d451dc86d896f

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值