前言:
前几天面试的是面试官问了一个问题
接口和抽象类了解吧?
说一下 他们的分别用在什么场景下(就是让说一下我该如何选择使用接口还是抽象类)
我回答的不忍直视。
我事后问了一个同学,你给我来了一句那要看业务
我不知道看业务吗,我想知道的是啥业务用接口啥业务用抽象类
吐槽完毕开始正题
Java基础之接口与抽象类的区别
Java中接口和抽象类的定义语法分别为interface与abstract关键字。
抽象类: 在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体。抽象类的特点:
a、抽象类不能被实例化只能被继承;
b、包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;
c、抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;
d、一个子类继承一个抽象类,则子类必须实现父类抽象方法,否则子类也必须定义为抽象类;
e、抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。
接口:Java中接口使用interface关键字修饰,特点为:
a、接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);
b、接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;
c、一个类可以实现多个接口;
d、JDK1.8中对接口增加了新的特性:(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。
1.基本概念和声明
1.1抽象类
抽象类是特殊的类,只是不能被实例化,天生就是要被继承的;除此以外,具有类的其他特性;重要的是抽象类可以包括抽象方法,这是普通类所不能的。抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须覆盖它们。另外,抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖,如果不覆盖,则其派生类必须覆盖它们
抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。
一个简单的声明
public abstract class Door { //定义抽象类
private String brand;//品牌
private double price;//价格
public abstract void open();
public abstract void close();
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:
1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
2)抽象类不能用来创建对象;
3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
1.2接口
接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象。在Java中,定一个接口的形式如下:
public interface Alarm {
public static final String effect ="提醒/警告";//相当于常量
void alarm();
default void alarmMode()
{
System.out.println("叫");
};
}
接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法(特别提醒1.8后接口也有可以非抽象方法了
但是要用 default修饰的方法)。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
1.3 应用场景介绍
什么时候使用抽象类和接口?
-
如果拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
对用一类事物共有的方法可以定义成抽象方法场景: 如 AbstractQueuedSynchronize 这个抽象类 继承这个类的时候你不用考虑线程的插入排队和休眠 只需要考虑获取的实现就行 如果你是定义成接口的话 所有的 方法都需要你去自己实现 (那些CAS的方法和线程等待的方法是可以共用的)
-
如果想实现多重继承,那么必须使用接口。由于Java不支持多继承,即一个类 只能有一个超类。但是,可以实现多个接口,因此可以使用接口来解决它。
有以下一个场景 我想声明一个接口或者抽象类 但是要继承这个类子类的之类已经有父类 则个时候就不能在声明抽象类了(就算声明了你也用不了)
-
如果基本功能在不断改变,那么就需要使用抽象类,达到解耦目的。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
这种一般是在项目初期才会出现吧 因为开闭原则:软件实体应当对扩展开放,对修改关闭。 (在理想的状态下,当我们需要为一个软件系统增加新功能时, 只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。)
接口更多的是在系统架构设计方面发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用。例如,模板方法设计模式就是抽象类的一个典型应用,假设某个项目的所有HTTP请求都要用相同的方式进行权限判断、访问日志记录和异常处理,那么就可以定义一个抽象的基类,让所有的controller都继承这个抽象基类,在抽象基类的service方法中实现上述功能,在各个子类中只是完成各自的业务逻辑代码,伪代码如下:
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public abstract class BaseServlet extends HttpServlet {
public final void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// 记录访问日志
// 进行权限判断
}
protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
// 注意访问权限定义成protected,显得既专业,又严谨,因为它是专门给子类用的
}
class MyServlet1 extends BaseServlet {
protected void doService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// 本Servlet只处理的具体业务逻辑代码
}
}
基类方法中间的某段代码不确定,留给子类去实现。
1.4 举例说明
下面看一个网上流传最广泛的例子——门和警报——门都有open()和close( )两个动作,通过抽象类和接口来定义这个抽象概念。
public abstract class Door { //定义抽象类
private String brand;//品牌
private double price;//价格
public Door() {
}
public Door(String brand, double price) {
this.brand = brand;
this.price = price;
}
public abstract void open();//抽象开门方法
public abstract void close();//抽象关门方法
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
但是现在如果需要门具有报警alarm()的功能,那么该如何实现?下面提供两种思路:
- 将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;
- 将这三个功能都放在接口里面,需要用到报警功能的类就实现接口中的open()和close(),也许这个类根本就不具备open()和close()功能,比如火灾报警器。
从这里可以看出,Door的open()、 close()和alarm()根本就属于两个不同范畴内的行为,open()和close()属于门本身固有的行为特性,而alarm)属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。再设计一个报警门继承Door类和实现Alarm接口。
public interface Alarm {
public static final String effect ="提醒/警告";
void alarm();
default void alarmMode()
{
System.out.println("叫");
};
}
public abstract class Door { //定义抽象类
private String brand;//品牌
private double price;//价格
public Door() {
}
public Door(String brand, double price) {
this.brand = brand;
this.price = price;
}
public abstract void open();//抽象开门方法
public abstract void close();//抽象关门方法
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
public class SecurityDoor extends Door implements Alarm {
public SecurityDoor() {
}
public SecurityDoor(String brand, double price) {
super(brand, price);
}
@Override
public void open() {
System.out.println("64位加密密码开启");
}
@Override
public void close() {
System.out.println("三层防盗依次关闭");
}
@Override
public void alarm() {
System.out.println("默认开启报警");
}
}
1.5 常见问题
以下来自网图