文章目录
抽象类和抽象方法
抽象类
- 抽象类:用 abstract 修饰的类,不能被实例化。它可以包含抽象方法(没有方法体)和具体方法。
- 不能被实例化(不能 new 对象);
- 虽然不能直接 new 抽象类,但可以通过匿名内部类的方式快速实现并创建对象;
- 可以包含抽象方法(没有方法体)和具体方法(有方法体);
- 可以定义构造方法,供子类调用;
- 可以有成员变量、常量、静态成员等。
java
public abstract class Animal {
private String name; // 普通成员变量
public static final String CATEGORY = "动物界"; // 常量(静态+final)
private static int totalCount = 0; // 静态变量,统计创建的动物总数
// 构造方法 ,供子类调用,初始化名称,并增加计数
public Animal(String name) {
this.name = name;
totalCount++; // 每创建一个动物,计数加1
}
public abstract void sound(); // 抽象方法,没有方法体,必须由子类实现
// 具体方法
public void sleep() {System.out.println(name + "在睡觉");}
public String getName() { return name;}
// 静态方法
public static int getTotalCount() {return totalCount;}
public static void main(String[] args) {
// 匿名内部类,直接实现抽象方法
Animal dog = new Animal("旺财") {
@Override
public void sound() {
System.out.println(getName() + "汪汪叫");
}
};
dog.sound(); // 输出:旺财汪汪叫
dog.sleep(); // 输出:旺财在睡觉
// 创建普通子类对象(演示构造方法调用和静态成员)
Animal cat = new Animal("咪咪") {
@Override
public void sound() {
System.out.println(getName() + "喵喵叫");
}
};
cat.sound(); // 输出:咪咪喵喵叫
}
}
抽象方法
- 抽象方法:用 abstract 修饰的方法,只有声明,没有实现,必须由子类重写。
- 如果一个类中包含至少一个抽象方法,那么这个类必须被声明为抽象类;
- 抽象类可以没有抽象方法,即使一个类没有任何抽象方法,只要加了 abstract,它就不能被实例化。这通常用于阻止外界直接创建该类的对象;
- 与 final、private、static互斥:final修饰的方法不能被重写,private修饰的方法子类无法重写,static静态方法属于类级别也不能被重写。
java
public abstract void makeSound(); // 正确:没有花括号 {}
继承抽象类的规则
当一个类使用 extends 关键字继承抽象类时,它面临两条路:
- 成为具体类(推荐): 子类必须 重写(实现)父类中所有的抽象方法。
- 继续做抽象类: 如果子类无法或不愿意实现父类的所有抽象方法,那么子类自身也必须声明为 abstract 抽象类,把实现任务继续向下传递给它的子类。
java
public abstract class Animal {
public abstract void makeSound();
}
// 路线 1:实现所有抽象方法
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
// 路线 2:自身也声明为抽象类
public abstract class Bird extends Animal {
// 遗留 makeSound() 让后代实现
public abstract void fly();
}
抽象类的具体应用(模板方法模式)
模板方法模式:定义一个算法的骨架,将一些步骤延迟到子类实现,使得子类可以在不改变算法结构的情况下重定义某些步骤。如下例所示的数据加密处理,
java
public abstract class DataProcessor {
// 模板方法,定义算法骨架
public final void process() {
readData();
encryptData(); // 抽象方法,由子类实现
writeData();
}
private void readData() {
System.out.println("读取数据");
}
// 抽象方法,子类实现具体加密算法
protected abstract void encryptData();
private void writeData() {
System.out.println("写入数据");
}
}
class AESProcessor extends DataProcessor {
@Override
protected void encryptData() {
System.out.println("使用 AES 加密数据");
}
}
class DESProcessor extends DataProcessor {
@Override
protected void encryptData() {
System.out.println("使用 DES 加密数据");
}
}
public class TemplatePatternDemo {
public static void main(String[] args) {
DataProcessor aes = new AESProcessor();
aes.process(); // 执行 AES 加密流程
DataProcessor des = new DESProcessor();
des.process(); // 执行 DES 加密流程
}
}
接口
接口是 Java 中定义对象行为规范的核心机制。它主要用于规定实现类必须具备哪些能力(方法),而不负责具体的逻辑实现。面向接口编程是降低系统耦合度、提升代码扩展性的重要设计原则。
- 接口中的成员变量默认是 public static final,即常量;
- 接口中的方法默认是 public abstract(抽象方法);
- Java 8 后可以有 default、static 方法。对于default默认方法,实现类可以选择重写,也可以直接继承;static静态方法可以通过接口名直接调用。通常用于提供与接口相关的工具方法
- Java 9 后为了在接口内部的默认方法和静态方法之间共享代码,可以定义私有方法(private),它们不能被外部访问;
- 接口没有构造方法,不能实例化;
- 接口支持多继承(一个类可以实现多个接口);
- 一个类通过 implements 关键字实现接口,并必须重写接口中的所有抽象方法(除非该类是抽象类)
接口的定义与实现
java
//接口的定义
public interface MyInterface {
// 常量(默认 public static final)
int MAX_SIZE = 100;
// 抽象方法(默认 public abstract)
void doSomething();
// Java 8 开始:默认方法(default)
default void defaultMethod() {
System.out.println("默认实现");
}
// Java 8 开始:静态方法
static void staticMethod() {
System.out.println("静态方法");
}
// Java 9 开始:私有方法(供 default/static 方法内部使用)
private void privateHelper() {
System.out.println("私有辅助方法");
}
}
java
// 定义接口
interface Playable {
void play();
}
// 实现接口
class Piano implements Playable {
@Override
public void play() {
System.out.println("弹钢琴");
}
}
class Guitar implements Playable {
@Override
public void play() {
System.out.println("弹吉他");
}
}
// 多态演示
public class InterfaceDemo {
public static void main(String[] args) {
Playable p1 = new Piano(); // 接口引用指向实现类对象
Playable p2 = new Guitar();
p1.play(); // 输出:弹钢琴
p2.play(); // 输出:弹吉他
}
}
方法冲突
Java 8 引入的默认方法带来 了多继承中常见的方法冲突和菱形问题。
当一个类实现多个接口,而这些接口包含相同签名的默认方法时,编译器无法确定应该继承哪个默认实现,这就产生了方法冲突。例子中编译时会报错,类 C 不知道应该使用 A 的 hello 还是 B 的 hello。
java
interface A {
default void hello() {
System.out.println("Hello from A");
}
}
interface B {
default void hello() {
System.out.println("Hello from B");
}
}
class C implements A, B {
// 这里发生了方法冲突:A 和 B 都提供了 hello() 的默认实现
}
菱形问题
在 Java 接口中,由于一个类可以实现多个接口,而接口之间也可以继承(extends),因此也可能出现类似的菱形结构,导致方法冲突。
java
interface Top {
default void method() {
System.out.println("Top");
}
}
interface Left extends Top {
@Override
default void method() {
System.out.println("Left");
}
}
interface Right extends Top {
@Override
default void method() {
System.out.println("Right");
}
}
class Bottom implements Left, Right {
// 冲突:Left 和 Right 都重写了 Top 的 method,而且都有默认实现
// 形成了菱形:Bottom 通过 Left 和 Right 两条路径继承 method
}
解决方案
(1)Java使用类优先规则,如果类本身显式声明了方法(无论是抽象方法还是具体实现),则优先使用类中的方法,接口的默认方法被忽略。
java
class C implements A, B {
// 类自己实现了 hello,解决冲突
@Override
public void hello() {
System.out.println("Hello from C");
}
}
(2)Java 使用子接口优先规则,如果接口之间存在继承关系,那么"更具体"的接口(子接口)的默认方法优先于父接口的默认方法。也就是说,如果两个冲突的接口中,一个是另一个的子接口,则子接口的方法胜出。
java
interface Parent {
default void method() { System.out.println("Parent"); }
}
interface Child extends Parent {
default void method() { System.out.println("Child"); }
}
class MyClass implements Parent, Child {
// 这里 Child 是 Parent 的子接口,所以 Child.method 胜出
// 不需要在 MyClass 中重写
}
(3)Java 使用显式重写规则,如果上述规则无法解决冲突(例如两个接口没有继承关系),则类必须显式重写冲突的方法,并可以选择使用 接口名.super.方法名() 来调用某个特定接口的默认实现。
java
class C implements A, B {
@Override
public void hello() {
// 可以选择调用 A 的默认实现
A.super.hello();
// 或者调用 B 的默认实现
// B.super.hello();
// 或者自定义实现
System.out.println("My own implementation");
}
}
抽象类与接口的异同
相同点:都不能实例化,都支持多态,都可能包含抽象方法
不同点
- 抽象类用于表示"是什么"(is-a)的共性,强调代码复用和模板;接口用于定义"能做什么"(can-do)的规范,强调行为契约和多态;
- 抽象类可以有成员变量,接口在Java 8之前不能有成员变量,Java 8以后可以有静态变量(public static final)
- 一个类只能继承一个抽象类,一个类可以实现多个接口;
- 抽象类可以有构造方法供子类调用,接口没有构造方法;
- 抽象类可以包含抽象方法,也可以没有;接口所有方法默认 public abstract(Java 8 后 default/static 除外)
面试问题
1.抽象类能加final修饰吗?
不能,final 修饰类的表示这个类是最终形态,不能被继承。abstract 修饰类表示这个类是不完整的,必须被继承去实现具体逻辑。二者互斥。
2.既然 Java 8 之后接口也能写方法体(default 方法)了,那接口能完全替代抽象类吗?
不能。(1)接口内部只能有静态常量(public static final),不能有普通的成员变量。这意味着接口无法保存对象的状态数据。而抽象类可以有各种普通的成员变量来保存自身的状态。(2)接口没有构造器,无法参与对象的初始化过程;抽象类有构造器,可以强制要求子类在实例化时初始化特定的父类属性。
3.如何在实际开发中决定使用抽象类还是接口?
(1)需要共享状态或构造器时,优先用抽象类(如 Vehicle 抽象类拥有品牌、速度等属性,并提供 start() 模板方法)。(2)需要定义跨层级的行为规范时,优先用接口(如 Serializable、Comparable)(3)既需要状态又需要多实现时,可组合使用:抽象类实现接口,子类继承抽象类并补充具体实现(如 ArrayList 继承 AbstractList 并实现 List 接口)