Effective Java笔记(20)接口优于抽象类

这篇具有很好参考价值的文章主要介绍了Effective Java笔记(20)接口优于抽象类。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        Java提供了两种机制,可以用来定义允许多个实现的类型:接口和抽象类。自从Java 8为继承引入了缺省方法( default method),这两种机制都允许为某些实例方法提供实现。主要的区别在于,为了实现由抽象类定义的类型,类必须成为抽象类的一个子类。因为Java只允许单继承,所以用抽象类作为类型定义受到了限制。任何定义了所有必要的方法并遵守通用约定的类,都允许实现一个接口,无论这个类是处在类层次结构中的什么位置。

        现有的类可以很容易被更新,以实现新的接口。如果这些方法尚不存在,你所需要做的就只是增加必要的方法,然后在类的声明中增加一个implements子句。例如,当Comparable、Iterable 和Autocloseable接口被引入Java平台时,更新了许多现有的类,以实现这些接口。一般来说,无法更新现有的类来扩展新的抽象类。如果你希望两个类扩展同一个抽象类,就必须把抽象类放到类型层次( type hierarchy) 的高处,这样它就成了那两个类的一个祖先。遗憾的是,这样做会间接地伤害到类层次,迫使这个公共祖先的所有后代类都扩展这个新的抽象类,无论它对于这些后代类是否合适。

        接口是定义mixin (混合类型)的理想选择。不严格地讲,mixin 类型是指:类除了实现它的“基本类型”之外,还可以实现这个mixin类型,以表明它提供了某些可供选择的行为。例如Comparable是一个mixin接口,它允许类表明它的实例可以与其他的可相互比较的对象进行排序。这样的接口之所以被称为mixin,是因为它允许任选的功能可被混合到类型的主要功能中。抽象类不能被用于定义mixin,同样也是因为它们不能被更新到现有的类中:类不可能有一个以上的父类,类层次结构中也没有适当的地方来插入mixin。

        接口允许构造非层次结构的类型框架。类型层次对于组织某些事物是非常合适的,但是其他事物并不能被整齐地组织成一个严格的层次结构。例如,假设我们有一个接口代表一个singer (歌唱家),另一个接口代表一个songwriter (作曲家):
 

public interface Singer {
    AudioClip sing(Song s);
}
public interface Songwriter {
    Song compose(int chartPosition);
}

        在现实生活中,有些歌唱家本身也是作曲家。因为我们使用了接口而不是抽象类来定义这些类型,所以对于单个类而言,它同时实现Singer和Songwriter是完全允许的。实际上,我们可以定义第三个接口,它同时扩展Singer和Songwriter,并添加一些适合于这种组合的新方法:

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

        也许并非总是需要这种灵活性,但是一旦这样做了,接口可就成了救世主。另外一种做法是编写一个臃肿( bloated)的类层次,对于每一-种要被支持的属性组合,都包含一个单独的类。如果在整个类型系统中有n个属性,那么就必须支持2”种可能的组合。这种现象被称为“组合爆炸”(combinatorialexplosion)。类层次臃肿会导致类也臃肿,这些类包含许多方法,并且这些方法只是在参数的类型上有所不同而已,因为类层次中没有任何类型体现了公共的行为特征。

        通过第18条中介绍的包装类(wrapperclass)模式,接口使得安全地增强类的功能成为可能。如果使用抽象类来定义类型,那么程序员除了使用继承的手段来增加功能,再没有其他的选择了。这样得到的类与包装类相比,功能更差,也更加脆弱。

        当一个接口方法根据其他接口方法有了明显的实现时,可以考虑以缺省方法的形式为程序员提供实现协助。通过缺省方法可以提供的实现协助是有限的。虽然许多接口都定义了object方法的行为,如equals和hashCode,但是不允许给它们提供缺省方法。而且接口中不允许包含实例域或者非公有的静态成员(私有的静态方法除外)。最后一点,无法给不受你控制的接口添加缺省方法。

        但是,通过对接口提供一个抽象的骨架实现( skeletal implementation)类,可以把接口和抽象类的优点结合起来。接口负责定义类型,或许还提供一些缺省方法,而骨架实现类则负责实现除基本类型接口方法之外,剩下的非基本类型接口方法。扩展骨架实现占了实现接口之外的大部分工作。这就是模板方法。

        按照惯例,骨架实现类被称为AbstractInterface,这里的Interface是指所实现的接口的名字。例如,Collections Framework为每个重要的集合接口都提供了一个骨架实现,包括AbstractCollection、AbstractSet、AbstractList和AbstractMap。将它们称作SkeletalCollection、SkeletalSet、 SkeletalList 和SkeletalMap也是有道理的,但是现在Abstract的用法已经根深蒂固。如果设计得当,骨架实现(无论是单独一个抽象类,还是接口中唯-包含的缺省方法)可以使程序员非常容易地提供他们自己的接口实现。例如,下面是一个静态工厂方法,除AbstractList之外,它还包含了一个完整的、功能全面的List实现:

static List<Integer> intArrayAsList(int[] a) {
    Objects. requireNonNull(a);
    return new AbstractList<>() {
        @Override 
        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;
        }
        @Override 
        public int size() {
            return a.length;
        }
    };
}

        如果想知道一个List实现应该为你完成哪些工作,这个例子就充分演示了骨架实现的强大功能。顺便提一下,这个例子是个Adapter,它允许将int数组看作Integer实例的列表。由于在int值和Integer实例之间来回转换需要开销,它的性能不会很好。注意,这个实现采用了匿名类(anonymous class)的形式。

        骨架实现类的美妙之处在于,它们为抽象类提供了实现上的帮助,但又不强加“抽象.类被用作类型定义时”所特有的严格限制。对于接口的大多数实现来讲,扩展骨架实现类是个很显然的选择,但并不是必须的。如果预置的类无法扩展骨架实现类,这个类始终能手工实现这个接口。同时,这个类本身仍然受益于接口中出现的任何缺省方法。此外,骨架实现类仍然有助于接口的实现。实现了这个接口的类可以把对于接口方法的调用转发到一个内部私有类的实例上,这个内部私有类扩展了骨架实现类。这种方法被称作模拟多重继承( simulated multiple inheritance),它与第18条中讨论过的包装类模式密切相关。这项技术具.有多重继承的绝大多数优点,同时又避免了相应的缺陷。

        编写骨架实现类相对比较简单,只是过程有点乏味。首先,必须认真研究接口,并确定哪些方法是最为基本的,其他的方法则可以根据它们来实现。这些基本方法将成为骨架实现类中的抽象方法。接下来,在接口中为所有可以在基本方法之.上直接实现的方法提供缺省方法,但要记住,不能为object方法(如equals和hashCode)提供缺省方法。如果基本方法和缺省方法覆盖了接口,你的任务就完成了,不需要骨架实现类了。否则,就要编写一个类,声明实现接口,并实现所有剩下的接口方法。这个类中可以包含任何非公有的域,以及适合该任务的任何方法。

        以Map.Entry接口为例,举个简单的例子。明显的基本方法是getKey、getValue和(可选的) setValue。接口定义了equals和hashCode的行为,并且有一个明显的toString实现。由于不允许给object方法提供缺省实现,因此所有实现都放在骨架实现类中:

public abstract class AbstractMapEntry<K, V>
        implements Map.Entry<K,V> {

    @Override
    public V setValue(V value) {
        throw new UnsupportedOperationException();
    }
    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry) o;
        return Objects.equals(e.getKey(), getKey())
                && Objects.equals(e.getValue(), getValue());
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    }
    @Override
    public String toString() {
        return getKey() + "=" + getValue();
    }
}

        注意,这个骨架实现不能在Map.Entry接口中实现,也不能作为子接口,因为不允许缺省方法覆盖object方法,如equals、hashCode和toString。因为骨架实现类是为了继承的目的而设计的,所以应该遵从第19条中介绍的所有关于设计和文档的指导原则。为了简洁起见,上面例子中的文档注释部分被省略掉了,但是对于骨架实现类而言,好的文档绝对是非常必要的,无论它是否在接口或者单独的抽象类中包含了缺省方法。

        骨架实现上有个小小的不同,就是简单实现( simple implementation), AbstractMap.SimpleEntry就是个例子。简单实现就像骨架实现一样,这是因为它实现了接口,并且是为了继承而设计的,但是区别在于它不是抽象的:它是最简单的可能的有效实现。你可以原封不动地使用,也可以看情况将它子类化。

        总而言之,接口通常是定义允许多个实现的类型的最佳途径。如果你导出了一个重要的接口,就应该坚决考虑同时提供骨架实现类。而且,还应该尽可能地通过缺省方法在接口中提供骨架实现,以便接口的所有实现类都能使用。也就是说,对于接口的限制,通常也限制了骨架实现会采用的抽象类的形式。文章来源地址https://www.toymoban.com/news/detail-622897.html

