编写类的时候,通常会为这个类定义一些方法,用来描述该类的行为方式,这些方法通常应该有具体的实现代码。但在有些情况下,父类只知道子类应该有什么行为,却不知道子类该如何实现这些行为。
例如,父类Shape知道,它的子类应该能够计算周长。但是不同的子类计算周长的方式不同,即Shape类不知道子类该如何实现这个方法。
既然这样,那父类干脆不要知道这个行为就好了。但是这样一来,假设一个Shape引用变量
Shape a
实际上指向的是Shape的子类的实例,那么这个引用变量就无法调用子类的方法了。除非将这个引用变量强制转换成子类类型(回忆一下向上转换和强制转换),才能调用子类的方法。而想要让程序更加灵活,只需要在父类里也声明子类的方法,但是不做具体实现即可。但是只声明一个空方法,有可能会让指向父类的引用变量直接调用,出现一些不可预期的错误。所以再加一道保险,就是把这个方法声明为抽象方法。
abstract
修饰符
使用abstract
修饰的方法称为抽象方法;使用abstract
修饰的类称为抽象类。
- 含有抽象方法的类必须定义为抽象类;
- 抽象类里可以不包含抽象方法。
抽象方法很简单,就一条要求:没有方法体。
抽象类稍微事儿多一点:
- 抽象类不能被实例化,不能使用new调用它的构造器创建实例。即使抽象类里没有抽象方法,也不能实例化。
- 抽象类可以包含成员变量、方法、构造器、初始化块、内部类(接口|枚举)等5种成分。需要重复一遍,抽象类的构造器不能用来实例化,只能被子类调用。
- 只要含有抽象方法,它就必须被定义成抽象类。
我们可以把抽象和具象对比起来看:父类里干的都是抽象的事儿,具象化是由子类实现的。
注意,抽象方法不是空方法。它没有方法体。所谓没有方法体,就是没有{}
。直接在方法定义后加分号。
public abstract class Shape
{
{
System.out.println("执行Shape的初始化块");
}
private String color;
// 计算周长的抽象方法
public abstract double calPerimeter();
// 返回形状类型的抽象方法
public abstract String getType();
// 构造器,这个构造器不能用来实例化,只能被子类调用
public Shape(){}
public Shape(string color)
{
System.out.println("执行Shape的构造器");
this.color = color;
}
// color的get和set方法
}
- 因为Shape类里定义了抽象方法,所以Shape只能是抽象类;
- Shape类的两个构造器都不能用来实例化Shape对象,只能被它的子类调用。
定义Triangle类,派生自Shape类
public class Triangle extends Shape
{
// 三角形的三条边
private double a;
private double b;
private double c;
public Triangle(String color, double a, double b, double c)
{
super(color); // 调用父类带一个参数的构造器
this.setSides(a, b, c);
}
public void setSides(double a, double b, double c)
{
if (a >= b + c || b >= a + c || c >= a + b)
{
System.out.println("三角形两边之和必须大于第三边");
return;
}
this.a = a;
this.b = b;
this.c = c;
}
// 重写(Override)Shape类中的抽象方法
public double calPerimeter()
{
return a + b + c;
}
public String getType()
{
return "三角形";
}
}
定义Circle类,同样是Shape的子类
public class Circle extends Shape
{
private double radius;
public Circle(String color, double radius)
{
super(color);
this.radius = radius;
}
public void setRadius(double radius)
{
this.radius = radius;
}
// 重写父类的抽象方法
public double calPerimeter()
{
return 2 * Math.PI * raduis;
}
public String getType()
{
return getColor() + "圆"; // getColor是父类的普通方法
}
}
main中使用
public class Class
{
public static void main(String[] args)
{
Shape s1 = new Triagle("黑色", 3, 4, 5);
Shape s2 = new Circle("红色", 3);
System.out.println(s1.getType());
System.out.println(s1.calPerimeter());
System.out.println(s2.getType());
System.out.println(s2.calPerimeter());
}
}
注意,s1和s2都是Shape引用变量,但是因为Shape里定义了getType和calPerimeter方法,所以不需要强制类型转换也可以调用子类的方法。
抽象类是多态的重要组成机制。
多态的另两个重要组成机制是重载和重写。
抽象类的作用是啥?
类似于顶层设计,先用父类给子类的行为做一个设定。子类必须按照父类的设定,实现具体方法,从而避免子类设计过于随意。
这种父类先打个样,子类照着往里填写的方式,就是模版模式。模版就是基于抽象类实现的。