abstract class和interface



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和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?

声明方法的存在而不去实现它的类被叫做抽象类(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语言中都是用来进行抽象类定义的,
那么什么是抽象类?
使用抽象类能为我们带来什么好处呢?


 在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。
并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的






在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉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 class和interface 上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。 前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。 考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:
使用abstract class方式定义Door:
abstract class Door
 { abstract void open();
 abstract void close();
}
使用interface方式定义Door:
interface Door {
void open();
void close();
 }
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。 如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。
解决方案一: 简单的在Door的定义中增加一个alarm方法,如下:
abstract class Door {
abstract void open();
abstract void close();
abstract void alarm(); } 或者
interface Door {
void open();
 void close();
 void alarm(); }
 那么具有报警功能的AlarmDoor的定义方式如下:
class AlarmDoor extends Door { void open() { … } void close() { … } void alarm() { … } }
或者
class AlarmDoor implements Door { void open() { … } void close() { … } void alarm() { … } }
这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。 解决方案二: 既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。
 显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。 如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。 如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?
前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:
abstract class Door {
abstract void open();
abstract void close();
 }
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
 void alarm() { … }
 }
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。














在以前的编程过程中,经常对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功能,但是我们深究一下,其本质是一样的吗?
三、分析本质: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)静态变量(相当于常量)可以用类名.静态变量名直接使用,接口又是类的一种,所以接口名.静态变量名可用;




发布了27 篇原创文章 · 获赞 271 · 访问量 124万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览