Thinking in Java : 接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象方法的声明规则:抽象方法只包含一个方法名,而没有方法体(无花括号):abstract void f();
以抽象类Shapes为例:
|
|
当Square类继承Shapes时,它提供了getArea和getPerimeter方法的实现:
|
|
包含抽象方法的类叫做抽象类,如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。
如果从一个抽象类继承,并想创建该导出类的对象,那么就必须为基类中的所有抽象方法提供方法定义。如果选择不做,那么导出类便也是抽象类,编译器会强制用abstract关键字来限定这个类。
抽象类不能被实例化,如下:
|
|
在使用抽象类时需要注意几点:
Think in java:创建抽象类和抽象方法非常有用,因为他们可以使类的抽象性明确起来,并告诉用户和编译器打算怎样使用他们.抽象类还是有用的重构器,因为它们使我们可以很容易地将公共方法沿着继承层次结构向上移动。
接口是一种比抽象类更加抽象的一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
接口建立类与类之间的协议,它所提供的只是一种形式,而没有具体的实现。实现该接口的实现类必须要实现该接口的所有方法。
接口是抽象类的延伸,Java了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类。但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,但是推荐继承和接口共同使用,因为这样既可以保证数据安全性又可以实现多重继承。
以Externalizable接口为例:
|
|
当你实现这个接口时,你就需要实现上面的两个方法:
public class Employee implements Externalizable {int employeeId;String employeeName;@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {employeeId = in.readInt();employeeName = (String) in.readObject();}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeInt(employeeId);out.writeObject(employeeName);}}
Interface的所有方法访问权限自动被声明为public。确切的说只能为public,当然你可以显示的声明为protected、private,但是编译会出错.在使用接口过程中需要注意如下几个问题:
尽管抽象类和接口之间存在较大的相同点,甚至有时候还可以互换,但这样并不能弥补他们之间的差异之处。下面将从语法层次和设计层次两个方面对抽象类和接口进行阐述。
在语法层次,java语言对于抽象类和接口分别给出了不同的定义。下面已Demo类来说明他们之间的不同之处。
使用抽象类来实现:
public abstract class Demo {abstract void method1();void method2(){//具体实现}}
interface Demo {使用接口来实现:
void method1();void method2();}
但是接口方式中,它仅能够有静态、不能修改的成员数据(但是我们一般是不会在接口中使用成员数据),同时它所有的方法都必须是抽象的。在某种程度上来说,接口是抽象类的特殊化。抽象类方式中,抽象类可以拥有任意范围的成员数据,同时也可以拥有自己的非抽象方法.
对子类而言,它只能继承一个抽象类(这是java为了数据安全而考虑的),但是却可以实现多个接口。
参数 | 抽象类 | 接口 |
---|---|---|
实现 | 子类使用extends关键字来继承抽象类,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字implements来实现接口,它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
方法 | 抽象类中可以拥有自己的成员变量和非抽象类方法 | 接口中只能存在静态的不可变的成员数据(不过一般都不在接口中定义成员数据),而且它的所有方法都是抽象的 |
访问修饰符 | 抽象方法可以有public、protected和default这些修饰符 | 接口方法默认修饰符是public。你不可以使用其它修饰符。 |
main方法 | 抽象方法可以有main方法并且我们可以运行它 | 接口没有main方法,因此我们不能运行它 |
多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 |
上面只是从语法层次和编程角度来区分它们之间的关系,这些都是低层次的,要真正使用好抽象类和接口,我们就必须要从较高层次来区分了。只有从设计理念的角度才能看出它们的本质所在。一般来说他们存在如下三个不同点:
为了更好的阐述他们之间的区别,下面将使用一个例子来说明:
我们有一个Door的抽象概念,它具备两个行为open()和close(),此时我们可以定义通过抽象类和接口来定义这个抽象概念:
抽象类:
abstract class Door{abstract void open();abstract void close();}
接口:
但是现在如果我们需要门具有报警的功能,那么该如何实现呢?至于其他的具体类可以通过使用extends使用抽象类方式定义Door或者Implements使用接口方式定义Door,这里发现两者并没有什么很大的差异。interface Door{void open();void close();}
abstract class Door{abstract void open();abstract void close();abstract void clarm();}
interface Door{void open();void close();void clarm();}
或者
这种方法违反了面向对象设计中的一个核心原则:ISP (Interface Segregation Principle),在Door的定义中把Door概念本身固有的行为方法和另外一个概念”报警器”的行为方 法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为”报警器”这个概念的改变而改变,反之依然。
ISP(Interface Segregation Principle):面向对象的一个核心原则。它表明使用多个专门的接口比使用单一的总接口要好。一个类对另外一个类的依赖性应当是建立在最小的接口上的。一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。
既然open()、close()和alarm()属于两个不同的概念,那么我们依据ISP原则将它们分开定义在两个代表两个不同概念的抽象类里面,定义的方式有三种:
由于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(){}}