JAVA 接口、抽象类的关系和用处 详细解析
接口 - Java教程 - 廖雪峰的官方网站
一个 抽象类 如果实现了一个接口,可以只选择实现接口中的 部分方法(所有的方法都要有,可以一部分已经写具体,另一部分继续保留抽象),原因在于:
- 抽象类本身是 不完整的类,它可以有未实现的方法(即抽象方法),因此可以选择不完全实现接口。
- 由继承该抽象类的具体子类去完成未实现的方法。
这也是抽象类的一个强大功能,它在实现接口时,提供了一个“中间层次”,部分实现接口的行为,为具体的子类提供基础。
这里有两个箭头指向同一个类(例如 AbstractList
),是因为:
- 接口(如
List
)定义了行为规范:接口是用来定义类应该具有的功能和行为,例如List
定义了与列表相关的方法(如add()
,get()
等),但不提供具体实现。 - 抽象类(如
AbstractList
)提供了部分实现:抽象类用于实现接口的一部分行为,同时为具体类(如ArrayList
和LinkedList
)提供可以复用的代码。
AbstractList
和 List
的区别
-
List
接口:- 是一个完全抽象的接口,只定义了列表操作的规范。
- 方法如
add(E element)
,get(int index)
,remove(int index)
等都只是方法声明,没有实现。
-
AbstractList
抽象类:- 是一个抽象类,实现了
List
接口的大部分通用功能。 - 目的是让具体实现类(如
ArrayList
和LinkedList
)复用这些功能,只需实现特定的方法即可。例如,AbstractList
中实现了addAll()
,具体类无需再写这部分代码。
- 是一个抽象类,实现了
示例代码
假设你要实现一个自定义的列表,直接实现 List
和继承 AbstractList
的区别如下:
直接实现 List
接口
如果从零实现 List
接口,你需要定义接口中所有的方法(包括很多通用方法,比如 size()
和 addAll()
)。
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class CustomList<E> implements List<E> {
private Object[] elements = new Object[10];
private int size = 0;
@Override
public boolean add(E e) {
if (size == elements.length) {
Object[] newElements = new Object[size * 2];
System.arraycopy(elements, 0, newElements, 0, size);
elements = newElements;
}
elements[size++] = e;
return true;
}
@Override
public int size() {
return size;
}
@Override
public E get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index);
}
return (E) elements[index];
}
// 还需实现 List 中所有的方法,如 remove()、iterator() 等,工作量很大。
}
继承 AbstractList
抽象类
通过继承 AbstractList
,你只需实现一些关键方法,剩下的方法由 AbstractList
提供默认实现。
import java.util.AbstractList;
public class CustomList<E> extends AbstractList<E> {
private Object[] elements = new Object[10];
private int size = 0;
@Override
public E get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index);
}
return (E) elements[index];
}
@Override
public int size() {
return size;
}
@Override
public boolean add(E e) {
if (size == elements.length) {
Object[] newElements = new Object[size * 2];
System.arraycopy(elements, 0, newElements, 0, size);
elements = newElements;
}
elements[size++] = e;
return true;
}
// 不需要手动实现 addAll() 等通用方法,AbstractList 已提供默认实现。
}
运行示例
public class Main {
public static void main(String[] args) {
CustomList<String> list = new CustomList<>();
list.add("A");
list.add("B");
list.add("C");
System.out.println(list.get(1)); // 输出: B
System.out.println(list.size()); // 输出: 3
}
}
为什么 Java 集合框架中要设计接口和抽象类的这种关系?
1. 灵活性:接口用于定义行为规范
接口(如 List
)允许不同的实现方式,适配多种需求,例如:
ArrayList
:基于数组实现的列表,适合随机访问操作。LinkedList
:基于链表实现的列表,适合插入和删除操作。- 自定义列表:可以实现特定的逻辑,比如线程安全或固定容量。
2. 代码复用:抽象类减少重复代码
抽象类(如 AbstractList
)避免了在每个实现类中重复编写通用逻辑。例如:
size()
的计算逻辑。- 批量添加方法(
addAll()
)的实现。 - 迭代器(
iterator()
)的通用实现。
通过这种设计,新实现类只需关注特定的细节。
3. 多层次抽象设计
如下图中的设计:
- 接口层: 定义行为规范(如
List
,Collection
)。 - 抽象类层: 提供部分实现(如
AbstractList
,AbstractCollection
)。 - 具体类层: 提供特定实现(如
ArrayList
,LinkedList
)。
这种多层次设计提供了灵活性和代码复用的平衡。
问题 1:调用的时候执行的是接口的方法还是抽象类的方法?
调用的是 对象的实际实现类中的方法,而不是接口或抽象类本身。尽管我们通过 List
这样的接口来引用一个对象,但具体执行的代码取决于 对象的具体实现类。
假设我们写了一段代码:
List<String> list = new ArrayList<>();
list.add("Hello");
-
编译时看接口,运行时看实现类:
list
的编译时类型是List
,所以编译器只会允许你调用List
接口中声明的方法,比如add()
、remove()
等。- 但
list
的运行时类型是ArrayList
,所以具体执行的add()
方法是ArrayList
类中定义的实现。
-
接口 vs 抽象类:
List
是接口,定义了add()
的方法规范。AbstractList
是一个抽象类,部分实现了List
的规范,并提供了通用实现。- 但是:在
ArrayList
中,它直接继承了AbstractList
,并可能覆写了某些方法,所以最终调用的是ArrayList
的实现。
为什么我们总是见到 List
,而没有见过 AbstractList
?
-
AbstractList
是设计细节:AbstractList
是为具体实现类(如ArrayList
和LinkedList
)服务的,目的是 减少代码重复。- 它为实现类提供了一些通用功能,比如:
- 默认实现
addAll()
方法。 - 默认实现
iterator()
方法。
- 默认实现
- 但是,
AbstractList
是抽象的,不能直接使用,所以开发者不会直接实例化或引用它。
-
面向接口编程的原则:我们习惯通过接口(如
List
)去引用对象,这是面向接口编程的核心思想。
default
方法
-
- 在 接口 中,
default
方法允许有具体的实现,提供一个方法体。 - 抽象类 中不需要使用
default
关键字,因为抽象类本身可以包含普通的具体方法(带方法体)和抽象方法(没有方法体)。
- 在 接口 中,
-
为什么
default
方法存在于接口:- 原本接口中的方法必须全部是抽象的,这意味着接口升级时(比如增加新方法),所有实现这个接口的类都必须修改,去实现新增的方法。
- 为了兼容老代码,同时给接口增加新功能,Java 8 引入了
default
方法。default
方法是为了 在接口中提供默认实现,而不破坏已有的实现类。
-
抽象类和接口在具体方法上的区别:
- 抽象类的普通方法天然支持具体实现,不需要额外关键字。
- 接口中的
default
方法则是接口为了支持具体实现而引入的额外能力。
具体对比:抽象类和接口中的具体方法
特点 | 抽象类中的具体方法 | 接口中的 default 方法 |
---|---|---|
是否需要关键字 | 不需要,直接定义普通方法即可 | 需要使用 default 关键字 |
是否可以有具体实现 | 是的,普通方法都可以有实现 | 是的,default 方法允许提供具体实现 |
是否可以被覆写 | 可以,子类可以选择覆写抽象类中的普通方法 | 可以,子类可以选择覆写接口中的 default 方法 |
是否强制实现 | 不是,子类可以选择继承普通方法的实现或覆写它 | 不是,默认继承接口中的 default 方法 |
default
方法的实际意义
1. 向接口新增方法时的兼容性问题
假设你有一个接口 MyInterface
和两个实现类:
interface MyInterface {
void methodA();
}
class ClassA implements MyInterface {
@Override
public void methodA() {
System.out.println("ClassA: methodA");
}
}
class ClassB implements MyInterface {
@Override
public void methodA() {
System.out.println("ClassB: methodA");
}
}
如果你需要给 MyInterface
添加一个新方法 methodB
,所有的实现类(ClassA
和 ClassB
)都必须实现这个方法,否则代码无法编译。
2. 使用 default
方法解决兼容问题
在这种情况下,可以用 default
方法为新方法提供一个默认实现,从而避免修改所有实现类:
interface MyInterface {
void methodA();
// 新增一个 default 方法
default void methodB() {
System.out.println("Default implementation of methodB");
}
}
class ClassA implements MyInterface {
@Override
public void methodA() {
System.out.println("ClassA: methodA");
}
}
class ClassB implements MyInterface {
@Override
public void methodA() {
System.out.println("ClassB: methodA");
}
}
运行示例
public class Main {
public static void main(String[] args) {
MyInterface objA = new ClassA();
objA.methodA(); // 输出: ClassA: methodA
objA.methodB(); // 输出: Default implementation of methodB
MyInterface objB = new ClassB();
objB.methodA(); // 输出: ClassB: methodA
objB.methodB(); // 输出: Default implementation of methodB
}
}
如果某个实现类需要对 default
方法提供自定义实现,可以覆写它:
class ClassB implements MyInterface {
@Override
public void methodA() {
System.out.println("ClassB: methodA");
}
@Override
public void methodB() {
System.out.println("ClassB: Custom implementation of methodB");
}
}
运行后:
MyInterface objB = new ClassB();
objB.methodB(); // 输出: ClassB: Custom implementation of methodB
结合数据库任务的实际场景
在你的数据库任务中,default
方法可以为某些操作提供通用实现。例如:
接口定义
public interface DBOperations {
boolean createTable(String tableName, List<String> columns);
default boolean dropTable(String tableName) {
System.out.println("[OK] Dropped table: " + tableName);
return true;
}
}
实现类
具体类可以选择覆写或继承接口中的 default
方法或者覆写