相同点
- 抽象类和接口都是对事物的
抽象
,都用于隐藏实现细节、定义接口、解耦系统 - 抽象类和接口都不能被
实例化
,都需要通过字类(实现类)来创建实例 - 抽象类和接口都包含
抽象方法
,且字类都需要去实现这些抽象方法。接口的抽象方法是隐式的,不用写关键字abstract
- 抽象类和接口都可以被
扩展
,抽象类可以被子类继承(extends
),接口可以被实现类实现(implement
) - 抽象类和接口都是
多态
的一种实现,也就是说子类(实现类)在创建对象的时候,可以用父类(接口)的类型进行接收
区别
语法层面的区别
-
关键词:
- 抽象类用
abstract
关键词定义 - 接口用
interface
关键词定义
- 抽象类用
-
继承与实现
- 抽象类可以继承另一个抽象类或普通类,一个类只能继承一个抽象类(单继承)
- 接口可以继承多个接口,一个类可以实现多个接口(多继承)
-
方法
- 抽象类可以包含抽象方法(无方法体)、普通方法(有方法体)、方法可以是任意访问符号
- jdk1.8之前,接口中只能有抽象方法(无方法体,可以不写abstract关键词),1.8之后可以有带方法体的default方法和static方法
-
字段(成员变量)
- 抽象类可以定义是实例变量和静态变量,变量可以用任意权限修饰符修饰(
private
、protected
、public
) - 接口中只能定义常量,而且默认给你添加了
public static final
修饰符,且必须进行初始化
- 抽象类可以定义是实例变量和静态变量,变量可以用任意权限修饰符修饰(
-
构造器
- 抽象类可以有构造器,用于被字类调用以初始化字段
- 接口不能有构造器,也不可以直接创建对象
-
设计目的
- 抽象类表达的是
is a
的关系,用于定义通用的特性(属性+行为),并提供部分实现(比如Animal
定义共有字段name
,共有方法eat()
)。 - 接口表达的是
has a
的关系,用于定义行为能力的扩展(比如Flyable
表示飞行能力,Swimmable
表示游泳能力)。
- 抽象类表达的是
设计层面的区别
抽象类是对一种事物的抽象,即对类的抽象,继承抽象类的子类和抽象类本身是一种is a
的关系。而接口是对行为的抽象,实现类与接口属于是hash a
的关系。抽象类是对整个类的整体抽象,包括属性和行为,但是接口是一种对类的局部抽象,只对行为进行抽象。
举个例子,飞机和鸟是不同的事物,他们都有一个共同的行为:会飞。所以你就可以把飞机和鸟分别定义一个抽象类:Airplane
、Bird
。把他们都会的能力定义一个接口:Fly。
战斗飞机和民用飞机都属于飞机这一种事物,所以这俩字类可以继承Airplane
抽象类,然后去实现Fly
接口。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
接口是对类的某种行为的一种抽象,接口和类之间并没有很强的关联关系,举个例子来说,所有的类都可以实现 Serializable
接口,从而具有序列化的功能,但不能说所有的类和 Serializable 之间是 is-a
的关系。
抽象类作为很多子类的父类,它是一种模板式设计
。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过 ppt 里面的模板,如果用模板 A 设计了 ppt B 和 ppt C,ppt B 和 ppt C 公共的部分就是模板 A 了,如果它们的公共部分需要改动,则只需要改动模板 A 就可以了,不需要重新对 ppt B 和 ppt C 进行改动。
而辐射式设计
,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
抽象类的使用场景
第一种场景:代码复用
当我们希望一些通用的功能被多个子类复用的时候,就可以使用抽象类。比如说,AbstractPlayer 抽象类中有一个普通的方法 sleep()
,表明所有运动员都需要休息,那么这个方法就可以被子类复用。
abstract class AbstractPlayer {
public void sleep() {
System.out.println("运动员也要休息而不是挑战极限");
}
}
子类 BasketballPlayer 继承了 AbstractPlayer 类:
class BasketballPlayer extends AbstractPlayer {
}
也就拥有了 sleep()
方法。BasketballPlayer 的对象可以直接调用父类的 sleep()
方法:
BasketballPlayer basketballPlayer = new BasketballPlayer();
basketballPlayer.sleep();
子类 FootballPlayer 继承了 AbstractPlayer 类:
class FootballPlayer extends AbstractPlayer {
}
也拥有了 sleep()
方法,FootballPlayer 的对象也可以直接调用父类的 sleep()
方法:
FootballPlayer footballPlayer = new FootballPlayer();
footballPlayer.sleep();
这样是不是就实现了代码的复用呢?
第二种场景:接口扩展
当我们需要在抽象类中定义好 API,然后在子类中扩展实现的时候就可以使用抽象类。比如说,AbstractPlayer 抽象类中定义了一个抽象方法 play()
,表明所有运动员都可以从事某项运动,但需要对应子类去扩展实现,表明篮球运动员打篮球,足球运动员踢足球。
abstract class AbstractPlayer {
abstract void play();
}
BasketballPlayer 继承了 AbstractPlayer 类,扩展实现了自己的 play()
方法。
public class BasketballPlayer extends AbstractPlayer {
@Override
void play() {
System.out.println("我是张伯伦,我篮球场上得过 100 分,");
}
}
FootballPlayer 继承了 AbstractPlayer 类,扩展实现了自己的 play()
方法。
public class FootballPlayer extends AbstractPlayer {
@Override
void play() {
System.out.println("我是C罗,我能接住任意高度的头球");
}
}
代码示例
加入现在有一个1.txt
文本文件,里面每一行都是一个英文单词,现在需要使用大写和小写两种模式进行内容读取和输出。
-
1.txt
文件中的内容hello word
-
首先定义抽象类
AbstractReader.java
,并在里面定义属性、普通方法、抽象方法package com.cryptogram.abstractDemo; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /** * 定义一个抽象类 * 用于定义内容读取的公共方法和规范 */ abstract public class AbstractReader { /** * 实例属性,可用任意权限字符修饰 */ public String filePath; /** * 构造方法传入文件路径 * 字类需要写构造方法,并用super传递filePath参数 * @param filePath 待读取文件路径 */ public AbstractReader(String filePath){ this.filePath = filePath; } /** * 普通方法,含方法体 * 内容解析 */ public void parse(){ try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { String line; while ((line = br.readLine()) != null) { // 逐行读取 // printPattern需要由字类扩展定义 System.out.println(printPattern(line)); } } catch (IOException e) { e.printStackTrace(); } } /** * 抽象方法 * 定义输出模式:大写输出还是小写输出 * @param str 待输出的原内容 * @return 格式化后的内容 */ public abstract String printPattern(String str); }
-
定义大写输出的类
UpperReader.java
,并继承抽象类package com.cryptogram.abstractDemo; public class UpperReader extends AbstractReader { public UpperReader(String filePath){ super(filePath); } @Override public String printPattern(String str){ return str.toUpperCase(); } }
-
定义小写输出的类
LowerReader.java
,并继承抽象类package com.cryptogram.abstractDemo; public class LowerReader extends AbstractReader { public LowerReader(String filePath){ super(filePath); } @Override public String printPattern(String str){ return str.toLowerCase(); } }
-
实际应用
package com.cryptogram.abstractDemo; public class App { public static void main(String[] args) { String filePath = "E:\\Projects\\04-LearningProjects\\LearningDemos\\src\\main\\java\\com\\mengxiangdong\\cryptogram\\abstractDemo\\1.txt"; AbstractReader lowerReader = new LowerReader(filePath); lowerReader.parse(); System.out.println("========="); AbstractReader upperReader = new UpperReader(filePath); upperReader.parse(); } }
-
输出
hello word ========= HELLO WORD
《Java抽象类和接口的区别》留言数:0