Java 每日一刊(第14期):抽象类和接口

"抽象是所有能力的精髓。"

前言

这里是分享 Java 相关内容的专刊,每日一更。

本期将为大家带来以下内容:

  1. 抽象类
  2. 接口
  3. 抽象类和接口的区别
  4. 什么时候用抽象类,什么时候用接口
  5. 抽象类可以实现接口
  6. 接口中的常量其实是 public static final
  7. 标记接口(Marker Interface)
  8. 接口可以扩展多个接口
  9. 接口中的方法可以有多个默认实现来源

抽象类

抽象类是一种不能直接创建对象的类,它是一种模板,通常是为了给其他类用来继承的。抽象类可以包含两种方法:

  • 抽象方法:只有方法名称,没有具体的功能代码,子类必须要实现这些方法。
  • 具体方法:已经写好了功能代码的方法,子类可以直接用。

抽象类的特点

不能直接创建对象 :你不能直接写 new Animal() 来创建一个抽象类的对象。它的主要作用是让其他类继承它。

可以有方法的具体实现:有一些方法在抽象类里已经实现了,所以子类可以直接用这些方法。

可以有构造方法:虽然你不能直接创建抽象类的对象,但你可以通过它的子类调用构造方法。

抽象类的例子

假设你想设计一个动物的类(Animal),但你不知道每种动物发出的声音是什么样的。所以你把 makeSound() 定义成一个抽象方法,让不同的动物自己去实现它。

java 复制代码
// 抽象类使用 abstract 关键字定义
abstract class Animal {
    // 抽象方法,没有实现,需要子类去实现
    public abstract void makeSound();

    // 具体方法,子类可以直接使用
    public void sleep() {
        System.out.println("Sleeping...");
    }
}

// 使用 extens 关键字继承自抽象类 Animal 的具体类
class Dog extends Animal {
    // 实现抽象方法
    @Override
    public void makeSound() {
        System.out.println("Woof");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal dog = new Dog(); // 创建 Dog 对象
        dog.makeSound();        // 输出:Woof
        dog.sleep();            // 输出:Sleeping...
    }
}

解释:

  • Animal 类是抽象类,它定义了一个抽象方法 makeSound(),不同的动物发出的声音不同,所以这个方法没有实现。
  • Dog 类继承了 Animal 类,并实现了 makeSound() 方法。
  • sleep() 方法在抽象类里已经实现了,子类(如 Dog)可以直接使用它。

抽象类的使用场景

当你想要让多个类(例如不同的动物)共享一些相同的功能(如 sleep() 方法),但有些方法(如 makeSound())需要不同的实现时,你可以使用抽象类。

接口

接口是一种比抽象类更抽象的东西,它定义了一些方法,但这些方法都没有实现。类可以通过 implements 关键字来"实现"接口里的方法。

接口的特点

不能有具体方法:接口里的方法在 Java 8 之前都是空的,没有具体实现。它只告诉你这个方法应该存在,但具体怎么做由类自己决定。

可以多实现:一个类可以实现多个接口,而不像继承只能继承一个父类。

接口强调行为:接口更像是"契约"或"协议",它规定了一个类应该具备哪些功能。

接口的例子

假设你想定义一种交通工具的接口(Vehicle),每种交通工具都应该有 start()stop() 方法,但你不需要知道每种交通工具具体是怎么启动或停止的,这由具体的交通工具类去实现。

java 复制代码
// 使用 interface 关键字定义一个接口
interface Vehicle {
    // 接口中的方法都是抽象的,不需要写具体实现
    void start();
    void stop();
}

// 使用 implements 关键字实现接口的具体类
class Car implements Vehicle {
    // 实现接口中的方法
    @Override
    public void start() {
        System.out.println("Car is starting");
    }

    @Override
    public void stop() {
        System.out.println("Car is stopping");
    }
}

public class Test {
    public static void main(String[] args) {
        Vehicle car = new Car(); // 创建 Car 对象
        car.start();  // 输出:Car is starting
        car.stop();   // 输出:Car is stopping
    }
}

解释:

  • Vehicle 是一个接口,它定义了 start()stop() 两个方法,但没有给出具体实现。
  • Car 类实现了 Vehicle 接口,并具体定义了这两个方法该怎么做。

接口的使用场景

当你希望不同的类(如汽车、飞机)有相同的行为(如 start()stop()),但它们的具体实现不同时,使用接口会更合适。

抽象类和接口的区别

对比点 抽象类 接口
定义 可以包含抽象方法和具体方法 只能包含抽象方法(Java 8 之后可以有默认方法)
成员变量 可以包含成员变量 只能包含常量(public static final
构造函数 可以有构造函数 不能有构造函数
继承关系 只能继承一个抽象类 可以实现多个接口
适用场景 用于定义类的通用行为,提供部分实现 用于定义类的行为规范,不提供实现
访问修饰符 可以有不同的访问修饰符(publicprotected 默认所有方法是 public

什么时候用抽象类,什么时候用接口

用抽象类的情况 :如果你有一些功能是所有子类共享的,但某些功能需要子类去实现,这时抽象类比较合适。例如,所有的动物都会"睡觉"(sleep()),但发出的声音不同(makeSound())。

用接口的情况 :如果你想为完全不同的类定义共同的行为,而不关心它们之间是否有继承关系,接口更合适。例如,汽车和飞机都是交通工具,都需要 start()stop() 功能,但它们可能没有其他的共同点。

抽象类可以实现接口

尽管抽象类不能被直接实例化,但它们可以实现接口中的方法,而不一定要求子类必须重新实现。这种做法适合为接口提供部分实现,子类可以选择使用或覆盖它们。

java 复制代码
interface Walkable {
    void walk();
}

abstract class Animal implements Walkable {
    @Override
    public void walk() {
        System.out.println("Animal is walking.");
    }

    // 抽象类中可以有自己的抽象方法
    public abstract void makeSound();
}

在这个例子中,抽象类 Animal 实现了 Walkable 接口中的 walk() 方法,而子类可以继承这个实现或进行自定义。

接口中的常量其实是 public static final

在接口中定义的字段默认是 public static final,即它们都是常量,必须在声明时初始化,且不能修改。即使你没有显式声明这些修饰符,编译器也会自动加上。

java 复制代码
interface MyConstants {
    // 实际上等同于:public static final int MAX_VALUE = 100
    int MAX_VALUE = 100; 
}

这意味着接口中的所有字段都是全局常量,并且只能用于共享常量的场景。

标记接口(Marker Interface)

Java 中有一种特殊的接口叫做标记接口(Marker Interface),它没有任何方法,作用仅仅是标记某个类具有某种特性。比如 java.io.Serializable,实现了这个接口的类可以被序列化。

java 复制代码
interface MarkerInterface {
    // 没有方法,仅用于标记类具有某种属性
}

class MyClass implements MarkerInterface {
    // 这个类被标记为拥有 MarkerInterface 的属性
}

标记接口本质上是一种元数据,用于表示类的某些行为或特性。这种设计理念后来被注解(Annotations)部分取代。

接口可以扩展多个接口

一个接口可以继承多个接口,这让 Java 实现了某种形式的"多继承",但它不同于类的多继承,类的多继承在 Java 中是被禁止的。这种机制允许你通过继承多个接口来组合各种行为。

java 复制代码
interface Eater {
    void eat();
}

interface Sleeper {
    void sleep();
}

interface Animal extends Eater, Sleeper {
    void makeSound();
}

class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating.");
    }

    @Override
    public void sleep() {
        System.out.println("Dog is sleeping.");
    }

    @Override
    public void makeSound() {
        System.out.println("Dog says: Woof!");
    }
}

Dog 类通过实现 Animal 接口,间接地获得了 EaterSleeper 接口中的方法。这种设计使得代码更具扩展性。

接口中的方法可以有多个默认实现来源

在 Java 8 引入了默认方法后,一个类可以从多个接口继承默认方法。如果这些接口中定义了相同的方法名,类必须手动解决冲突。这种情况在多重继承的情况下常见。

java 复制代码
interface InterfaceA {
    default void show() {
        System.out.println("InterfaceA");
    }
}

interface InterfaceB {
    default void show() {
        System.out.println("InterfaceB");
    }
}

class MyClass implements InterfaceA, InterfaceB {
    @Override
    public void show() {
        InterfaceA.super.show();  // 选择调用 InterfaceA 的 show()
    }
}

在这种情况下,你必须明确指明调用哪个接口的默认实现。通过 InterfaceA.super.show() 可以解决冲突。

本期小知识

在 Java 8 之后,接口中可以定义默认方法(default methods)和静态方法。这是接口功能的重要扩展,因为传统的接口不能包含实现。

默认方法:允许在接口中定义方法的默认实现,不强制要求实现类去实现它们。这在维护接口的向后兼容性上起到了很大作用。

java 复制代码
interface Vehicle {
    default void start() {
        System.out.println("Starting the vehicle");
    }
}

静态方法:接口中的静态方法只能通过接口名称调用,不能被子类继承或覆盖。

java 复制代码
interface Vehicle {
    static void checkEngine() {
        System.out.println("Engine checked");
    }
}
相关推荐
湫ccc13 分钟前
《Python基础》之字符串格式化输出
开发语言·python
弗拉唐13 分钟前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi771 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
mqiqe1 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin1 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
少说多做3431 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀1 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20201 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea