【JAVA基础面经】抽象类/方法与接口

文章目录


抽象类和抽象方法

抽象类

  • 抽象类:用 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 接口)

相关推荐
0xDevNull2 小时前
Java实现Redis延迟队列:从原理到高可用架构
java·开发语言·后端
糖炒栗子03262 小时前
Go 语言环境搭建与版本管理指南 (2026)
开发语言·后端·golang
于先生吖2 小时前
无人共享健身房 Java 后端源码 + 多端对接完整方案
java·开发语言
恼书:-(空寄2 小时前
Spring 事务失效的 8 大场景 + 原因 + 解决方案
java·后端·spring
cpp_learners2 小时前
银河麒麟V10+飞腾FT-2000/4处理器+QT源码静态编译5.14.2指南
开发语言·qt
野生技术架构师3 小时前
1000道互联网大厂Java岗面试原题解析(八股原理+场景题)
java·开发语言·面试
jiankeljx3 小时前
Java实战:Spring Boot application.yml配置文件详解
java·网络·spring boot
cyforkk3 小时前
Java 开源项目指南:如何规范地发布首个 GitHub Release
java·开源·github
qqty12173 小时前
Java进阶学习之路
java·开发语言·学习