抽象类与接口

来源:互联网 发布:ios11更新不了软件 编辑:程序博客网 时间:2024/06/16 03:32

(一)抽象类

在了解抽象类之前,先来了解一下抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为:
abstract void fun();

抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。

下面要注意一个问题:在《JAVA编程思想》一书中,将抽象类定义为“包含抽象方法的类”,但是后面发现如果一个类不包含抽象方法,只是用abstract修饰的话也是抽象类。也就是说抽象类不一定必须含有抽象方法。

从这里可以看出,抽象类就是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,那么等于白白创建了这个抽象类,因为你不能用它来做任何事情。对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为abstract方法,此时这个类也就成为abstract类了。

包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:

  1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。

  2)抽象类不能用来创建对象;

  3)如果一个类继承于一个抽象类,则子类必须实现父类的所有抽象方法,具体方法可以不用管。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

abstract public class AbstractClass {    public abstract void eat();}abstract class Dog extends AbstractClass{//继承抽象类的抽象类,可以自由的写    public abstract void run();    public void say(){        System.out.println("www...");    }}class Hashiqi extends Dog{   //具体的方法,必须实现所有抽象方法    @Override    public void run() {        // TODO Auto-generated method stub    }       @Override    public void eat() {        // TODO Auto-generated method stub    }}public class T{    public static void main(String[] args) {        Dog dog=new Hashiqi();        dog.say();  //www...      }}

补充:
(1)一个类如果有抽象方法,必须定义为抽象类,不管是否还包含有其他方法。
(2)abstract和final不能同时使用,abstract和private不能同时使用。因为抽象类就是为了被继承的
(3)abstract 不能与private、static、final或native并列修饰同一个方法。


(二)接口

在使用接口过程中需要注意如下几个问题:
1、个Interface的方所有法访问权限自动被声明为public。确切的说只能为public,当然你可以显示的声明为protected、private,但是编译会出错!
2、接口中可以定义“成员变量”,或者说是不可变的常量,因为接口中的“成员变量”会自动变为为public static final。可以通过类命名直接访问:ImplementClass.name。
3、接口中不存在实现的方法。
4、实现接口的非抽象类必须要实现该接口的所有方法。抽象类可以不用实现。
5、不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用(refer to)一个实现该接口的类的对象。可以使用 instanceof 检查一个对象是否实现了某个特定的接口。例如:if(anObject instanceof Comparable){}。
6、在实现多接口的时候一定要避免方法名的重复。


(三)区别

①语法方面的区别

抽象类方式中,抽象类可以拥有任意范围的成员数据,同时也可以拥有自己的非抽象方法
但是接口方式中,它仅能够有静态、不能修改的成员数据(但是我们一般是不会在接口中使用成员数据),同时它所有的方法都必须是抽象的。在某种程度上来说,接口是抽象类的特殊化。

抽象类

public abstract class Demo {      abstract void method1();      void method2(){          //实现      }  }  

接口

interface Demo {      void method1();      void method2();  }  

②设计层次

1、 抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。

2、 跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,但是接口不同。实现它的子类可以不存在任何关系,共同之处。例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在”is-a” 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已。

3、 设计层次不同。对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。比如我们只有一个猫类在这里,如果你这是就抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。


我们有一个Door的抽象概念,它具备两个行为open()和close(),此时我们可以定义通过抽象类和接口来定义这个抽象概念:

抽象类:

abstract class Door{      abstract void open();      abstract void close();  }  

或接口

interface Door{      void open();      void close();  }  

至于其他的具体类可以通过使用extends使用抽象类方式定义Door或者Implements使用接口方式定义Door,这里发现两者并没有什么很大的差异。
但是现在如果我们需要门具有报警的功能,那么该如何实现呢?

解决方案一:给Door增加一个报警方法:clarm();

abstract class Door{      abstract void open();      abstract void close();      abstract void alarm();  }  

interface Door{      void open();      void close();      void alarm();  }  

这种方法违反了面向对象设计中的一个核心原则 ISP (Interface Segregation Principle)—见批注,在Door的定义中把Door概念本身固有的行为方法和另外一个概念”报警器”的行为方 法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为”报警器”这个概念的改变而改变,反之依然。

既然open()、close()和alarm()属于两个不同的概念,那么我们依据ISP原则将它们分开定义在两个代表两个不同概念的抽象类里面,定义的方式有三种:
1、两个都使用抽象类来定义。
2、两个都使用接口来定义。
3、一个使用抽象类定义,一个是用接口定义。

由于java不支持多继承所以第一种是不可行的。后面两种都是可行的,但是选择何种就反映了你对问题域本质的理解。

如果选择第二种都是接口来定义,那么就反映了两个问题:1、我们可能没有理解清楚问题域,AlarmDoor在概念本质上到底是门还报警器。2、如果我们对问题域的理解没有问题,比如我们在分析时确定了AlarmDoor在本质上概念是一致的,那么我们在设计时就没有正确的反映出我们的设计意图。因为你使用了两个接口来进行定义,他们概念的定义并不能够反映上述含义。

如果我们对问题域的理解是这样的:AlarmDoor本质上Door,但同时它也拥有报警的行为功能,这个时候我们使用第三种方案恰好可以阐述我们的设计意图。AlarmDoor本质是们,所以对于这个概念我们使用抽象类来定义,同时AlarmDoor具备报警功能,说明它能够完成报警概念中定义的行为功能,所以alarm可以使用接口来进行定义。如下:

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(){}  }  

这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实抽象类表示的是”is-a”关系,接口表示的是”like-a”关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。


批注:
ISP(Interface Segregation Principle):面向对象的一个核心原则。它表明使用多个专门的接口比使用单一的总接口要好。
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。


四、总结

  1、 抽象类在java语言中所表示的是一种继承关系,一个子类只能存在一个父类,但是可以存在多个接口。  2、 在抽象类中可以拥有自己的成员变量和非抽象类方法,但是接口中只能存在静态的不可变的成员数据(不过一般都不在接口中定义成员数据),而且它的所有方法都是抽象的。  3、抽象类和接口所反映的设计理念是不同的,抽象类所代表的是“is-a”的关系,而接口所代表的是“like-a”的关系。

抽象类和接口是java语言中两种不同的抽象概念,他们的存在对多态提供了非常好的支持,虽然他们之间存在很大的相似性。但是对于他们的选择往往反应了您对问题域的理解。只有对问题域的本质有良好的理解,才能做出正确、合理的设计。


is a 代表的是类之间的继承关系比如兔子属于一种动物(继承关系),如果A是B,那么B就是A的基类。
has-a( 有 “a” 汽车有轮胎) 表示组合,包含关系。比如兔子包含有腿,头等组件;就不能说兔子腿是属于一种兔子(不能说是继承关系)
like a是组合