前言
Java 是十分典型的面向对象语言,已经有一段时间,程序员终日把面向对象、设计模式挂在嘴边。尽管现在大家对这方面曾经不再那么狂热,然而不可否认,把握面向对象设计准则和技巧,是保障高质量代码的根底之一。
本篇博文的重点是,接口和抽象类有什么区别?
概述
接口和抽象类是 Java 面向对象设计的两个根底机制。
接口是对行为的形象,它是形象办法的汇合,利用接口能够达到 API 定义和实现拆散的目标。接口,不能实例化;不能蕴含任何十分量成员,任何 field 都是隐含着 public static final 的意义;同时,没有非静态方法实现,也就是说要么是形象办法,要么是静态方法。Java 规范类库中,定义了十分多的接口,比方 java.util.List。
抽象类是不能实例化的类,用 abstract 关键字润饰 class,其目标次要是代码重用。除了不能实例化,模式上和个别的 Java 类并没有太大区别,能够有一个或者多个形象办法,也能够没有形象办法。抽象类大多用于抽取相干 Java 类的共用办法实现或者是独特成员变量,而后通过继承的形式达到代码复用的目标。Java 规范库中,比方 collection 框架,很多通用局部就被抽取成为抽象类,例如 java.util.AbstractList。
Java 类实现 interface 应用 implements 关键词,继承 abstract class 则是使 用 extends 关键词,咱们能够参考 Java 规范库中的 ArrayList。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//...
}
复制代码
注释
Java 相比于其余面向对象语言,如 C++,设计上有一些根本区别,比方 Java 不反对多继承。这种限度,在标准了代码实现的同时,也产生了一些局限性,影响着程序设计构造。Java 类能够实现多个接口,因为接口是形象办法的汇合,所以这是申明性的,但不能通过扩大多个抽象类来重用逻辑。
在一些状况下存在特定场景,须要形象出与具体实现、实例化无关的通用逻辑,或者纯调用关系的逻辑,然而应用传统的抽象类会陷入到单继承的困境。以往常见的做法是,实现由静态方法组成的工具类(Utils),比方 java.util.Collections。
构想,为接口增加任何形象办法,相应的所有实现了这个接口的类,也必须实现新增办法,否则会呈现编译谬误。对于抽象类,如果咱们增加非形象办法,其子类只会享受到能力扩大,而不必放心编译出问题。
接口的职责也不仅仅限于形象办法的汇合,其实有各种不同的实际。有一类没有任何办法的接口,通常叫作 Marker Interface,顾名思义,它的目标就是为了申明某些货色,比方咱们熟知的 Cloneable、Serializable 等。这种用法,也存在于业界其余的 Java 产品代码中。
从外表看,这仿佛和 Annotation 殊途同归,也的确如此,它的益处是简略间接。对于 Annotation,因为能够指定参数和值,在表达能力上要更弱小一些,所以更多人抉择应用 Annotation。
Java 8 减少了函数式编程的反对,所以又减少了一类定义,即所谓 functional interface,简略说就是只有一个形象办法的接口,通常倡议应用 @FunctionalInterface Annotation 来标记。Lambda 表达式自身能够看作是一类 functional interface,某种程度上这和面向对象能够算是两码事。咱们熟知的 Runnable、Callable 之类,都是 functional interface。
还有一点可能让人感到意外,严格说,Java 8 当前,接口也是能够有办法实现的!
从 Java 8 开始,interface 减少了对 default method 的反对。Java 9 当前,甚至能够定义 private default method。Default method 提供了一种二进制兼容的扩大已有接口的方法。比方,咱们熟知的 java.util.Collection,它是 collection 体系的 root interface,在 Java 8 中增加了一系列 default method,次要是减少 Lambda、Stream 相干的性能。我在专栏后面提到的相似 Collections 之类的工具类,很多办法都适宜作为 default method 实现在根底接口外面。
你能够参考上面代码片段:
public interface Collection<E> extends Iterable<E> {
/**
* Returns a sequential Stream with this collection as its source
* ...
**/
default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);
}
}
复制代码
面向对象设计
咱们肯定要分明面向对象的基本要素:封装、继承、多态。
封装的目标是暗藏事务外部的实现细节,以便进步安全性和简化编程。封装提供了正当的边界,防止内部调用者接触到外部的细节。咱们在日常开发中,因为无意间裸露了细节导致的难缠 bug 太多了,比方在多线程环境裸露外部状态,导致的并发批改问题。从另外一个角度看,封装这种暗藏,也提供了简化的界面,防止太多无意义的细节节约调用者的精力。
继承是代码复用的根底机制,相似于咱们对于马、白马、黑马的演绎总结。但要留神,继承能够看作是十分紧耦合的一种关系,父类代码批改,子类行为也会变动。在实践中,适度滥用继承,可能会起到反成果。
多态,你可能立刻会想到重写(override)和重载(overload)、向上转型。简略说,重写是父子类中雷同名字和参数的办法,不同的实现;重载则是雷同名字的办法,然而不同的参数,实质上这些办法签名是不一样的,为了更好阐明,请参考上面的样例代码:
public int doSomething() {
return 0;
}
// 输出参数不同,意味着办法签名不同,重载的体现
public int doSomething(List<String> strs) {
return 0;
}
// return 类型不一样,编译不能通过
public short doSomething() {
return 0;
}
复制代码
这里你能够思考一个小问题,办法名称和参数统一,然而返回值不同,这种状况在 Java 代码中算是无效的重载吗?答案是不是的,编译都会出错的。
进行面向对象编程,把握根本的设计准则是必须的,这里介绍最通用的局部,也就是所谓的 S.O.L.I.D 准则。
繁多职责(Single Responsibility),类或者对象最好是只有繁多职责,在程序设计中如果发现某个类承当着多种任务,能够思考进行拆分。
开关准则(Open-Close, Open for extension, close for modification),设计要对扩大凋谢,对批改敞开。换句话说,程序设计应保障平滑的扩展性,尽量避免因为新增同类性能而批改已有实现,这样能够少产出些回归(regression)问题。
里氏替换(Liskov Substitution),这是面向对象的基本要素之一,进行继承关系形象时,但凡能够用父类或者基类的中央,都能够用子类替换。
接口拆散(Interface Segregation),咱们在进行类和接口设计时,如果在一个接口里定义了太多办法,其子类很可能面临两难,就是只有局部办法对它是有意义的,这就毁坏了程序的内聚性。对于这种状况,能够通过拆分胜利能繁多的多个接口,将行为进行解耦。在将来保护中,如果某个接口设计有变,不会对应用其余接口的子类形成影响。
依赖反转(Dependency Inversion),实体应该依赖于形象而不是实现。也就是说高层次模块,不应该依赖于低层次模块,而是应该基于形象。实际这一准则是保障产品代码之间适当耦合度的法宝。
OOP 准则实际中的取舍
值得注意的是,古代语言的倒退,很多时候并不是齐全恪守后面的准则的,比方,Java 10 中引入了本地办法类型推断和 var 类型。依照,里氏替换准则,咱们通常这样定义变量:
List<String> list = new ArrayList<>();
复制代码
如果应用 var 类型,能够简化为
var list = new ArrayList<String>();
复制代码
然而,list 理论会被推断为 ArrayList<String>
ArrayList<String> list = new ArrayList<String>();
复制代码
实践上,这种语法上的便当,其实是加强了程序对实现的依赖,然而渺小的类型透露却带来了书写的便当和代码可读性的进步,所以,实际中咱们还是要依照得失利弊进行抉择,而不是一味得遵循原则。
后记
以上就是【JAVA】接口和抽象类有什么区别?的所有内容了;
对 Java 面向对象技术进行了梳理,比照了抽象类和接口,剖析了 Java 语言在接口层面的演进和相应程序设计实现,最初回顾并实际了面向对象设计的根本准则,心愿对你有所帮忙。