在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
在 Java 语言中使用 abstract class 来定义抽象类。如下实例:
注意到该 Employee 类没有什么不同,尽管该类是抽象类,但是它仍然有 3 个成员变量,7 个成员方法和 1 个构造方法。 现在如果你尝试如下的例子:
当你尝试编译 AbstractDemo 类时,会产生如下错误:
Employee.java:46: Employee is abstract; cannot be instantiated Employee e = new Employee("George W.", "Houston, TX", 43); 1 error我们可以通过以下方式继承 Employee 类的属性:
尽管我们不能实例化一个 Employee 类的对象,但是如果我们实例化一个 Salary 类对象,该对象将从 Employee 类继承 7 个成员方法,且通过该方法可以设置或获取三个成员变量。
以上程序编译运行结果如下:
Constructing an Employee Constructing an Employee Call mailCheck using Salary reference -- Within mailCheck of Salary class Mailing check to Mohd Mohtashim with salary 3600.0 Call mailCheck using Employee reference-- Within mailCheck of Salary class Mailing check to John Adams with salary 2400. 如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
声明抽象方法会造成以下两个结果:
如果一个类包含抽象方法,那么该类必须是抽象类。 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。
如果Salary类继承了Employee类,那么它必须实现computePay()方法:
2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
菲菲小姐
xf0***123@163.com
并不是说" 一定要调用父类的显性构造器 ",而是子类在继承父类时,如果父类的显式构造器中有参数,子类要声明给出这个参数。这是一个关于继承的问题。
举一个例子如果把上面的代码改成:
abstract class Animal{ private int age = 10; public Animal(){ System.out.println("初始化Animal"); public void move(){ System.out.println("跑步数:"+this.age); abstract class Dog extends Animal{ public Dog(int age){ // super(age);//去掉会报异常 System.out.println("初始化Dog"); public class BigDogs extends Dog{ public BigDogs(){ super(20); System.out.println("初始化BigDog"); public static void main(String[] args){ BigDogs a = new BigDogs(); a.move();将第一个父类构造器中要求参数那一行去掉,则第二个抽象类中调用父类构造器的部分就可以删去,编译是可以通过的。
public abstract class Shape { public abstract double area(); public abstract double perimeter();public class Rectangle extends Shape { private double length; private double width; public double getLength() { return length; public void setLength(double length) { this.length = length; public double getWidth() { return width; public void setWidth(double width) { this.width = width; @Override public double area() { return getLength() * getWidth(); @Override public double perimeter() { return 2 * (getWidth() + getWidth());三角形类:
public class Triangle extends Shape { private double a, b, c; public double getA() { return a; public void setA(double a) { this.a = a; public double getB() { return b; public void setB(double b) { this.b = b; public double getC() { return c; public void setC(double c) { this.c = c; @Override public double area() { double p = (getA() + getB() + getC()) / 2; return Math.sqrt(p * (p - getA()) * (p - getB()) * (p - getC())); @Override public double perimeter() { return getA() + getB() + getC();public class Circle extends Shape { private double diameter; public double getDiameter() { return diameter; public void setDiameter(double diameter) { this.diameter = diameter; @Override public double area() { return Math.PI * Math.pow(getDiameter() / 2, 2); @Override public double perimeter() { return Math.PI * getDiameter();测试代码:
public class Test { public static void main(String [] args){ Rectangle rec = new Rectangle(); rec.setLength(10); rec.setWidth(5); double rec_area = rec.area(); double rec_perimeter = rec.perimeter(); System.out.println("矩形的面积:"+rec_area+",周长"+rec_perimeter); Triangle tri = new Triangle(); tri.setA(3); tri.setB(4); tri.setC(5); double tri_area = tri.area(); double tri_perimeter = tri.perimeter(); System.out.println("三角形的面积:"+tri_area+",周长"+tri_perimeter); Circle cir = new Circle(); cir.setDiameter(10); double cir_area = cir.area(); double cir_perimeter = cir.perimeter(); System.out.println("圆形的面积:"+cir_area+",周长"+cir_perimeter);
1 )抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类 Airplane,将鸟设计为一个类 Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
2) 设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过 ppt 里面的模板,如果用模板 A 设计了 ppt B 和 ppt C,ppt B 和 ppt C 公共的部分就是模板 A 了,如果它们的公共部分需要改动,则只需要改动模板 A 就可以了,不需要重新对 ppt B 和 ppt C 进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
下面看一个网上流传最广泛的例子:门和警报的例子:门都有 open() 和 close() 两个动作,此时我们可以定义通过抽象类和接口来定义这个抽象概念:
abstract class Door { public abstract void open(); public abstract void close(); interface Door { public abstract void open(); public abstract void close();但是现在如果我们需要门具有报警 的功能,那么该如何实现?下面提供两种思路:
1)将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;
2)将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的 open( ) 和 close( ),也许这个类根本就不具备 open( ) 和 close( ) 这两个功能,比如火灾报警器。
从这里可以看出, Door 的 open() 、close() 和 alarm() 根本就属于两个不同范畴内的行为,open() 和 close() 属于门本身固有的行为特性,而 alarm() 属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含 alarm() 行为,Door 设计为单独的一个抽象类,包含 open 和 close 两种行为。再设计一个报警门继承 Door 类和实现 Alarm 接口。
interface Alram { void alarm(); abstract class Door { void open(); void close(); class AlarmDoor extends Door implements Alarm { void oepn() { //.... void close() { //.... void alarm() { //....
抽象方法和抽象类看上去是多余的,对于抽象方法,不知道如何实现,定义一个空方法体不就行了吗,而抽象类不让创建对象,看上去只是增加了一个不必要的限制。
引入抽象方法和抽象类,是Java提供的一种语法工具,对于一些类和方法,引导使用者正确使用它们,减少被误用。
使用抽象方法,而非空方法体,子类就知道他必须要实现该方法,而不可能忽略。
使用抽象类,类的使用者创建对象的时候,就知道他必须要使用某个具体子类,而不可能误用不完整的父类。
无论是写程序,还是平时做任何别的事情的时候,每个人都可能会犯错,减少错误不能只依赖人的优秀素质,还需要一些机制,使得一个普通人都容易把事情做对,而难以把事情做错。抽象类就是Java提供的这样一种机制。