在Java语言中,abstract class和interface是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。
|
Abstract class |
Interface |
实例化 |
不能 |
不能 |
类 |
一种继承关系,一个类只能使用一次继承关系。可以通过继承多个接口实现多重继承 |
一个类可以实现多个interface |
数据成员 |
可有自己的 |
静态的不能被修改即必须是static final,一般不在此定义 |
方法 |
可以私有的,非abstract方法,必须实现 |
不可有私有的,默认是public,abstract 类型 |
变量 |
可有私有的,默认是friendly 型,其值可以在子类中重新定义,也可以重新赋值 |
不可有私有的,默认是public static final 型,且必须给其初值,实现类中不能重新定义,不能改变其值。 |
设计理念 |
表示的是“is-a”关系 |
表示的是“like-a”关系 |
实现 |
需要继承,要用extends |
要用implements |
声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类为。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。
接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现 这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。 然后,它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到 接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。
接口可以继承接口。抽象类可以实现(implements)接口,抽象类是可以继承实体类,但前提是实体类必须有明确的构造函数。接口更关注“能实现什么功能”,而不管“怎么实现的”。
1.相同点
A. 两者都是抽象类,都不能实例化。
B. interface实现类及abstrct class的子类都必须要实现已经声明的抽象方法。
2. 不同点
A. interface需要实现,要用implements,而abstract class需要继承,要用extends。
B. 一个类可以实现多个interface,但一个类只能继承一个abstract class。
C. interface强调特定功能的实现,而abstract class强调所属关系。
D. 尽管interface实现类及abstrct class的子类都必须要实现相应的抽象方法,但实现的形式不同。interface中的每一个方法都是抽象方法,都只是声明的 (declaration, 没有方法体),实现类必须要实现。而abstract class的子类可以有选择地实现。
这个选择有两点含义:
一是Abastract class中并非所有的方法都是抽象的,只有那些冠有abstract的方法才是抽象的,子类必须实现。那些没有abstract的方法,在Abstrct class中必须定义方法体。
二是abstract class的子类在继承它时,对非抽象方法既可以直接继承,也可以覆盖;而对抽象方法,可以选择实现,也可以通过再次声明其方法为抽象的方式,无需实现,留给其子类来实现,但此类必须也声明为抽象类。既是抽象类,当然也不能实例化。
E. abstract class是interface与Class的中介。
interface是完全抽象的,只能声明方法,而且只能声明pulic的方法,不能声明private及protected的方法,不能定义方法体,也 不能声明实例变量。然而,interface却可以声明常量变量,并且在JDK中不难找出这种例子。但将常量变量放在interface中违背了其作为接 口的作用而存在的宗旨,也混淆了interface与类的不同价值。如果的确需要,可以将其放在相应的abstract class或Class中。
abstract class在interface及Class中起到了承上启下的作用。一方面,abstract class是抽象的,可以声明抽象方法,以规范子类必须实现的功能;另一方面,它又可以定义缺省的方法体,供子类直接使用或覆盖。另外,它还可以定义自己 的实例变量,以供子类通过继承来使用。
3. interface的应用场合
A. 类与类之前需要特定的接口进行协调,而不在乎其如何实现。
B. 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
C. 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系。
D. 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系。
4. abstract class的应用场合
一句话,在既需要统一的接口,又需要实例变量或缺省的方法的情况下,就可以使用它。最常见的有:
A. 定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口。可以用abstract class定义一组方法体,甚至可以是空方法体,然后由子类选择自己所感兴趣的方法来覆盖。
B. 某些场合下,只靠纯粹的接口不能满足类与类之间的协调,还必需类中表示状态的变量来区别不同的关系。abstract的中介作用可以很好地满足这一点。
C. 规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能。
abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。
abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。
其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。
理解抽象类 abstract class和interface在Java语言中都是用来进行抽象类定义的,
那么什么是抽象类r> 使用抽象类能为我们带来什么好处呢r>
在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。
从语法定义层面看
abstract class和interface 在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。 使用abstract class的方式定义Demo抽象类的方式如下: abstract class Demo { abstract void method1(); abstract void method2(); … } 使用interface的方式定义Demo抽象类的方式如下: interface Demo { void method1(); void method2(); … } 在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。
从编程的角度来看,
abstract class和interface都可以用来实现”design by contract”的思想。但是在具体的使用上面还是有一些区别的。
首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。
其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。
在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。 同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了”one rule,one place”原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。
在以前的编程过程中,经常对abstract和interface混淆,相信很多初学者都有这样的困惑,也问过很多经验丰富的程序员,他们也不能清楚地说出个所以然来。经过自己的思考,想到了一种比较形象的方式来区别和深入理解abstract和interface的概念。
abstract和interface的区别不仅仅是一种程序上的区别,更是一种设计思想的区别。
一、前提知识:abstract是一种继承关系,一个类只能使用一次继承关系;interface其实也是一种特殊的abstract class,但是一个类可以实现多个interface。
二、现实场景:现有一辆车car,它具有基本的开车driver和停车stop动作,Car是一个抽象的概念,他可以实例化为轿车、卡车等,我们可以用abstract和interface两种方法去定义这辆车car。
[java] view plain copy
abstract class Car{
abstarct void driver();
abstarct void stop();
}
[java] view plain copy
interface Car{
void driver();
void stop();
}
如果要实现卡车类struck,我们可以通过两种方法实现:1.extends Car,2.interface Car;两种方法看起来都可以实现,现在我们需要在struck类上添加一个GPS功能,应该如何实现我们可以在abstract Car 和 interface Car中添加方法,如下:
[java] view plain copy
abstract class Car{
abstract void driver();
abstract void stop();
abstract void GPS();
}
[java] view plain copy
interface Car{
void driver();
void stop();
void GPS();
}
然后两种方式去实现:
[java] view plain copy
class Struck extends Car{
void driver(){};
void stop(){};
void GPS(){};
}
[java] view plain copy
class Struck implements Car{
void driver(){};
void stop(){};
void GPS(){};
}
这两种方法都可以实现在struck中添加GPS功能,但是我们深究一下,其本质是一样的吗r> 三、分析本质:1.GPS与Car的关系——Car在这里有两个主要动作:driver和stop,而GPS是struck需要添加的功能,GPS并不是Car的基本属性一部分,GPS应该是一种另外的个体,所以GPS与Car是不同的范畴。
2.两个不同概念的实现——GPS和Car两个不同概念,现在需要在struck中集中表现出来,我们可以有一下方式:A.继承extends——(再定义一个abstract class GPS)因为一个类只能单一继承,要struck继承Car和GPS显然是行不通的,所以继承的方法不行;B.接口implements——(再定义一个interface GPS)struck可以实现GPS和Car两个接口,这种方法可以实现;C.继承与接口extends+implements——Struck可通过extends一个GPS(或者Car)在implements一个Car(GPS)实现,这种方法可行。
3.比较实现方法——在这里,我们需要理解清楚一个问题:卡车Struck到底是Car还是GPS,这里的答案很清晰,必然是Car。我们知道,继承关系extends是一种“is-a”的关系,那么卡车Struck是Car的一种,也是”is-a”关系,所以Struck与Car之间的关系应该是继承extends的关系,我们应该用abstract class来完成。
另外,Struck与GPS不是一种“is-a”关系,Struck只是具有GPS的功能,所以Struck不能使用继承的方法去完成GPS的功能,但是我们可以通过interface方式来实现。
[java] view plain copy
abstract class Car{
abstract void driver();
abstract void stop();
}
[java] view plain copy
interface GPS{
void GPS();
}
Struck实现:
[java] view plain copy
class Struck extends Car implements GPS{
void driver(){};
void stop(){};
void GPS();
}
abstract和interface虽然在用法上很接近,但是我们可以通过本质来观察到其实两者之间是具有很大的差别。
abstract修饰符可以修饰类和方法。
(1)abstract修饰类,会使这个类成为一个抽象类,这个类将不能生成对象实例,但可以做为对象变量声明的类型(见后面实例),也就是编译时类型。抽象类就相当于一类的半成品,需要子类继承并覆盖其中的抽象方法。
(2)abstract修饰方法,会使这个方法变成抽象方法,也就是只有声明而没有实现,需要子类继承实现。
(3)注意的地方:
A:有抽象方法的类一定是抽象类。但是抽象类中不一定都是抽象方法,也可以全是具体方法。abstract修饰符在修饰类时必须放在类名前。abstract修饰方法就是要求其子类(实现)这个方法,调用时就可以以多态方式调用子类覆盖(实现)后的方法,除非子类本身也是抽象类。
B:父类是抽象类,其中有抽象方法,那么子类继承父类,并把父类中的所有抽象方法都实现(覆盖)了,子类才有创建对象的实例的能力,否则子类也必须是抽象类。简单的例子下面有一个抽象类
[java] view plain copy
abstract class E{
public abstract void show();
}
class F extends E{
public void show(){
System.out.print(“test all FFFF n”);
}
}
class G extends E{
public void show(){
System.out.print(“test all GGGG n”);
}
}
public class main
{
public static void main(String[] args)throws InterruptedException {
E p = new F();
p.show();
E q = new G();
q.show();
}
}
就会发生多态现象。执行结果就是在console输出
test all FFFF
test all GGGG
=============================================================================================================================
extends是继承父类,只要那个类不是声明为final就能继承。Java中不支持多重继承,但是可以用接口来实现,这样就要用到implements。继承只能继承一个类,但implements可以实现多个接口,用逗 分开就行了,比如 class A extends B implements C,D,E。
与extends的差别:extends 是继承某个类,继承之后可以使用父类的方法也可以重写父类的方法;implements 是实现多个接口,接口的方法必须重写才能使用。要注意以下几点:
A,接口中一般定义的是常量和抽象方法。抽象类中可以包含抽象方法,也可以有非抽象方法,但是有抽象方法的类一定是抽象类。抽象方法不能有方法体。
B,接口(interface)中,方法只能定义抽象方法而且默认是Public,常量则是public static final 修饰的(不管有没有这些修饰符,方法和常量默认具这种属性)。
C,一个类可以实现多个无关的接口(这点和继承要有所区别)。
D,接口可以继承其他的接口,并添加新的属性和抽象方法。
E,在类中实现接口的方法时必须加上public修饰符。
示例:
[java] view plain copy
interface W1{ //定义接口
public final static int i = 3;
void start();
void run();
void stop();
}
interface W2 extends W1{ //接口间可以继承,并添加新的属性方法
public final static int j = 4; //常量的修饰
void openMonth();
void upAndDown();
void goIn();
}
class TT implements W2{
public void start(){ //实现接口的同时会继承接口的变量,实现接口的方法加上public
System.out.println(“—-start()—-“);
}
public void run(){
System.out.println(“—-run()—-“);
}
public void stop(){
System.out.println(“—-stop()—-“);
}
public void openMonth(){
System.out.println(“—-openMonth()—-“);
}
public void upAndDown(){
System.out.println(“—-upAndDown()—-“);
}
public void goIn(){
System.out.println(“—-goIn()—-“);
}
}
public class main {
public static void main(String[] args) {
// TODO Auto-generated method stub
W1 tt = new TT(); //实现对象指向接口引用的父类
System.out.println(TT.i); //类名.静态变量
System.out.println(tt.i); //实例.静态变量
System.out.println(W1.i); //接口名.静态变量
tt.start();
W2 ee = new TT();
System.out.println(TT.j); //类名.静态变量
System.out.println(ee.j); //实例.静态变量
System.out.println(W2.j); //接口名.静态变量
ee.start();
}
}
执行结果:
3
3
3
—-start()—-
4
4
4
—-start()—-
得到这个结果,需要注意的几点:(1)静态变量(相当于常量)可以用类名.静态变量名直接使用,接口又是类的一种,所以接口名.静态变量名可用;
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!