Interface:本质、Java和Kotlin中Android的契约

作者: IT Sectr 发布日期: 2026-02-18 阅读时间: 9 分钟

Interface — 是一个契约,定义了一组类必须实现的抽象方法。在Java和Kotlin中,接口是抽象和多态的主要机制。在Java 8+中,接口可以包含default和static方法,在Kotlin中则可以包含默认实现。根据Google Android Developers(2025)的数据,90%的Android项目使用接口来定义架构层——仓库、UseCase和服务。

要点

  • Interface — 抽象类型,定义方法签名而不包含实现(Java 8之前)
  • Implements — 将类与接口关联的关键字;一个类可以实现多个接口
  • Default method — Java接口中带有实现的方法,在Java 8中添加以保持向后兼容性
  • Kotlin interface 支持带有getter的属性和方法实现,在许多场景中取代了abstract class
  • Markup interface — 用作标记的空接口(Serializable、Cloneable、RandomAccess)

Java和Kotlin中的Interface是什么?

Interface — 是一种引用类型,包含抽象方法、常量和默认方法。实现接口的类必须提供其所有抽象方法的实现。在Java中,接口不能有状态(实例字段),Kotlin也遵循这一限制,但支持带有访问器的属性。

Java
public interface Repository {
    T findById(Long id);
    List findAll();
    T save(T entity);
    void deleteById(Long id);
    
    default long count() {
        return findAll().size();
    }
}

@Entity
public class UserEntity {
    private Long id;
    private String email;
    // getter和setter
}

public class UserRepositoryImpl implements Repository {
    private final EntityManager em;
    
    public UserEntity findById(Long id) {
        return em.find(UserEntity.class, id);
    }
    
    public List findAll() {
        return em.createQuery("FROM UserEntity", UserEntity.class)
                .getResultList();
    }
    // 其他方法
}

Repository<T> — 一个带有CRUD方法的泛型接口。默认方法count()提供了一个可以覆盖的默认实现。UserRepositoryImpl实现了该接口,使用EntityManager访问数据。这种方法允许在没有真实数据库的情况下通过接口的mock来测试数据层。

Interface vs abstract class:何时选择什么

接口和抽象类之间的选择取决于是否存在共享状态以及类型之间的关系。接口定义契约(类可以做什么),抽象类定义共同的实现(类是什么)。

标准InterfaceAbstract Class
状态(字段)仅static final常量是,任何字段
构造函数
多重继承是(implements)否(extends一个)
访问修饰符public(Java 8-),default方法全部(private、protected、public)
何时使用不相关类的契约相关类的共同基础

Clean Architecture中,接口位于内层(domain),实现位于外层(data)。这允许遵守依赖规则:外层依赖于内层,而不是相反。抽象类更常用于模板方法(Template Method模式)。

Kotlin中的Interface:属性和委托

Kotlin接口比Java更灵活:它们可以声明抽象属性并提供方法实现。与Java不同,Kotlin支持通过关键字by进行委托,这大大减少了实现Delegate模式时的样板代码。

Kotlin
interface ApiService {
    val baseUrl: String  // 抽象属性
    
    suspend fun fetchData(): Result>
    
    fun getEndpoint(path: String): String {
        return "$baseUrl/$path"  // 默认实现
    }
}

class RetrofitApiService(
    override val baseUrl: String
) : ApiService {
    private val client = Retrofit.Builder()
        .baseUrl(baseUrl)
        .build()
    
    override suspend fun fetchData(): Result> {
        // 请求的实现
    }
}

// 通过by进行委托
interface Logger {
    fun log(message: String)
}

class ConsoleLogger : Logger {
    override fun log(message: String) = println(message)
}

class UserService(logger: Logger) : Logger by logger

UserService通过委托(by)实现了Logger接口。所有log()调用都被重定向到logger对象,无需编写包装方法。这是组合的一个例子,在Java中需要5-10行样板代码。

Clean Architecture Android中的Interface

Clean Architecture为Android清晰地划分应用程序为多个层。接口扮演层之间边界的角色:Domain定义仓库和UseCase的接口,Data提供实现。这允许在不改变业务逻辑的情况下替换实现——这是从Room迁移到Firebase或从REST迁移到GraphQL时的关键优势。

Kotlin
// Domain层 — 接口(契约)
interface UserRepository {
    suspend fun getUser(id: String): User
    suspend fun updateUser(user: User)
}

