Interface: esencia, contratos en Java y Kotlin para Android

Autor: IT Sectr Publicado: 2026-02-18 Tiempo de lectura: 9 min

Interface es un contrato que define un conjunto de métodos abstractos que una clase debe implementar. En Java y Kotlin, las interfaces son el mecanismo principal de abstracción y polimorfismo. En Java 8+, las interfaces pueden contener métodos default y static, en Kotlin — implementaciones por defecto. Según Google Android Developers (2025), las interfaces se usan en el 90% de los proyectos Android para definir capas de arquitectura — repositorios, UseCases y servicios.

Puntos clave

  • Interface es un tipo abstracto que define firmas de métodos sin implementación (antes de Java 8)
  • Implements es la palabra clave que vincula una clase con una interfaz; una clase puede implementar varias interfaces
  • Default method es un método con implementación en una interfaz Java, añadido en Java 8 para compatibilidad hacia atrás
  • Kotlin interface admite propiedades con getters e implementaciones de métodos, reemplazando abstract class en muchos escenarios
  • Markup interface es una interfaz vacía utilizada como marcador (Serializable, Cloneable, RandomAccess)

¿Qué es Interface en Java y Kotlin?

Interface es un tipo de referencia que contiene métodos abstractos, constantes y métodos por defecto. Una clase que implementa una interfaz debe proporcionar implementación para todos sus métodos abstractos. En Java, una interfaz no puede tener estado (campos de instancia); Kotlin también sigue esta restricción pero admite propiedades con accesores.

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;
    // getters y 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();
    }
    // otros métodos
}

Repository<T> es una interfaz genérica con métodos CRUD. El método default count() proporciona una implementación por defecto que puede ser sobrescrita. UserRepositoryImpl implementa la interfaz usando EntityManager para el acceso a datos. Este enfoque permite probar la capa de datos mediante un mock de la interfaz sin una base de datos real.

Interface vs abstract class: cuándo elegir cada uno

La elección entre una interfaz y una clase abstracta depende de la presencia de estado compartido y las relaciones entre tipos. Una interfaz define un contrato (lo que una clase puede hacer), una clase abstracta define una implementación común (lo que una clase es).

CriterioInterfaceAbstract Class
Estado (campos)Solo constantes static finalSí, cualquier campo
ConstructoresNo
Herencia múltipleSí (implements)No (extends uno)
Modificadores de accesopublic (Java 8-), métodos defaultTodos (private, protected, public)
Cuándo usarContrato para clases no relacionadasBase común para clases relacionadas

En Clean Architecture, las interfaces se colocan en la capa interna (domain), y las implementaciones en la capa externa (data). Esto permite mantener la Dependency Rule: la capa externa depende de la interna, pero no al revés. Las clases abstractas se usan más a menudo para métodos plantilla (Template Method pattern).

Interface en Kotlin: propiedades y delegados

Las interfaces de Kotlin son más flexibles que las de Java: pueden declarar propiedades abstractas y proporcionar implementaciones de métodos. A diferencia de Java, Kotlin admite la delegación mediante la palabra clave by, lo que reduce poderosamente el boilerplate al implementar el patrón Delegate.

Kotlin
interface ApiService {
    val baseUrl: String  // propiedad abstracta
    
    suspend fun fetchData(): Result>
    
    fun getEndpoint(path: String): String {
        return "$baseUrl/$path"  // implementación por defecto
    }
}

class RetrofitApiService(
    override val baseUrl: String
) : ApiService {
    private val client = Retrofit.Builder()
        .baseUrl(baseUrl)
        .build()
    
    override suspend fun fetchData(): Result> {
        // implementación de solicitud
    }
}

// Delegación mediante 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 implementa la interfaz Logger mediante delegación (by). Todas las llamadas a log() se redirigen al objeto logger sin escribir métodos envolventes. Este es un ejemplo de composición que en Java requeriría 5-10 líneas de código boilerplate.

Interface en Clean Architecture Android

Clean Architecture para Android separa claramente la aplicación en capas. Las interfaces actúan como límites entre capas: Domain define las interfaces de repositorios y UseCases, Data proporciona las implementaciones. Esto permite reemplazar implementaciones sin cambiar la lógica de negocio — una ventaja clave al migrar de Room a Firebase o de REST a GraphQL.

Kotlin
// Domain layer — interfaz (contrato)
interface UserRepository {
    suspend fun getUser(id: String): User
    suspend fun updateUser(user: User)
}

// Domain layer — use case (depende de la interfaz)
class GetUserUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(id: String): Result {
        return runCatching { repository.getUser(id) }
    }
}

