谈谈接口和抽象类有什么区别? 默认jdk8以前
典型回答
接口和抽象类是Java面向对象设计的两个基础机制。
接口是对行为的抽象,它是抽象方法的集合,利用接口可以达到API定义和实现分离的目的。接口,不能实例化;不能包含任何非常量成员,任何feld都是隐含着public static
fnal的意义;同时,没有非静态方法实现,也就是说要么是抽象方法,要么是静态方法(jdk8)。Java标准类库中,定义了非常多的接口,比如java.util.List。
抽象类是不能实例化的类,用abstract关键字修饰class,其目的主要是代码重用。除了不能实例化,形式上和一般的Java类并没有太大区别,可以有一个或者多个抽象方法,也可
以没有抽象方法。抽象类大多用于抽取相关Java类的共用方法实现或者是共同成员变量,然后通过继承的方式达到代码复用的目的。Java标准库中,比如collection框架,很多通用
部分就被抽取成为抽象类,例如java.util.AbstractList。
Java类实现interface使用implements关键词,继承abstract class则是使用extends关键词,我们可以参考Java标准库中的ArrayList。
接口vs抽象类vs类
- 支持多重继承:接口支持;抽象类不支持;类不支持;
- 支持抽象函数:接口语义上支持;抽象类支持;类不支持;
- 允许函数实现:接口不允许;抽象类支持;类允许;
- 允许实例化:接口不允许;抽象类不允许;类允许;
- 允许部分函数实现:接口不允许;抽象类允许;类不允许。
- 定义的内容:接口中只能包括public抽象函数以及public static fnal常量;抽象类与类均无任何限制。
- 使用时机:当想要支持多重继承,或是为了定义一种类型请使用接口;当打算提供带有部分实现的“模板”类,而将一些功能需要延迟实现请使用抽象类;当你打算提供完整的具体实现请使用
类。
面向对象设计
面向对象的基本要素:封装、继承、多态。
封装的目的是隐藏事务内部的实现细节,以便提高安全性和简化编程。封装提供了合理的边界,避免外部调用者接触到内部的细节。我们在日常开发中,因为无意间暴露了细节导致
的难缠bug太多了,比如在多线程环境暴露内部状态,导致的并发修改问题。从另外一个角度看,封装这种隐藏,也提供了简化的界面,避免太多无意义的细节浪费调用者的精力。
继承是代码复用的基础机制,类似于我们对于马、白马、黑马的归纳总结。但要注意,继承可以看作是非常紧耦合的一种关系,父类代码修改,子类行为也会变动。在实践中,过度
滥用继承,可能会起到反效果。
多态,你可能立即会想到重写(override)和重载(overload)、向上转型。简单说,重写是父子类中相同名字和参数的方法,不同的实现;重载则是相同名字的方法,但是不同的
参数,本质上这些方法签名是不一样的。
S.O.L.I.D原则
- 单一职责(Single Responsibility),类或者对象最好是只有单一职责,在程序设计中如果发现某个类承担着多种义务,可以考虑进行拆分。
- 开关原则(Open-Close, Open for extension, close for modifcation),设计要对扩展开放,对修改关闭。换句话说,程序设计应保证平滑的扩展性,尽量避免因为新增同
类功能而修改已有实现,这样可以少产出些回归(regression)问题。 - 里氏替换(Liskov Substitution),这是面向对象的基本要素之一,进行继承关系抽象时,凡是可以用父类或者基类的地方,都可以用子类替换。
- 接口分离(Interface Segregation),我们在进行类和接口设计时,如果在一个接口里定义了太多方法,其子类很可能面临两难,就是只有部分方法对它是有意义的,这就破坏
了程序的内聚性。对于这种情况,可以通过拆分成功能单一的多个接口,将行为进行解耦。在未来维护中,如果某个接口设计有变,不会对使用其他接口的子类构成影响。 - 依赖反转(Dependency Inversion),实体应该依赖于抽象而不是实现。也就是说高层次模块,不应该依赖于低层次模块,而是应该基于抽象。实践这一原则是保证产品代码之
间适当耦合度的法宝。