💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 抽象类和接口的区别 ### 抽象类 抽象类是用来捕捉子类的通用特性的 。它不能被实例化,只能被用作子类的超类。抽象类是被用来创建继承层级里子类的模板。以JDK中的GenericServlet为例: ~~~ public abstract class GenericServlet implements Servlet, ServletConfig, Serializable { // abstract method abstract void service(ServletRequest req, ServletResponse res); void init() { // Its implementation } // other method related to Servlet } ~~~ 当HttpServlet类继承GenericServlet时,它提供了service方法的实现: ~~~ public class HttpServlet extends GenericServlet { void service(ServletRequest req, ServletResponse res) { // implementation } protected void doGet(HttpServletRequest req, HttpServletResponse resp) { // Implementation } protected void doPost(HttpServletRequest req, HttpServletResponse resp) { // Implementation } // some other methods related to HttpServlet } ~~~ ### 接口 接口是抽象方法的集合。如果一个类实现了某个接口,那么它就继承了这个接口的抽象方法。这就像契约模式,如果实现了这个接口,那么就必须确保使用这些方法。接口只是一种形式,接口自身不能做任何事情。以Externalizable接口为例: ~~~ public interface Externalizable extends Serializable { void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; } ~~~ 当你实现这个接口时,你就需要实现上面的两个方法: ~~~ public class Employee implements Externalizable { int employeeId; String employeeName; @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { employeeId = in.readInt(); employeeName = (String) in.readObject(); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(employeeId); out.writeObject(employeeName); } } ~~~ ### 抽象类和接口的对比 | 参数 | 抽象类 | 接口 | | --- | --- | --- | | 默认的方法实现 | 它可以有默认的方法实现 接口完全是抽象的 | 它根本不存在方法的实现 | | 实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 | | 构造器 | 抽象类可以有构造器 | 接口不能有构造器 | | 与正常Java类的区别 | 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 | 接口是完全不同的类型 | | 访问修饰符 | 抽象方法可以有public、protected和default这些修饰符 | 接口方法默认修饰符是public。你不可以使用其它修饰符。 | | main方法 | 抽象方法可以有main方法并且我们可以运行它 | 接口没有main方法,因此我们不能运行它。 | | 多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 | | 速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 | | 添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 | 如果你往接口中添加方法,那么你必须改变实现该接口的类。 | ### 什么时候使用抽象类和接口 * 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。 * 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。 * 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。 * * * * * * ## 利用枚举实现单例模式 ### Java枚举基本用法 枚举的用法比较多,本文主要旨在介绍利用枚举实现单例模式的原理,所以这里也主要介绍一些相关的基础内容。 首先,枚举类似类,一个枚举可以拥有成员变量,成员方法,构造方法。先来看枚举最基本的用法: ~~~ enum Type{ A,B,C,D; } ~~~ 创建enum时,编译器会自动为我们生成一个继承自java.lang.Enum的类,我们上面的enum可以简单看作: ~~~ class Type extends Enum{ public static final Type A; public static final Type B; ... } ~~~ 对于上面的例子,我们可以把Type看作一个类,而把A,B,C,D看作类的Type的实例。 当然,这个构建实例的过程不是我们做的,一个enum的构造方法限制是private的,也就是不允许我们调用。 ### “类”方法和“实例”方法 上面说到,我们可以把Type看作一个类,而把A,B。。。看作Type的一个实例。同样,在enum中,我们可以定义类和实例的变量以及方法。看下面的代码: ~~~ enum Type{ A,B,C,D; static int value; public static int getValue() { return value; } String type; public String getType() { return type; } } ~~~ 在原有的基础上,添加了类方法和实例方法。我们把Type看做一个类,那么enum中静态的域和方法,都可以视作类方法。和我们调用普通的静态方法一样,这里调用类方法也是通过 Type.getValue()即可调用,访问类属性也是通过Type.value即可访问。 下面的是实例方法,也就是每个实例才能调用的方法。那么实例是什么呢?没错,就是A,B,C,D。所以我们调用实例方法,也就通过 Type.A.getType()来调用就可以了。 最后,对于某个实例而言,还可以实现自己的实例方法。再看下下面的代码: ~~~ enum Type{ A{ public String getType() { return "I will not tell you"; } },B,C,D; static int value; public static int getValue() { return value; } String type; public String getType() { return type; } } ~~~ 这里,A实例后面的{…}就是属于A的实例方法,可以通过覆盖原本的方法,实现属于自己的定制。 除此之外,我们还可以添加抽象方法在enum中,强制ABCD都实现各自的处理逻辑: ~~~ enum Type{ A{ public String getType() { return "A"; } },B { @Override public String getType() { return "B"; } },C { @Override public String getType() { return "C"; } },D { @Override public String getType() { return "D"; } }; public abstract String getType(); } ~~~ ### 枚举单例 有了上面的基础,我们可以来看一下枚举单例的实现方法: ~~~ class Resource{ } public enum SomeThing { INSTANCE; private Resource instance; SomeThing() { instance = new Resource(); } public Resource getInstance() { return instance; } } ~~~ 上面的类Resource是我们要应用单例模式的资源,具体可以表现为网络连接,数据库连接,线程池等等。 获取资源的方式很简单,只要 SomeThing.INSTANCE.getInstance() 即可获得所要实例。下面我们来看看单例是如何被保证的: 首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。 也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。 可以看到,枚举实现单例还是比较简单的,除此之外我们再来看一下Enum这个类的声明: ~~~ public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable ~~~ 可以看到,枚举也提供了序列化机制。某些情况,比如我们要通过网络传输一个数据库连接的句柄,会提供很多帮助。 最后借用 《Effective Java》一书中的话, `单元素的枚举类型已经成为实现Singleton的最佳方法。`