Interface — 是一个契约,定义了一组类必须实现的抽象方法。在Java和Kotlin中,接口是抽象和多态的主要机制。在Java 8+中,接口可以包含default和static方法,在Kotlin中则可以包含默认实现。根据Google Android Developers(2025)的数据,90%的Android项目使用接口来定义架构层——仓库、UseCase和服务。
要点
Interface — 是一种引用类型,包含抽象方法、常量和默认方法。实现接口的类必须提供其所有抽象方法的实现。在Java中,接口不能有状态(实例字段),Kotlin也遵循这一限制,但支持带有访问器的属性。
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 | Abstract Class |
|---|---|---|
| 状态(字段) | 仅static final常量 | 是,任何字段 |
| 构造函数 | 无 | 有 |
| 多重继承 | 是(implements) | 否(extends一个) |
| 访问修饰符 | public(Java 8-),default方法 | 全部(private、protected、public) |
| 何时使用 | 不相关类的契约 | 相关类的共同基础 |
在Clean Architecture中,接口位于内层(domain),实现位于外层(data)。这允许遵守依赖规则:外层依赖于内层,而不是相反。抽象类更常用于模板方法(Template Method模式)。
Kotlin接口比Java更灵活:它们可以声明抽象属性并提供方法实现。与Java不同,Kotlin支持通过关键字by进行委托,这大大减少了实现Delegate模式时的样板代码。
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 loggerUserService通过委托(by)实现了Logger接口。所有log()调用都被重定向到logger对象,无需编写包装方法。这是组合的一个例子,在Java中需要5-10行样板代码。
Clean Architecture为Android清晰地划分应用程序为多个层。接口扮演层之间边界的角色:Domain定义仓库和UseCase的接口,Data提供实现。这允许在不改变业务逻辑的情况下替换实现——这是从Room迁移到Firebase或从REST迁移到GraphQL时的关键优势。
// 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。
Default方法在Java 8中被添加,用于在不破坏向后兼容性的情况下逐步扩展接口。如果ArrayList没有实现Collection中添加的新stream()方法,旧代码将继续工作。接口中的static方法用于与接口相关的工具——替代工具类。
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原则。让我们看看三个常见问题。
一个包含15+方法的接口违反了接口隔离原则(ISP)。例子——旧的java.util.Dictionary有10+方法。解决方案:拆分为多个小接口——ReadableRepository、WritableRepository、SearchableRepository。客户端(服务)只依赖于它需要的方法。
在没有真正需要多态的情况下为每个类创建接口是Interface overkill反模式。标志:该接口恰好有一个实现,并且项目中没有添加替代方案的计划。解决方案:仅在出现第二个实现变体或需要mock进行测试时添加接口。
常见问题
Interface只定义契约(方法签名),不能有状态,支持多重继承。Abstract class可以包含字段、构造函数和已实现的方法,但一个类只能继承一个抽象类。从Java 8开始,接口获得了default和static方法,缩小了差距。
是的,在Java和Kotlin中,接口支持继承。public interface AdvancedRepository<T> extends Repository<T>, Pageable — 一个结合了另外两个接口的接口。实现AdvancedRepository的类必须实现两个父接口的所有方法。多重继承只允许用于接口。
函数式接口 — 只有一个抽象方法的接口(SAM — Single Abstract Method)。@FunctionalInterface注解保证了这一限制。例子:Runnable、Callable、Comparator、Consumer。函数式接口是Java 8中lambda表达式的基础:() -> System.out.println() 实现了Runnable。
Default方法允许在不修改所有实现类的情况下向接口添加新方法。例如,Java 8将stream()作为default方法添加到Collection。如果没有这种机制,foreach()、stream()和其他方法将需要修改JDK中数千个类。Default — 一种向后兼容的扩展方式。
Kotlin禁止类的多重继承,但允许接口的多重实现。如果两个接口具有相同签名和默认实现的方法,编译器要求通过调用super<InterfaceName>.method()显式覆盖。这在编译层面解决了菱形问题。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。