到了这里,关于Effective Java笔记(20)接口优于抽象类的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JAVA-抽象类和接口

    文章目录 前言 1.1抽象类的概念 1. 2抽象类的语法 1.3抽象类的特性 1.3.1抽象类不能直接实例化对象 1.3.2抽象方法不能被private,final和static修饰 1.3.3 抽象类的子类要么也是抽象类,要么重写所有抽象方法 1.4抽象类的作用 2.1 接口的概念 2.2 语法规则 2.3 接口的使用  2.4 接口的特性

    2024年02月05日
    浏览(50)
  • 【JAVA】抽象类与接口

    作者主页:paper jie_的博客 本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 本文录入于《JAVASE语法系列》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将javaSE基础知识一网打尽,希望可以帮到读者们哦。 其他专栏

    2024年02月09日
    浏览(34)
  • Effective Java笔记(29)优先考虑泛型

            一般来说 ,将集合声 明参数化,以及使用 JDK 所提供的泛型方法,这些都不太困难 。编写自己的泛型会比较困难一些,但是值得花些时间去学习如何编写 。         以简单的(玩具)堆校实现为例 :         这个类应该先被参数化,但是它没有,我们可

    2024年02月13日
    浏览(32)
  • 【JAVA】抽象类与接口--下

    ⭐ 作者:小胡_不糊涂 🌱 作者主页:小胡_不糊涂的个人主页 📀 收录专栏:浅谈Java 💖 持续更文,关注博主少走弯路,谢谢大家支持 💖 在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。 通过类来表示一组动

    2024年02月11日
    浏览(30)
  • Java抽象类和接口【超详细】

    在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的, 如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类 。 比如: 在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际

    2024年02月14日
    浏览(46)
  • Effective Java笔记(30)优先考虑泛型方法

            正如类可以从泛型中受益一般 ,方法也一样。静态工具方法尤其适合于泛型化 。 Collections 中的所有“算法”方法(例如 binarySearch 和 sort )都泛型化了 。         编写泛型方法与编写泛型类型相类似 。 例如下面这个方法,它返回两个集合的联合 :      

    2024年02月13日
    浏览(31)
  • 【Java SE】 带你走近Java的抽象类与接口

    🌹🌹🌹【JavaSE】专栏🌹🌹🌹 🌹🌹🌹个人主页🌹🌹🌹 🌹🌹🌹上一篇文章🌹🌹🌹 上一章:【Java SE】 带你走近Java的抽象类与接口 下一章:【Java SE】 带你在String类世界中遨游!!! 上一篇文章我们讲了Java中的两大特性:继承和多态,在多态中父类的方法被子类重

    2024年02月05日
    浏览(39)
  • Java中抽象类和接口的区别

    定义上来看,被abstract修饰的类称为抽象类。被abstract修饰的方法称为抽象方法。当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类.例: 特征: 抽象类不可以实例化,可以被继承,可以看作是对类的进一

    2024年02月16日
    浏览(39)
  • Java中抽象类和接口的区别?

    抽象类是对具体概念的抽象 抽象类本质是为了继承 只能被public或默认修饰 行为层面抽象出来抽象方法 抽象类的注意事项 抽象类不可以被直接实例化 抽象类中可以存在构造方法 抽象类可以存在普通方法 抽象方法的注意 抽象方法必须定义在抽象类中 仅声明 实现需要交给子

    2023年04月26日
    浏览(82)
  • Effective Java笔记(5)优先考虑依赖注入来引用资源

            有许多类会依赖一个或多个底层的资源 。 例如,拼写检查器需要依赖词典 。 因此,像下面这样把类实现为静态工具类的做法并不少见(详见第 4 条):         同样地,将这些类实现为 Singleton 的做法也并不少见(详见第 3 条) :         以上两种方法

    2024年02月15日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包