持续创造,加速成长!这是我参与「日新方案 10 月更文应战」的第20天,点击查看活动详情

前言

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 言语在接口层面的演进和相应程序规划完成,最终回忆并实践了面向对象规划的根本准则,希望对你有所协助。

上篇精讲:【JAVA】文件拷贝方式