Interface — är ett kontrakt som definierar en uppsättning abstrakta metoder som en klass måste implementera. I Java och Kotlin är gränssnitt den huvudsakliga mekanismen för abstraktion och polymorfism. I Java 8+ kan gränssnitt innehålla default och static metoder, i Kotlin — standardimplementeringar. Enligt Google Android Developers (2025) används gränssnitt i 90% av Android-projekt för att definiera arkitekturlager — repositories, UseCase och tjänster.
Huvudpunkter
Interface — är en referenstyp som innehåller abstrakta metoder, konstanter och standardmetoder. Klassen som implementerar ett gränssnitt måste tillhandahålla implementering av alla dess abstrakta metoder. I Java kan ett gränssnitt inte ha tillstånd (instansfält), Kotlin följer också denna begränsning men stödjer egenskaper med accessorer.
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;
// getters och setters
}
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();
}
// övriga metoder
}Repository<T> — ett generiskt gränssnitt med CRUD-metoder. Standardmetoden count() tillhandahåller en standardimplementering som kan överskridas. UserRepositoryImpl implementerar gränssnittet med EntityManager för dataåtkomst. Detta tillvägagångssätt möjliggör testning av datalagret via en mock av gränssnittet utan verklig databas.
Valet mellan gränssnitt och abstrakt klass beror på förekomsten av gemensamt tillstånd och relationer mellan typer. Ett gränssnitt definierar ett kontrakt (vad klassen kan göra), en abstrakt klass — en gemensam implementering (vad klassen är).
| Kriterium | Interface | Abstract Class |
|---|---|---|
| Tillstånd (fält) | Endast static final konstanter | Ja, alla fält |
| Konstruktorer | Nej | Ja |
| Multiplelv nedärvning | Ja (implements) | Nej (extends en) |
| Åtkomstmodifierare | public (Java 8-), default metoder | Alla (private, protected, public) |
| När ska man använda | Kontrakt för orelaterade klasser | Gemensam bas för relaterade klasser |
I Clean Architecture placeras gränssnitt i det inre lagret (domain) och implementeringar i det yttre (data). Detta möjliggör efterlevnad av Beroenderegeln: det yttre lagret beror på det inre, inte tvärtom. Abstrakta klasser används oftare för mallmetoder (Template Method pattern).
Kotlin-gränssnitt är mer flexibla än Java: de kan deklarera abstrakta egenskaper och tillhandahålla metodimplementeringar. Till skillnad från Java stödjer Kotlin delegering via nyckelordet by, vilket avsevärt minskar boilerplate vid implementering av Delegate-mönstret.
interface ApiService {
val baseUrl: String // abstrakt egenskap
suspend fun fetchData(): Result>
fun getEndpoint(path: String): String {
return "$baseUrl/$path" // standardimplementering
}
}
class RetrofitApiService(
override val baseUrl: String
) : ApiService {
private val client = Retrofit.Builder()
.baseUrl(baseUrl)
.build()
override suspend fun fetchData(): Result>
{
// implementering av förfrågan
}
}
// Delegering via by
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println(message)
}
class UserService(logger: Logger) : Logger by loggerUserService implementerar Logger-gränssnittet via delegering (by). Alla log()-anrop omdirigeras till loggerobjektet utan att skriva wrapper-metoder. Detta är ett exempel på komposition som i Java skulle kräva 5-10 rader boilerplate-kod.
Clean Architecture för Android delar tydligt upp applikationen i lager. Gränssnitt fungerar som gränser mellan lager: Domain definierar gränssnitt för repositories och UseCase, Data tillhandahåller implementeringar. Detta möjliggör utbyte av implementeringar utan att ändra affärslogiken — en viktig fördel vid migrering från Room till Firebase eller från REST till GraphQL.
// Domain-lager — gränssnitt (kontrakt)
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun updateUser(user: User)
}
// Domain-lager — use case (beroende av gränssnitt)
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(id: String): Result {
return runCatching { repository.getUser(id) }
}
}
// Data-lager — implementering
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 — gränssnitt i domain-lagret. GetUserUseCase är beroende av gränssnittet, inte implementeringen. UserRepositoryImpl i data-lagret implementerar gränssnittet och kombinerar API och lokal databas. Denna arkitektur möjliggör testning av UseCase med ett mock-repository utan databas- eller nätverkskonfiguration.
Default-metoder lades till i Java 8 för evolutionär utvidgning av gränssnitt utan att bryta bakåtkompatibilitet. Om ArrayList inte hade implementerat den nya metoden stream() som lades till i Collection, skulle gammal kod fortsätta att fungera. Static-metoder i gränssnitt tjänar som verktyg relaterade till gränssnittet — ett alternativ till verktygsklasser.
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();
}
// konstant
String CATEGORY = "transport";
}Default-metoder löser diamond problem: om en klass implementerar två gränssnitt med samma default-metod, kräver kompilatorn explicit överskrivning. Static-metoder anropas via gränssnittsnamnet — Vehicle.of("car"), utan instans.
Misstag vid design av gränssnitt leder till skör kod, svårigheter med testning och SOLID-överträdelser. Låt oss titta på tre vanliga problem.
Ett gränssnitt med 15+ metoder bryter mot Interface Segregation Principle (ISP). Exempel — gamla java.util.Dictionary med 10+ metoder. Lösning: dela upp i flera små gränssnitt — ReadableRepository, WritableRepository, SearchableRepository. Klienten (tjänsten) är bara beroende av de metoder den behöver.
Att skapa ett gränssnitt för varje klass utan verkligt behov av polymorfism är anti-mönstret Interface overkill. Tecken: gränssnittet har exakt en implementering och det finns inga planer på att lägga till alternativ i projektet. Lösning: lägg till gränssnitt endast när en andra implementeringsvariant uppstår eller behov av mock för testning.
Vanliga frågor
Interface definierar bara kontraktet (metodsignaturer), kan inte ha tillstånd och stödjer multiplelv nedärvning. Abstract class kan innehålla fält, konstruktorer och implementerade metoder, men en klass kan bara ärva en abstrakt klass. Från Java 8 har gränssnitt fått default och static metoder, vilket minskat gapet.
Ja, i Java och Kotlin stödjer gränssnitt nedärvning. public interface AdvancedRepository<T> extends Repository<T>, Pageable — ett gränssnitt som kombinerar två andra. Klassen som implementerar AdvancedRepository måste implementera alla metoder från båda föräldragränssnitten. Multiplelv nedärvning är endast tillåten för gränssnitt.
Funktionellt gränssnitt — ett gränssnitt med en abstrakt metod (SAM — Single Abstract Method). Annotationen @FunctionalInterface garanterar denna begränsning. Exempel: Runnable, Callable, Comparator, Consumer. Funktionella gränssnitt är grunden för lambda-uttryck i Java 8: () -> System.out.println() implementerar Runnable.
Default-metoder gör det möjligt att lägga till nya metoder i ett gränssnitt utan att ändra alla implementerande klasser. Till exempel lade Java 8 till stream() i Collection som en default-metod. Utan denna mekanism skulle foreach(), stream() och andra metoder ha krävt ändring av tusentals klasser i JDK. Default — ett bakåtkompatibelt sätt att utvidga.
Kotlin förbjuder multiplelv nedärvning av klasser men tillåter multipel implementering av gränssnitt. Om två gränssnitt har en metod med samma signatur och standardimplementering, kräver kompilatorn explicit överskrivning med anrop av super<InterfaceName>.method(). Detta löser diamond problem på kompileringsnivå.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också