// Data layer — implementación
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 es una interfaz en la capa domain. GetUserUseCase depende de la interfaz, no de la implementación. UserRepositoryImpl en la capa data implementa la interfaz, combinando API y base de datos local. Esta arquitectura permite probar UseCases con un repositorio mock sin configurar una base de datos o red.

Métodos default y static en Java 8+

Los métodos default se añadieron en Java 8 para la extensión evolutiva de interfaces sin romper la compatibilidad hacia atrás. Si ArrayList no implementara el nuevo método stream() añadido a Collection, el código antiguo seguiría funcionando. Los métodos static en interfaces sirven como utilidades relacionadas con la interfaz — una alternativa a las clases utilitarias.

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();
    }
    
    // constante
    String CATEGORY = "transport";
}

Los métodos default resuelven el diamond problem: si una clase implementa dos interfaces con el mismo método default, el compilador requiere una sobrescritura explícita. Los métodos static se llaman a través del nombre de la interfaz — Vehicle.of("car"), sin una instancia.

Errores comunes en el diseño de interfaces

Los errores en el diseño de interfaces conducen a código frágil, complejidad en las pruebas y violaciones de SOLID. Veamos tres problemas comunes.

Interface Pollution — demasiados métodos en una interfaz

Una interfaz que contiene 15+ métodos viola el Interface Segregation Principle (ISP). Un ejemplo es el antiguo java.util.Dictionary con 10+ métodos. Solución: dividir en varias interfaces pequeñas — ReadableRepository, WritableRepository, SearchableRepository. Un cliente (servicio) depende solo de los métodos que necesita.

Abstracción excesiva — interfaz para cada clase

Crear una interfaz para cada clase sin una necesidad real de polimorfismo es el antipatrón Interface overkill. Indicador: la interfaz tiene exactamente una implementación y el proyecto no tiene planes de añadir alternativas. Solución: añadir una interfaz solo cuando aparece una segunda opción de implementación o se necesita un mock para pruebas.

Preguntas frecuentes

¿En qué se diferencia interface de abstract class en Java?

Interface define solo un contrato (firmas de métodos), no puede tener estado y admite herencia múltiple. Abstract class puede contener campos, constructores y métodos implementados, pero una clase puede heredar solo una clase abstracta. Desde Java 8, las interfaces han obtenido métodos default y static, reduciendo la brecha.

¿Puede una interfaz heredar de otra interfaz?

Sí, en Java y Kotlin las interfaces admiten herencia. public interface AdvancedRepository<T> extends Repository<T>, Pageable es una interfaz que combina otras dos. Una clase que implementa AdvancedRepository debe implementar todos los métodos de ambas interfaces padre. La herencia múltiple solo está permitida para interfaces.

¿Qué es una interfaz funcional en Java?

Interfaz funcional es una interfaz con un único método abstracto (SAM — Single Abstract Method). La anotación @FunctionalInterface garantiza esta restricción. Ejemplos: Runnable, Callable, Comparator, Consumer. Las interfaces funcionales son la base de las expresiones lambda de Java 8: () -> System.out.println() implementa Runnable.

¿Por qué se necesitan métodos default en las interfaces de Java?

Los métodos default permiten añadir nuevos métodos a una interfaz sin modificar todas las clases que la implementan. Por ejemplo, Java 8 añadió stream() a Collection como método default. Sin este mecanismo, foreach(), stream() y otros métodos requerirían cambios en miles de clases del JDK. Default es una forma de extensión compatible hacia atrás.

¿Cómo resuelve Kotlin el problema de la herencia múltiple?

Kotlin prohíbe la herencia múltiple de clases pero permite la implementación múltiple de interfaces. Si dos interfaces tienen un método con la misma firma e implementación por defecto, el compilador requiere una sobrescritura explícita con la llamada super<InterfaceName>.method(). Esto resuelve el diamond problem a nivel de compilación.

Resumen

  • Interface es un contrato que define métodos que una clase debe implementar; el mecanismo principal de polimorfismo en Java y Kotlin
  • Java 8+ añadió métodos default y static a las interfaces, reduciendo la brecha con las clases abstractas
  • Kotlin interfaces admiten propiedades abstractas, implementaciones por defecto y delegación mediante by
  • Clean Architecture usa interfaces como límites entre las capas Domain y Data
  • Interface Segregation Principle requiere dividir interfaces grandes en especializadas
  • Interfaces funcionales (Single Abstract Method) son la base de las expresiones lambda y Stream API
  • Recomendación: añadir una interfaz cuando aparece una segunda implementación o se necesita un mock para pruebas

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también