从接口与抽象类说起

每当面试的时候,我总喜欢问一下应聘者这样一个问题:接口与抽象类有什么区别?这个问题看上去很简单,网上的答案也一搜一大把。然而,我根本不想要应聘者背出来的答案–一个对技术热衷的人,这个问题一定会反复思考过无数次,一定能说出自己的一些见解。然而,令我失望的是,我很少得倒令我满意的答案。写下这篇,分享一下我对接口和抽象类的认识。

表象

  • 接口用interface声明,而抽象类用class声明
  • 实现接口关键字 implements ,继承抽象类关键字 extends

这些都是表象的东西,是语言层面上对接口和抽象类做了一个区分

abstract 不能与 private、static、final或native并列修饰同一个方法

说到这里,我们就顺便看一下这几个关键字吧

public protected deafult(缺省)private

这几个关键字是作用域关键字

  • public 该方法或字段对所有用户开发

  • protected 该方法或字段对自己,同包下的类,子类开放

  • 缺省 该方法或字段对自己,同包下的类开放

  • private 该方法或字段仅对自己开放

ps:这里有两点可能会被有些人搞混
  • protected比默认缺省作用范围更广
  • protected方法和字段可以对同包下的类开放,有些人认为,被protected修饰了的,仅对自己和子类开放
final
  • final类不能被继承,没有子类,final类中的方法默认是final的
  • final方法不能被子类的方法覆盖,但可以被继承
  • final成员变量表示常量,只能被赋值一次,赋值后值不再改变
  • final不能用于修饰构造方法

接口的所有方法都是抽象的,也就是说,接口的方法都是被abstract隐式修饰的。所以接口方法不能出现tatic、final或native。而且接口的访问权限都是public的。

接口的字段都是 public static final 关键字修饰

  • 接口中的字段必须在声明的时候初始化

    这是由于,接口的字段都是被final隐式修饰的。

  • 不要用接口取代枚举

这个问题在之前的分享中提到过,这里不再赘述

接口定义类型

单继承与多实现

这个特性造成了接口的一个重要应用:用于定义类型。举例说明用抽象类定义类型的弊端。

比如我们定义一个歌手类型和一个歌曲作家类型:

public interface SongWriter {
void writeSong();
}   

public interface Singer {
void singSong();
}

如果一个人,既是一个歌手,又是一个作曲家,三里屯这边这样的人并不罕见,那么这个人我们可以这样定义。

public class SingerAndWriter implements Singer,SongWriter{
 @Override
 public void singSong() {

 }

 @Override
 public void writeSong() {

 }
}   

但是如果我们不小心把类型定义成抽象类,那么抽象类不支持多继承的规则将会限制一个歌手为自己写歌。

前段时间,和小伙伴讨论这个问题,小伙伴给出了一个解决方案。写一个SingerAndWriter,让他持有SongWriter和Singer的引用,这样想唱就唱,想写就写。但是这样的设计有一个很大的问题。本来SingerAndWriter is a SongWriter以及SingerAndWriter is a Singer。而采用小伙伴的这种方式的话,就成了SingerAndWriter has a SongWriter以及SingerAndWriter has a Singer。如果一旦这样做了,功能看上去实现了。但是某天我们要办一场音乐会,那么需要传递Singer作为入参。。。。。。。惊出一身冷汗!

接口的方法名不能重复,以防止一个类实现多个接口的时候出现问题

由于接口可以多实现,这就造成了,如果客户端实现的两个接口中有相同方法名的时候,会造成功能实现上的混乱甚至错误。而一旦发生了这样的问题,结果将是灾难性的。因为接口这种顶层的东西,往往是牵一发而动全身的。

抽象类为了复用

以下说明摘自effective java

  • 抽象类为了复用
    抽象类的一个特点就是,允许里面有非抽象方法。
    继承是一种实现代码重用的有力手段。但他并非永远是完成这项工作的最佳手段。只有当子类和超类之间确实存在子类型关系时,使用继承才是恰当的。即便如此,如果子类和超类处在不同包中,并且超类并不是为了继承而设计的,那么继承将会导致脆弱性。

  • 防止继承滥用,请使用装饰模式

与方法调用不同的是,继承打破了封装性。换句话说,子类依赖于其超类中特定功能的实现细节。超类的实现有可能会随着发行版本的不同而有所变化,子类可能会遭到破坏,即使它的代码完全没有改变。因而,子类必须跟着其超类的更新二演变,除非超类是专门为了扩展而设计的,并且有很好的文档说明。

解决办法:装饰模式。(“复合”)

新类与旧类实现同一个接口,新类持有旧类的一个引用。

新类中每个实例方法都可以调用包含现有类实例中对应的方法,并返回他的结果。这样得到的类将会非常稳固,他不依赖于现有类的实现细节。即使现有类增加了新的方法,也不会影响新的类。

包装类几乎没什么缺点。需要注意的一点是,包装类不适合用在回调框架中;对象把自身的引用传递给其他对象,用于后续的调用“回调”。因为被包装起来的对象并不知道他外面的包装对象,所以它传递一个指向自身的引用(this),回调时避开了外面的包装对象。这被称为SELF问题。

有些人担心转发方法调用所带来的性能影响,或者包装对象导致的内存占用。在实践中,这两者都不会造成很大的影响。编写转发方法倒是有些琐碎,但是只需要给每个接口编写一次构造器,转发类则可以通过包含接口的包替你提供。

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值