抽象类和接口的区别
抽象类可以有方法体的方法,但接口没有(Java 8 以前)。
接口中的成员变量隐式为 static final
,但抽象类不是的。
复制 // 隐式的abstract
interface Coach {
// 隐式的public
void defend();
void attack();
}
接口是隐式抽象的,所以声明时没有必要使用 abstract
关键字;
接口的每个方法都是隐式抽象的,所以同样不需要使用 abstract
关键字;
抽象类是对一种事物的抽象 ,即对类抽象,继承抽象类的子类和抽象类本身是一种 is-a
的关系。而接口是对行为的抽象 。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类 Airplane,将鸟设计为一个类 Bird,但是不能将 飞行 这个特性也设计为类 ,因此它只是一个行为特性,并不是对一类事物的抽象描述。
此时可以将 飞行 设计为一个接口 Fly,包含方法 fly(),然后 Airplane 和 Bird 分别根据自己的需要实现 Fly 这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承 Airplane 即可,对于鸟也是类似的,不同种类的鸟直接继承 Bird 类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系 。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
接口是对类的某种行为的一种抽象,接口和类之间并没有很强的关联关系,举个例子来说,所有的类都可以实现 Serializable 接口,从而具有序列化的功能,但不能说所有的类和 Serializable 之间是 is-a
的关系。
接口定义
复制 public interface Electronic {
// 常量
String LED = "LED";
// 抽象方法
int getElectricityUse();
// 静态方法
static boolean isEnergyEfficient(String electtronicType) {
return electtronicType.equals(LED);
}
// 默认方法
default void printDescription() {
System.out.println("电子");
}
}
注意事项
接口中定义的变量会在编译的时候自动加上 public static final
修饰符
没有使用 private
、 default
或者 static
关键字修饰的方法是隐式抽象的 ,在编译的时候会自动加上 public abstract
修饰符。也就是说上例中的 getElectricityUse()
其实是一个抽象方法,没有方法体——这是定义接口的本意。
接口中允许定义 default
方法 也是从 Java 8 开始的,比如说上例中的 printDescription()
方法,它始终由一个代码块组成,为实现该接口而不覆盖该方法的类提供默认实现。
接口不允许直接实例化 ,需要定义一个类去实现接口,也因此接口不能是 final 的
复制 public class Computer implements Electronic {
public static void main(String[] args) {
new Computer();
}
@Override
public int getElectricityUse() {
return 0;
}
}
接口可以是空的 。最典型的例子就是 Serializable 接口,在 java.io
包下。
复制 public interface Serializable {
}
// Serializable 接口用来为序列化的具体实现提供一个标记,
// 也就是说,只要某个类实现了 Serializable 接口,那么它就可以用来序列化了。
// Java 的序列化是指,将对象转换成以字节序列的形式来表示,这些字节序中包含了对象的字段和方法。
// 序列化后的对象可以被写到数据库、写到文件,也可用于网络传输。
接口的抽象方法不能是 private、protected 或者 final
接口的作用
使某些实现类具有我们想要的功能 ,比如说,实现了 Cloneable 接口的类具有拷贝的功能,实现了 Comparable 或者 Comparator 的类具有比较功能。
Cloneable 和 Serializable 一样,都属于标记型接口,它们内部都是空的。实现了 Cloneable 接口的类可以使用 Object.clone()
方法,否则会抛出 CloneNotSupportedException。
复制 public class CloneableTest implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneableTest c1 = new CloneableTest();
CloneableTest c2 = (CloneableTest) c1.clone();
}
}
Java 原则上只支持单一继承,但通过接口可以实现多重继承的目的
复制 public interface Fly {
void fly();
}
public interface Run {
void run();
}
public class Pig implements Fly,Run{
@Override
public void fly() {
System.out.println("会飞的猪");
}
@Override
public void run() {
System.out.println("会跑的猪");
}
}
实现多态 。多态可以通过继承(extends
)的关系实现,也可以通过接口的形式实现父类引用指向子类对象
复制 public interface Shape {
String name();
}
public class Circle implements Shape {
@Override
public String name() {
return "圆";
}
}
public class Square implements Shape {
@Override
public String name() {
return "正方形";
}
}
复制 List<Shape> shapes = new ArrayList<>();
Shape circleShape = new Circle();
Shape squareShape = new Square();
shapes.add(circleShape);
shapes.add(squareShape);
for (Shape shape : shapes) {
System.out.println(shape.name());
}
接口的三种模式
在使用接口的时候,经常会用到三种模式,分别是策略模式、适配器模式和工厂模式。
策略模式
策略模式的思想是,针对一组算法,将每一种算法封装到具有共同接口的实现类中,接口的设计者可以在不影响调用者的情况下对算法做出改变。
复制 // 接口:教练
interface Coach {
// 方法:防守
void defend();
}
// 何塞·穆里尼奥
class Hesai implements Coach {
@Override
public void defend() {
System.out.println("防守赢得冠军");
}
}
// 德普·瓜迪奥拉
class Guatu implements Coach {
@Override
public void defend() {
System.out.println("进攻就是最好的防守");
}
}
public class Demo {
// 参数为接口
public static void defend(Coach coach) {
coach.defend();
}
public static void main(String[] args) {
// 为同一个方法传递不同的对象
defend(new Hesai());
defend(new Guatu());
}
}
Demo.defend()
方法可以接受不同风格的 Coach,并根据所传递的参数对象的不同而产生不同的行为,这被称为“策略模式”
适配器模式
适配器模式的思想是,针对调用者的需求对原有的接口进行转接。生活当中最常见的适配器就是HDMI线,可以同时发送音频和视频信号。适配器模式的示例如下:
复制 interface Coach {
void defend();
void attack();
}
// 抽象类实现接口,并置空方法
abstract class AdapterCoach implements Coach {
public void defend() {};
public void attack() {};
}
// 新类继承适配器
class Hesai extends AdapterCoach {
public void defend() {
System.out.println("防守赢得冠军");
}
}
public class Demo {
public static void main(String[] args) {
Coach coach = new Hesai();
coach.defend();
}
}
Coach 接口中定义了两个方法(defend()
和 attack()
),如果类直接实现该接口的话,就需要对两个方法进行实现。
如果我们只需要对其中一个方法进行实现的话,就可以使用一个抽象类作为中间件,即适配器(AdapterCoach),用这个抽象类实现接口,并对抽象类中的方法置空(方法体只有一对花括号),这时候,新类就可以绕过接口,继承抽象类,我们就可以只对需要的方法进行覆盖,而不是接口中的所有方法。
工厂模式
什么工厂生产什么,比如说宝马工厂生产宝马,奔驰工厂生产奔驰,A 级学院毕业 A 级教练,C 级学院毕业 C 级教练。示例如下:
复制 // 教练
interface Coach {
void command();
}
// 教练学院
interface CoachFactory {
Coach createCoach();
}
// A级教练
class ACoach implements Coach {
@Override
public void command() {
System.out.println("我是A级证书教练");
}
}
// A级教练学院
class ACoachFactory implements CoachFactory {
@Override
public Coach createCoach() {
return new ACoach();
}
}
// C级教练
class CCoach implements Coach {
@Override
public void command() {
System.out.println("我是C级证书教练");
}
}
// C级教练学院
class CCoachFactory implements CoachFactory {
@Override
public Coach createCoach() {
return new CCoach();
}
}
public class Demo {
public static void create(CoachFactory factory) {
factory.createCoach().command();
}
public static void main(String[] args) {
// 对于一支球队来说,需要什么样的教练就去找什么样的学院
// 学院会介绍球队对应水平的教练。
create(new ACoachFactory());
create(new CCoachFactory());
}
}