// Domain层 — use case(依赖于接口)
class GetUserUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(id: String): Result {
        return runCatching { repository.getUser(id) }
    }
}

// Data层 — 实现
class UserRepositoryImpl(
    private val api: UserApi,
    private val dao: UserDao
) : UserRepository {
    override suspend fun getUser(id: String): User {
        val cached = dao.getUser(id)
        if (cached != null) return cached
        val remote = api.fetchUser(id)
        dao.insertUser(remote)
        return remote
    }
    // updateUser...
}

UserRepository — domain层中的接口。GetUserUseCase依赖于接口,而不是实现。Data层中的UserRepositoryImpl实现了该接口,结合了API和本地数据库。这种架构允许在没有数据库或网络配置的情况下,使用mock仓库测试UseCase。

Java 8+中的Default和static方法

Default方法在Java 8中被添加,用于在不破坏向后兼容性的情况下逐步扩展接口。如果ArrayList没有实现Collection中添加的新stream()方法,旧代码将继续工作。接口中的static方法用于与接口相关的工具——替代工具类。

Java
public interface Vehicle {
    void start();
    void stop();
    
    default void honk() {
        System.out.println("Beep!");
    }
    
    static Vehicle of(String type) {
        if ("car".equals(type)) return new Car();
        return new Bicycle();
    }
    
    // 常量
    String CATEGORY = "transport";
}

Default方法解决菱形问题(diamond problem):如果一个类实现了两个具有相同default方法的接口,编译器要求显式覆盖。Static方法通过接口名调用——Vehicle.of("car"),无需实例。

设计接口时的常见错误

设计接口时的错误导致脆弱的代码、测试困难和违反SOLID原则。让我们看看三个常见问题。

Interface Pollution — 一个接口中有太多方法

一个包含15+方法的接口违反了接口隔离原则(ISP)。例子——旧的java.util.Dictionary有10+方法。解决方案:拆分为多个小接口——ReadableRepository、WritableRepository、SearchableRepository。客户端(服务)只依赖于它需要的方法。

过度抽象 — 为每个类创建接口

在没有真正需要多态的情况下为每个类创建接口是Interface overkill反模式。标志:该接口恰好有一个实现,并且项目中没有添加替代方案的计划。解决方案:仅在出现第二个实现变体或需要mock进行测试时添加接口。

常见问题

Java中interface和abstract class有什么区别?

Interface只定义契约(方法签名),不能有状态,支持多重继承。Abstract class可以包含字段、构造函数和已实现的方法,但一个类只能继承一个抽象类。从Java 8开始,接口获得了default和static方法,缩小了差距。

接口可以继承另一个接口吗?

是的,在Java和Kotlin中,接口支持继承。public interface AdvancedRepository<T> extends Repository<T>, Pageable — 一个结合了另外两个接口的接口。实现AdvancedRepository的类必须实现两个父接口的所有方法。多重继承只允许用于接口。

Java中的函数式接口是什么?

函数式接口 — 只有一个抽象方法的接口(SAM — Single Abstract Method)。@FunctionalInterface注解保证了这一限制。例子:Runnable、Callable、Comparator、Consumer。函数式接口是Java 8中lambda表达式的基础:() -> System.out.println() 实现了Runnable。

为什么Java接口中需要default方法?

Default方法允许在不修改所有实现类的情况下向接口添加新方法。例如,Java 8将stream()作为default方法添加到Collection。如果没有这种机制,foreach()、stream()和其他方法将需要修改JDK中数千个类。Default — 一种向后兼容的扩展方式。

Kotlin如何解决多重继承问题?

Kotlin禁止类的多重继承,但允许接口的多重实现。如果两个接口具有相同签名和默认实现的方法,编译器要求通过调用super<InterfaceName>.method()显式覆盖。这在编译层面解决了菱形问题。

总结

  • Interface — 定义待实现方法的契约;Java和Kotlin中多态的主要机制
  • Java 8+ 在接口中添加了default和static方法,缩小了与抽象类的差距
  • Kotlin interfaces 支持抽象属性、默认实现和通过by进行委托
  • Clean Architecture 使用接口作为Domain和Data层之间的边界
  • 接口隔离原则(ISP) 要求将大接口拆分为专门的接口
  • 函数式接口(Single Abstract Method)— lambda表达式和Stream API的基础
  • 建议:在出现第二个实现或需要mock进行测试时添加接口

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读