Java有两种机制可以为某个抽象提供多种实现——Interfaceabstract class

Interface 和 abstract class,
除了比较明显的区别(也就是能否提供基本实现),
比较重要的区别是——
接口的实现类可以处于类层次的任何一个位置,而抽象类的子类则受到这一限制。

Existing classes can be easily retrofitted to implement a new interface.

即,如果一个类要实现某个接口,只需要加上implements语句和方法实现。
而继承一个抽象类则可能会破坏类层次,比如,属于两个不同类层次的类都想要某一个抽象类的行为时我们需要重新整理一下类层次。

Interfaces are ideal for defining mixins.

(“mixin”这一词不知该如何翻译,翻译为"混合类型"显得很僵硬。)
个人觉得这一条和第一条几乎是说明同一个问题。
关于mixin,作者用Comparable举例,其实现类表明自己的实例有相互比较的能力。
而抽象类不能随意更新到现有的类中,考虑到类层次结构,为类提供某个行为的抽象时接口更为合适。

Interfaces allow the construction of nonhierarchical type frameworks.

事实上类层次结构并不是一无是处的,但这需要我们思考:是否需要组织为严格的层次结构。
比如,歌手和作曲家是否需要层次结构? 显然他们没有层次关系。

即:

public interface Singer{
AudioClip sing(Song s);
} public interface Songwriter{
Song compose(boolean hit);
}

如果是创作型歌手,他需要同时扩展Singer和Songwriter。
幸好上面二者都是interface,于是我们可以:

public interface SingerSongwirter extends Singer, Songwriter{
AudioClip strum();
void actSensitive();
}

如果试图使用抽象类解决这一问题,
也许我们可以将一个类的对象作为另一个类的field,
也许我们也可以将它们组织成类层次关系。
但如果类的数量越来越多,出现更多的组合,结构变得越来越臃肿(ps:称为"combinatorial explosion")。

另外,说说接口比较明显的"缺点",也就是不能提供任何实现。
但需要注意的是,这个特征并不能使抽象类取代接口。
比较好的方法将两者结合起来,这种用法很常见。
比如Apache Shiro的DefaultSecurityManager的类层次(当然,Shiro还在不断完善中...):

即,为接口中的定义提供一个抽象的骨架实现(skeletal implementation),将接口和抽象类的优点结合起来。

通常,一个抽象类为接口提供skeletal实现时存在这样的命名规则,比如AbstractSet和Set、AbstractCollection和Collection。

如果我用这个skeletal实现,岂不是又要受类层次的困扰?
确实是这样,但skeletal的意义并不在于灵活性。
先举书中的代码例子,静态工厂方法使用skeletal类返回整型列表(ps:过度使用自动装拆箱...):

public class IntArrays {
static List<Integer> intArrayAsList(final int[] a) {
if (a == null)
throw new NullPointerException(); return new AbstractList<Integer>() {
public Integer get(int i) {
return a[i];
} @Override
public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val;
return oldVal;
} public int size() {
return a.length;
}
};
}
}

另外再举个Apache Shiro中的例子,在org.apache.shiro.realm.Realm接口中有这么一段说明:

Most users will not implement the Realm interface directly, but will extend one of the subclasses, {@link org.apache.shiro.realm.AuthenticatingRealm AuthenticatingRealm} or {@link org.apache.shiro.realm.AuthorizingRealm}, greatly reducing the effort requird to implement a Realm from scratch.

即,直接实现某个接口是个繁琐的工作。我们更建议使用其子类(当然,并不是必须),比如:

org.apache.shiro.realm.CachingRealm CachingRealm
org.apache.shiro.realm.AuthenticatingRealm AuthenticatingRealm
org.apache.shiro.realm.AuthorizingRealm

当然,也有简单实现类(simple implementation),比如:

org.apache.shiro.authc.pam.ModularRealmAuthenticator

下面是书中提供的编写skeletal的例子:

public abstract class AbstractMapEntry<K, V> implements Map.Entry<K, V> {
// Primitive operations
public abstract K getKey(); public abstract V getValue(); // Entries in modifiable maps must override this method
public V setValue(V value) {
throw new UnsupportedOperationException();
} // Implements the general contract of Map.Entry.equals
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?, ?> arg = (Map.Entry) o;
return equals(getKey(), arg.getKey())
&& equals(getValue(), arg.getValue());
} private static boolean equals(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
} // Implements the general contract of Map.Entry.hashCode
@Override
public int hashCode() {
return hashCode(getKey()) ^ hashCode(getValue());
} private static int hashCode(Object obj) {
return obj == null ? 0 : obj.hashCode();
}
}

相对于提供一个实现类,编写一个skeletal实现有一些限制。
首先必须了解接口中哪些是最基本的行为,并将其实现留给子类实现,skeletal则负责接口中的其他方法(或者一个都不实现)或者其特征相关的实现。
另外,skeletal类的价值在于继承,稍不注意就可能因为继承而破坏封装性。
为继承而设计的类还需要提供相应的文档,比如哪些方法是self-use、类实现了Serializable或者Clonnable等...

除了可以提供基本实现这一"优势",抽象类还有一个优势就是:抽象类的变化比接口的变化更容易。
即,在后续版本中为抽象类增加方法比接口增加方法更容易。
在不破坏实现类的情况下,在一个公有接口中增加方法,这是不可能的(即便下面是skeletal类,但一直detect下去总会有实现类存在)。
因此,设计接口是个技术活,一旦定下来就别想再变了。

抽象类和接口之间的选择,某种角度上可以说是易扩展性和灵活性之间的选择。

Java - 接口还是抽象类的更多相关文章

  1. java 接口和抽象类的区别

    java 接口和抽象类的区别抽象类:1.含有抽象方法的类一定为抽象类,反过来抽象类,不一定含有抽象方法:2.抽象类必须用abstract来进行定义,抽象方法也必须用abstract来进行定义:3.抽象 ...

  2. 初探设计:Java接口和抽象类何时用?怎么用?

    今天犯了个错: “接口变动,伤筋动骨,除非你确定只有你一个人在用”.哪怕只是throw了一个新的Exception.哈哈,这是我犯的错误. 一.接口和抽象类 类,即一个对象. 先抽象类,就是抽象出类的 ...

  3. Java接口和抽象类有什么区别,哪些时候用接口,哪些时候用抽象类?

    Java接口和抽象类有什么区别,哪些时候用接口,哪些时候用抽象类? 2013-01-05 17:16:09|  分类: JAVA |  标签:java  |举报|字号 订阅     下面比较一下两者的 ...

  4. Java接口和抽象类的区别

    今天看到项目中,写了一个抽象类,里面有很多方法继承了这类,当调用这个接口时,采用的是这个抽象类去调方法的,当时一想,这个不就是我们说的Java的多态的特征: 继承:存在继承关系的子类和父类 重写:子类 ...

  5. java接口和抽象类

    关于接口 1.创建一个接口,需要使用interface关键字. 2.实现一个接口,需要使用implements关键字. 3.接口的成员属性都是静态常量(默认public static final). ...

  6. java接口与抽象类的区别

    接口可以是标志接口,里面没有任何常量和方法. 抽象类不一定必须有抽象方法,也可也没有方法,但含抽象方法的类必须被声明为抽象类. 在抽象层次结构中,Java接口在最上面,然后紧跟着抽象类,然后是一般类. ...

  7. Java接口和抽象类的实现方法

    一.java中的接口本质上是加约束的抽象类 //抽象类 public abstract class AExample { public abstract int add(int x,int y); p ...

  8. Java 接口和抽象类差别

    原文:http://blog.csdn.net/sunboard/article/details/3831823 1.概述 一个软件设计的好坏,我想非常大程度上取决于它的总体架构,而这个总体架构事实上 ...

  9. Java接口和抽象类的理解

    接口和抽象类的相同之处就是 都会有抽象方法 抽象方法就是一个没有方法体 等待继承的子类完成的方法 然而接口比较严格 它的方法必须是抽象方法且是公开的 抽象类 可以有自己的属性 和 实体方法 首相用面向 ...

  10. java接口与抽象类

    本片随笔讲讲java中接口与抽象类. 一,接口 1.什么是接口? 那在日常生活中接口是什么呢?就是两个对象之间进行连接的部分就是接口,就比如热水器与水管的接口一样,他可以确保不同的东西之间的顺利连接, ...

随机推荐

  1. @Configurable

    spring的一个注解,用来自动注入bean的注解,不需要通过BeanFactory去获取    

  2. ADB模块源码分析(二)——adb server的启动

    1. ADB Server的启动 前面我们讲到adb模块的源码在system/core/adb下面,通过查看Android.mk文件我们了解到这个adb 模块回编译生成连个可执行文件adb.adbd, ...

  3. docker的介绍以及常用命令

    一.docker的介绍 1. Docker是什么? Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚 ...

  4. ORM的查询操作

    查询的分类 class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() ...

  5. grunt 常用插件

    grunt-contrib-uglify:代码压缩 grunt-contrib-jshint:检查js拼写错误 csslint:检查css语法错误

  6. Java多线程——线程封闭

    线程封闭:当访问共享的可变数据时,通常需要同步.一种避免同步的方式就是不共享数据.如果仅在单线程内访问数据,就不需要同步,这种技术称为线程封闭(thread  confinement) 线程封闭技术一 ...

  7. elment 中 el-table 进行校验

    脑洞大开:什么是展示数据最好的方式呢,表格,写得又快,又清晰,又明显,那么就积累一些工作中表格经常使用到的东西. 第一步:效果图: 第二步:举个例子: <template> <div ...

  8. java的MethodHandle类详解

    一.总述   java7为间接调用方法提供了MethodHandle类,即方法句柄.可以将其看作是反射的另一种方式. 这是使用MethodHandle调用方法的一个例子: public class T ...

  9. numpy.argmax()

    numpy.argmax(a, axis=None, out=None) 返回沿轴axis最大值的索引 Parameters: a : array_like                      ...

  10. Maven 安装jar文件到本地repository

    Reference: https://maven.apache.org/general.html#importing-jars mvn install:install-file \ -Dfile=&l ...