Effective Java - 接口还是抽象类

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下去总会有实现类存在)。
因此,设计接口是个技术活,一旦定下来就别想再变了。

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

时间: 2024-10-26 02:29:20

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

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

Java接口和抽象类有什么区别,哪些时候用接口,哪些时候用抽象类? 2013-01-05 17:16:09|  分类: JAVA |  标签:java  |举报|字号 订阅 下面比较一下两者的语法区别:1.抽象类可以有构造方法,接口中不能有构造方法.2.抽象类中可以有普通成员变量,接口中没有普通成员变量3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法.4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然eclips

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

今天犯了个错: “接口变动,伤筋动骨,除非你确定只有你一个人在用”.哪怕只是throw了一个新的Exception.哈哈,这是我犯的错误. 一.接口和抽象类 类,即一个对象. 先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类).官方定义让人费解,但是记忆方法是也不错的 — 包含抽象方法的类叫做抽象类. 接口就是把抽象的深度更深,它就像用简短的非逻辑的一些规则表示类之间的关系.可以比作协议,比如通信使用的UDP/TCP协议等. 小结:类与接口是Java语言的基本抽象单元. 二.为什么有接口的两

浅谈Java接口和抽象类的区别

面向对象的设计,服用的重点其实应该是抽象层的复用,而不是具体某一个代码块的复用. 说到了抽象,就不得不提到Java接口和Java抽象类了,这也是我这里想要谈论的重点. Java接口和抽象类代表的就是抽象类型,就是我们需要提出的抽象层的具体表现.OOP面向对象编程,如果要提高程序的复用率,增加程序的可维护性,可扩展性,就必须是面向接口编程,面向抽象编程,正确地使用接口,抽象类这些有用的抽象类型作为你结构层次上的顶层. Java接口和Java抽象类有太多相似的地方,又有太多特别的地方,究竟在什么地方

Java 接口和抽象类小记

Java 接口和抽象类小记 @author ixenos 接口 1.接口没有构造函数,因为接口是不能被实例化的2.匿名对象如果使用接口的构造器也只是表示了一个协变的实现了接口的匿名对象3.接口里面的成员变量默认都是public static final类型的.必须被显示的初始化.4.接口里面的方法默认都是public abstract类型的.隐式声明.5.接口不能实现另一个接口,但可以继承多个接口.6.类如果实现了一个接口,那么必须实现接口里面的所有抽象方法 抽象类 1.若果在父类中(这里就是你

Java 接口和抽象类区别

1.概述 一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构 合理时,你底层的具体实现需要考虑的就仅仅是一些算法和一些具体的业务实现了.当你需要再开发另一个相近的项目时,你以前的抽象层说不定还可以再次利用 .面对对象的设计,复用的重点其实应该是抽象层的复用,而不是具体某一个代码块的复用. 说到了抽象,我就不能不提到曾让我头痛的Java接口和Java抽象类了,这也是本文我想说的重点. 既然面向对象设计的重点在于

java接口和抽象类

(一)接口和抽象类 作用: 1 利于设计和实现分工: 2 体现代码的层次性: (二)钩子方法或者叫模板方法方式 概念:23中设计模式中的一种,在代码执行的中间阶段,加入钩子,使得我们根据需要调用子类的不同方法,就行我们日常生活中的插座一样,如果你插上去的是电饭煲,那当然这个钩子就充当了煮饭的作用:如果你用来冲热水袋,你们它就充当了加热取暖的作用,我们在我们的某一个功能实现的过程中加入这样的钩子,那么这就是模板方法模式,它可以根据子类对这个方法(比如对于奔跑这个方法,不同子类不一样)来动态调用不同

浅谈JAVA接口和抽象类

一.什么是抽象方法.抽象类 抽象方法: 1.背景(为什么要有抽象方法):有一些方法,一出生就是用来让别人继承重写使用的,自己完全没有去实现的必要,只用定义就可以了,于是JAVA里就专门将这种方法称为抽象方法. 2.定义:用abstract修饰符来声明一种方法,而且这种方法只有声明,没有实现. 例如: public abstract test();//只做了声明,没有实现,连"{}"都不写. 抽象类: 1.背景:因为抽象方法是定义在类里面的,抽象方法是只有声明没有实现的方法,即是残缺不全

Java 接口和抽象类--缺省模式

一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构 合理时,你底层的具体实现需要考虑的就仅仅是一些算法和一些具体的业务实现了.当你需要再开发另一个相近的项目时,你以前的抽象层说不定还可以再次利用 .面对对象的设计,复用的重点其实应该是抽象层的复用,而不是具体某一个代码块的复用. 说到了抽象,我就不能不提到曾让我头痛的Java接口和Java抽象类了,这也是本文我想说的重点. 既然面向对象设计的重点在于抽象,那J

Java接口和抽象类的理解

接口和抽象类的相同之处就是 都会有抽象方法 抽象方法就是一个没有方法体 等待继承的子类完成的方法 然而接口比较严格 它的方法必须是抽象方法且是公开的 抽象类 可以有自己的属性 和 实体方法 首相用面向对象的思想来理解,类其实就是将生活中客观存在的对象,将对象的属性和特性封装起来 而接口与类不同,它不可以被实例化(这里指单独将接口实例化),因为它不可以当作一个对象 接口可以被理解为一种技能,功能.比如一扇门门可以有防盗及这个技能也可以同时拥有报警的功能 也就是说 一个类可以实现多个接口 然而每个子