Interface ist ein Vertrag, der eine Reihe abstrakter Methoden definiert, die eine Klasse implementieren muss. In Java und Kotlin sind Schnittstellen der primäre Mechanismus für Abstraktion und Polymorphismus. In Java 8+ können Schnittstellen default- und static-Methoden enthalten, in Kotlin — Standardimplementierungen. Laut Google Android Developers (2025) werden Schnittstellen in 90% der Android-Projekte zur Definition von Architekturschichten — Repositories, UseCases und Diensten — verwendet.
Wichtige Punkte
Interface ist ein Referenztyp, der abstrakte Methoden, Konstanten und Standardmethoden enthält. Eine Klasse, die eine Schnittstelle implementiert, muss Implementierungen für alle ihre abstrakten Methoden bereitstellen. In Java kann eine Schnittstelle keinen Zustand (Instanzfelder) haben; Kotlin hält sich ebenfalls an diese Einschränkung, unterstützt aber Eigenschaften mit Zugriffsmethoden.
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 und 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();
}
// andere Methoden
}Repository<T> ist eine generische Schnittstelle mit CRUD-Methoden. Die default-Methode count() bietet eine Standardimplementierung, die überschrieben werden kann. UserRepositoryImpl implementiert die Schnittstelle mit EntityManager für den Datenzugriff. Dieser Ansatz ermöglicht das Testen der Datenschicht über einen Mock der Schnittstelle ohne eine echte Datenbank.
Die Wahl zwischen einer Schnittstelle und einer abstrakten Klasse hängt vom Vorhandensein gemeinsamen Zustands und den Beziehungen zwischen Typen ab. Eine Schnittstelle definiert einen Vertrag (was eine Klasse tun kann), eine abstrakte Klasse definiert eine gemeinsame Implementierung (was eine Klasse ist).
| Kriterium | Interface | Abstract Class |
|---|---|---|
| Zustand (Felder) | Nur static final Konstanten | Ja, beliebige Felder |
| Konstruktoren | Nein | Ja |
| Mehrfachvererbung | Ja (implements) | Nein (extends einer) |
| Zugriffsmodifikatoren | public (Java 8-), default-Methoden | Alle (private, protected, public) |
| Wann verwenden | Vertrag für nicht verwandte Klassen | Gemeinsame Basis für verwandte Klassen |
In Clean Architecture werden Schnittstellen in der inneren Schicht (domain) platziert und Implementierungen in der äußeren Schicht (data). Dies ermöglicht die Einhaltung der Dependency Rule: Die äußere Schicht hängt von der inneren ab, aber nicht umgekehrt. Abstrakte Klassen werden häufiger für Vorlagenmethoden (Template Method Pattern) verwendet.
Kotlin-Schnittstellen sind flexibler als Java: Sie können abstrakte Eigenschaften deklarieren und Methodenimplementierungen bereitstellen. Anders als Java unterstützt Kotlin die Delegation über das Schlüsselwort by, was den Boilerplate-Code bei der Implementierung des Delegate-Musters erheblich reduziert.
interface ApiService {
val baseUrl: String // abstrakte Eigenschaft
suspend fun fetchData(): Result>
fun getEndpoint(path: String): String {
return "$baseUrl/$path" // Standardimplementierung
}
}
class RetrofitApiService(
override val baseUrl: String
) : ApiService {
private val client = Retrofit.Builder()
.baseUrl(baseUrl)
.build()
override suspend fun fetchData(): Result>
{
// Anfrageimplementierung
}
}
// Delegation über by
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println(message)
}
class UserService(logger: Logger) : Logger by loggerUserService implementiert die Logger-Schnittstelle durch Delegation (by). Alle log()-Aufrufe werden an das logger-Objekt weitergeleitet, ohne Wrappermethoden zu schreiben. Dies ist ein Beispiel für Komposition, die in Java 5-10 Zeilen Boilerplate-Code erfordern würde.
Clean Architecture für Android trennt die Anwendung klar in Schichten. Schnittstellen fungieren als Grenzen zwischen Schichten: Domain definiert Repository- und UseCase-Schnittstellen, Data stellt Implementierungen bereit. Dies ermöglicht das Austauschen von Implementierungen ohne Änderung der Geschäftslogik — ein entscheidender Vorteil bei der Migration von Room zu Firebase oder von REST zu GraphQL.
// Domain layer — Schnittstelle (Vertrag)
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun updateUser(user: User)
}
// Domain layer — use case (abhängt von der Schnittstelle)
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(id: String): Result {
return runCatching { repository.getUser(id) }
}
}
// Data layer — Implementierung
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 ist eine Schnittstelle in der domain-Schicht. GetUserUseCase hängt von der Schnittstelle ab, nicht von der Implementierung. UserRepositoryImpl in der data-Schicht implementiert die Schnittstelle und kombiniert API und lokale Datenbank. Diese Architektur ermöglicht das Testen von UseCases mit einem Mock-Repository ohne Einrichtung einer Datenbank oder eines Netzwerks.
Default-Methoden wurden in Java 8 für die evolutionäre Erweiterung von Schnittstellen ohne Bruch der Abwärtskompatibilität hinzugefügt. Wenn ArrayList die neue stream()-Methode, die zu Collection hinzugefügt wurde, nicht implementiert hätte, würde alter Code weiter funktionieren. Static-Methoden in Schnittstellen dienen als Hilfsmittel, die sich auf die Schnittstelle beziehen — eine Alternative zu Utility-Klassen.
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();
}
// Konstante
String CATEGORY = "transport";
}Default-Methoden lösen das Diamond Problem: Wenn eine Klasse zwei Schnittstellen mit derselben default-Methode implementiert, verlangt der Compiler eine explizite Überschreibung. Static-Methoden werden über den Schnittstellennamen aufgerufen — Vehicle.of("car"), ohne Instanz.
Fehler im Schnittstellendesign führen zu fragwürdigem Code, Testkomplexität und SOLID-Verletzungen. Betrachten wir drei häufige Probleme.
Eine Schnittstelle mit 15+ Methoden verstößt gegen das Interface Segregation Principle (ISP). Ein Beispiel ist das alte java.util.Dictionary mit 10+ Methoden. Lösung: Aufteilen in mehrere kleine Schnittstellen — ReadableRepository, WritableRepository, SearchableRepository. Ein Client (Dienst) hängt nur von den Methoden ab, die er benötigt.
Das Erstellen einer Schnittstelle für jede Klasse ohne echten Bedarf an Polymorphismus ist das Interface overkill-Antipattern. Indikator: Die Schnittstelle hat genau eine Implementierung und das Projekt hat keine Pläne, Alternativen hinzuzufügen. Lösung: Fügen Sie eine Schnittstelle nur hinzu, wenn eine zweite Implementierungsoption erscheint oder ein Mock für Tests benötigt wird.
Häufig gestellte Fragen
Interface definiert nur einen Vertrag (Methodensignaturen), kann keinen Zustand haben und unterstützt Mehrfachvererbung. Abstract class kann Felder, Konstruktoren und implementierte Methoden enthalten, aber eine Klasse kann nur eine abstrakte Klasse erben. Seit Java 8 haben Schnittstellen default- und static-Methoden erhalten, was die Lücke verkleinert hat.
Ja, in Java und Kotlin unterstützen Schnittstellen Vererbung. public interface AdvancedRepository<T> extends Repository<T>, Pageable ist eine Schnittstelle, die zwei andere kombiniert. Eine Klasse, die AdvancedRepository implementiert, muss alle Methoden beider Elternschnittstellen implementieren. Mehrfachvererbung ist nur für Schnittstellen erlaubt.
Funktionale Schnittstelle ist eine Schnittstelle mit einer einzigen abstrakten Methode (SAM — Single Abstract Method). Die Annotation @FunctionalInterface garantiert diese Einschränkung. Beispiele: Runnable, Callable, Comparator, Consumer. Funktionale Schnittstellen sind die Grundlage von Java 8-Lambda-Ausdrücken: () -> System.out.println() implementiert Runnable.
Default-Methoden ermöglichen das Hinzufügen neuer Methoden zu einer Schnittstelle, ohne alle implementierenden Klassen zu ändern. Beispielsweise hat Java 8 stream() zu Collection als default-Methode hinzugefügt. Ohne diesen Mechanismus hätten foreach(), stream() und andere Methoden Änderungen an Tausenden von Klassen im JDK erfordert. Default ist eine abwärtskompatible Erweiterungsmethode.
Kotlin verbietet die Mehrfachvererbung von Klassen, erlaubt aber die Mehrfachimplementierung von Schnittstellen. Wenn zwei Schnittstellen eine Methode mit derselben Signatur und Standardimplementierung haben, verlangt der Compiler eine explizite Überschreibung mit dem Aufruf super<InterfaceName>.method(). Dies löst das Diamond-Problem auf Kompilierungsebene.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch