Interface — adalah kontrak yang mendefinisikan sekumpulan metode abstrak yang harus diimplementasikan oleh kelas. Di Java dan Kotlin, antarmuka adalah mekanisme utama abstraksi dan polimorfisme. Di Java 8+ antarmuka dapat berisi metode default dan static, di Kotlin — implementasi default. Menurut Google Android Developers (2025), antarmuka digunakan di 90% proyek Android untuk mendefinisikan lapisan arsitektur — repositori, UseCase, dan layanan.
Poin Utama
Interface — adalah tipe referensi yang berisi metode abstrak, konstanta, dan metode default. Kelas yang mengimplementasikan antarmuka wajib menyediakan implementasi dari semua metode abstraknya. Di Java, antarmuka tidak dapat memiliki status (bidang instans), Kotlin juga mengikuti batasan ini, tetapi mendukung properti dengan aksesor.
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 dan 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();
}
// metode lainnya
}Repository<T> — antarmuka generik dengan metode CRUD. Metode default count() menyediakan implementasi default yang dapat ditimpa. UserRepositoryImpl mengimplementasikan antarmuka menggunakan EntityManager untuk akses data. Pendekatan ini memungkinkan pengujian lapisan data melalui mock antarmuka tanpa database nyata.
Pilihan antara antarmuka dan kelas abstrak tergantung pada keberadaan status bersama dan hubungan antar tipe. Antarmuka mendefinisikan kontrak (apa yang bisa dilakukan kelas), kelas abstrak — implementasi bersama (apa adanya kelas).
| Kriteria | Interface | Abstract Class |
|---|---|---|
| Status (bidang) | Hanya konstanta static final | Ya, bidang apa pun |
| Konstruktor | Tidak | Ya |
| Warisan berganda | Ya (implements) | Tidak (extends satu) |
| Pengubah akses | public (Java 8-), metode default | Semua (private, protected, public) |
| Kapan digunakan | Kontrak untuk kelas yang tidak terkait | Basis bersama untuk kelas terkait |
Di Clean Architecture, antarmuka ditempatkan di lapisan dalam (domain), dan implementasi — di lapisan luar (data). Ini memungkinkan kepatuhan terhadap Aturan Ketergantungan: lapisan luar bergantung pada lapisan dalam, bukan sebaliknya. Kelas abstrak lebih sering digunakan untuk metode template (Template Method pattern).
Antarmuka Kotlin lebih fleksibel daripada Java: mereka dapat mendeklarasikan properti abstrak dan menyediakan implementasi metode. Tidak seperti Java, Kotlin mendukung delegasi melalui kata kunci by, yang secara signifikan mengurangi boilerplate saat mengimplementasikan pola Delegate.
interface ApiService {
val baseUrl: String // properti abstrak
suspend fun fetchData(): Result>
fun getEndpoint(path: String): String {
return "$baseUrl/$path" // implementasi default
}
}
class RetrofitApiService(
override val baseUrl: String
) : ApiService {
private val client = Retrofit.Builder()
.baseUrl(baseUrl)
.build()
override suspend fun fetchData(): Result>
{
// implementasi permintaan
}
}
// Delegasi melalui by
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println(message)
}
class UserService(logger: Logger) : Logger by loggerUserService mengimplementasikan antarmuka Logger melalui delegasi (by). Semua panggilan log() dialihkan ke objek logger tanpa menulis metode wrapper. Ini adalah contoh komposisi yang di Java akan membutuhkan 5-10 baris kode boilerplate.
Clean Architecture untuk Android secara jelas membagi aplikasi menjadi beberapa lapisan. Antarmuka berperan sebagai batas antar lapisan: Domain mendefinisikan antarmuka repositori dan UseCase, Data menyediakan implementasi. Ini memungkinkan penggantian implementasi tanpa mengubah logika bisnis — keuntungan utama saat migrasi dari Room ke Firebase atau dari REST ke GraphQL.
// Lapisan Domain — antarmuka (kontrak)
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun updateUser(user: User)
}
// Lapisan Domain — use case (bergantung pada antarmuka)
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(id: String): Result {
return runCatching { repository.getUser(id) }
}
}
// Lapisan Data — implementasi
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 — antarmuka di lapisan domain. GetUserUseCase bergantung pada antarmuka, bukan pada implementasi. UserRepositoryImpl di lapisan data mengimplementasikan antarmuka, menggabungkan API dan basis data lokal. Arsitektur semacam itu memungkinkan pengujian UseCase dengan repositori mock tanpa konfigurasi basis data atau jaringan.
Metode default ditambahkan di Java 8 untuk memperluas antarmuka secara evolusioner tanpa melanggar kompatibilitas mundur. Jika ArrayList tidak mengimplementasikan metode stream() baru yang ditambahkan ke Collection, kode lama akan terus berfungsi. Metode static di antarmuka berfungsi untuk utilitas yang terkait dengan antarmuka — alternatif untuk kelas utilitas.
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();
}
// konstanta
String CATEGORY = "transport";
}Metode default memecahkan masalah diamond (diamond problem): jika sebuah kelas mengimplementasikan dua antarmuka dengan metode default yang sama, kompiler memerlukan penimpaan eksplisit. Metode static dipanggil melalui nama antarmuka — Vehicle.of("car"), tanpa instance.
Kesalahan dalam merancang antarmuka menyebabkan kode rapuh, kesulitan pengujian, dan pelanggaran SOLID. Mari kita bahas tiga masalah umum.
Antarmuka dengan 15+ metode melanggar Interface Segregation Principle (ISP). Contoh — java.util.Dictionary lama dengan 10+ metode. Solusi: bagi menjadi beberapa antarmuka kecil — ReadableRepository, WritableRepository, SearchableRepository. Klien (layanan) hanya bergantung pada metode yang dibutuhkannya.
Membuat antarmuka untuk setiap kelas tanpa kebutuhan nyata akan polimorfisme adalah anti-pola Interface overkill. Tanda: antarmuka memiliki tepat satu implementasi dan tidak ada rencana untuk menambahkan alternatif di proyek. Solusi: tambahkan antarmuka hanya ketika varian implementasi kedua muncul atau ada kebutuhan mock untuk pengujian.
Pertanyaan yang Sering Diajukan
Interface hanya mendefinisikan kontrak (tanda tangan metode), tidak dapat memiliki status, dan mendukung warisan berganda. Abstract class dapat berisi bidang, konstruktor, dan metode yang diimplementasikan, tetapi kelas hanya dapat mewarisi satu kelas abstrak. Sejak Java 8, antarmuka mendapatkan metode default dan static, memperkecil kesenjangan.
Ya, di Java dan Kotlin antarmuka mendukung warisan. public interface AdvancedRepository<T> extends Repository<T>, Pageable — antarmuka yang menggabungkan dua lainnya. Kelas yang mengimplementasikan AdvancedRepository harus mengimplementasikan semua metode dari kedua antarmuka induk. Warisan berganda hanya diizinkan untuk antarmuka.
Antarmuka fungsional — antarmuka dengan satu metode abstrak (SAM — Single Abstract Method). Anotasi @FunctionalInterface menjamin batasan ini. Contoh: Runnable, Callable, Comparator, Consumer. Antarmuka fungsional adalah dasar dari ekspresi lambda Java 8: () -> System.out.println() mengimplementasikan Runnable.
Metode default memungkinkan penambahan metode baru ke antarmuka tanpa mengubah semua kelas yang mengimplementasikannya. Misalnya, Java 8 menambahkan stream() ke Collection sebagai metode default. Tanpa mekanisme ini, foreach(), stream(), dan metode lainnya akan memerlukan perubahan ribuan kelas di JDK. Default — cara yang kompatibel mundur untuk memperluas.
Kotlin melarang warisan berganda kelas, tetapi mengizinkan implementasi berganda antarmuka. Jika dua antarmuka memiliki metode dengan tanda tangan dan implementasi default yang sama, kompiler memerlukan penimpaan eksplisit dengan panggilan super<InterfaceName>.method(). Ini memecahkan masalah diamond pada tingkat kompilasi.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga