Advertisement
  1. Code
  2. Kotlin

Kotlin Desde Cero: clases abstractas, interfaces, herencia y alias Type

Scroll to top
Read Time: 13 min
This post is part of a series called Kotlin From Scratch.
Kotlin From Scratch: Advanced Properties and Classes
Kotlin From Scratch: Exception Handling

() translation by (you can also view the original English article)

Kotlin es un lenguaje de programación moderno que compila a bytecode Java. Es gratuito y de código abierto, y promete hacer que la programacion para Android sea aún más divertida.

En el artículo anterior, aprendió más sobre las propiedades de Kotlin, como las propiedades de inicialización tardía, extensión y en línea. No solo eso, también aprendió sobre clases avanzadas como clases de datos, enumeración, anidadas y selladas en Kotlin.

En esta publicación, continuará aprendiendo sobre la programación orientada a objetos en Kotlin aprendiendo sobre clases abstractas, interfaces y herencia. Para obtener una bonificación, también aprenderá sobre los alias type.

1. Clases abstractas

Kotlin admite clases abstractas; al igual que Java, estas son clases de las que nunca intentará crear objetos. Una clase abstracta es incompleta o inútil sin algunas subclases concretas (no abstractas), desde las cuales puede instanciar objetos. Una subclase concreta de una clase abstracta implementa todos los métodos y propiedades definidas en la clase abstracta; de lo contrario, esa subclase también es una clase abstracta.

Creamos una clase abstracta con el modificador abstract (similar a Java).

1
abstract class Employee (val firstName: String, val lastName: String) {
2
    abstract fun earnings(): Double
3
}

Tenga en cuenta que no todos los miembros tienen que ser abstractos. En otras palabras, podemos tener la implementación predeterminada del método en una clase abstracta.

1
abstract class Employee (val firstName: String, val lastName: String) {
2
    // ... 

3
    
4
    fun fullName(): String {
5
        return lastName + " " + firstName;
6
    }
7
}

Aquí creamos la función no-abstracta fullName() en una clase abstracta Employee. Las clases concretas (subclases de la clase abstracta) pueden anular la implementación predeterminada de un método abstracto, pero solo si el método tiene el modificador open especificado (en breve conocerá más sobre esto).

También podemos almacenar el estado en clases abstractas.

1
abstract class Employee (val firstName: String, val lastName: String) {
2
    // ... 

3
    val propFoo: String = "bla bla"
4
}

Incluso si la clase abstracta no define ningún método, debemos crear una subclase antes de poder crear una instancia, como se muestra en el siguiente ejemplo.

1
class Programmer(firstName: String, lastName: String) : Employee(firstName, lastName) {
2
3
    override fun earnings(): Double {
4
        // calculate earnings

5
    }
6
}

Nuestra clase Programmer extiende la clase abstracta Employee. En Kotlin usamos un solo carácter de dos puntos (:) en lugar de la palabra clave extends de Java para extender una clase o implementar una interfaz.

Luego podemos crear un objeto de tipo Programmer y llamar a métodos, ya sea en su propia clase o en la superclase (clase base).

1
val programmer = Programmer("Chike", "Mgbemena")
2
println(programmer.fullName()) // "Mgbemena Chike"

Una cosa que podría sorprenderlo es que tenemos la capacidad de anular una propiedad val (inmutable) con var (mutable).

1
open class BaseA (open val baseProp: String) {
2
3
}
4
5
class DerivedA : BaseA("") {
6
7
    private var derivedProp: String = ""
8
9
    override var baseProp: String
10
        get() = derivedProp
11
        set(value) {
12
            derivedProp = value
13
        }
14
}

¡Asegúrate de usar esta funcionalidad sabiamente! Tenga en cuenta que no podemos hacer lo contrario: anular una propiedad var con val.

2. Interfaces

Una interfaz es simplemente una colección de métodos relacionados que normalmente le permiten decirle a los objetos qué hacer y también cómo hacerlo de forma predeterminada. (Los métodos predeterminados en las interfaces son una nueva característica agregada a Java 8). En otras palabras, una interfaz es un contrato que las clases de implementación deben cumplir.

Una interfaz se define utilizando la palabra clave interface en Kotlin (similar a Java).

1
class Result
2
class Student 
3
4
interface StudentRepository {
5
    fun getById(id: Long): Student
6
    fun getResultsById(id: Long): List<Result>
7
}

En el código anterior, hemos declarado una interfaz de StudentRepository. Esta interfaz contiene dos métodos abstractos: getById() y getResultsById(). Tenga en cuenta que incluir la palabra clave abstract es redundante en un método de interfaz porque ya están implícitamente abstractos.

Una interfaz es inútil sin uno o más implementadores, así que vamos a crear una clase que implementará esta interfaz.

1
class StudentLocalDataSource : StudentRepository {
2
    override fun getResults(id: Long): List<Result> {
3
       // do implementation

4
    }
5
6
    override fun getById(id: Long): Student {
7
        // do implementation

8
    }
9
}

Aquí creamos una clase StudentLocalDataSource que implementa la interfaz StudentRepository.

Usamos el modificador override para etiquetar los métodos y las propiedades que queremos redefinir desde la interfaz o superclase; esto es similar a la anotación @Override en Java.

Tenga en cuenta las siguientes reglas adicionales de interfaces en Kotlin:

  • Una clase puede implementar tantas interfaces como desee, pero solo puede extender una sola clase (similar a Java).
  • El modificador override es obligatorio en Kotlin, a diferencia de Java.
  • Junto con los métodos, también podemos declarar propiedades en una interfaz Kotlin.
  • Un método de interfaz Kotlin puede tener una implementación predeterminada (similar a Java 8).

Veamos un ejemplo de un método de interfaz con una implementación predeterminada.

1
interface StudentRepository {
2
    // ... 

3
    fun delete(student: Student) {
4
        // do implementation

5
    }
6
}

En el código anterior, agregamos un nuevo método delete() con una implementación predeterminada (aunque no agregué el código de implementación real para fines de demostración).

También tenemos la libertad de anular la implementación predeterminada si queremos.

1
class StudentLocalDataSource : StudentRepository {
2
    // ... 

3
    override fun delete(student: Student) {
4
       // do implementation

5
    }
6
}

Como se indicó, una interfaz Kotlin puede tener propiedades, pero tenga en cuenta que no puede mantener el estado. (Sin embargo, recuerde que las clases abstractas pueden mantener el estado). Por lo tanto, la siguiente definición de interfaz con una declaración de propiedad funcionará.

1
interface StudentRepository {
2
    val propFoo: Boolean // will work

3
    // ... 

4
}

Pero si intentamos agregar algún estado a la interfaz asignando un valor a la propiedad, no funcionará.

1
interface StudentRepository {
2
    val propFoo: Boolean = true // Error: Property initializers are not allowed in interfaces

3
    // .. 

4
}

Sin embargo, una propiedad de interfaz en Kotlin puede tener métodos de obtención y establecimiento (aunque solo este último si la propiedad es mutable). Tenga en cuenta también que la propiedad en una interfaz no puede tener un campo de respaldo.

1
interface StudentRepository {
2
    var propFoo: Boolean
3
        get() = true
4
        set(value)  {
5
           if (value) {
6
             // do something

7
           }
8
        }
9
    // ...

10
}

También podemos anular una propiedad de la interfaz si lo desea, para redefinirla.

1
class StudentLocalDataSource : StudentRepository {
2
    // ... 

3
    override var propFoo: Boolean
4
        get() = false
5
        set(value) {
6
            if (value) {
7
                
8
            }
9
        }
10
}

Veamos un caso en el que tenemos una clase que implementa múltiples interfaces con la misma firma de método. ¿Cómo decide la clase a qué método de interfaz llamar?

1
interface InterfaceA {
2
    fun funD() {}
3
}
4
5
interface InterfaceB {
6
    fun funD() {}
7
}

Aquí tenemos dos interfaces que tienen un método con la misma firma funD(). Vamos a crear una clase que implemente estas dos interfaces y reemplace el método funD().

1
class classA : InterfaceA, InterfaceB {
2
    override fun funD() {
3
        super.funD() // Error: Many supertypes available, please specify the one you mean in angle brackets, e.g. 'super<Foo>'

4
    }
5
}

El  compilador está confundido acerca de llamar al método super.funD() porque las dos interfaces que implementa la clase tienen la misma firma de método.

Para resolver este problema, envolvemos el nombre de la interfaz para la que queremos llamar al método entre paréntesis angulares <InterfaceName>. (IntelliJ IDEA o Android Studio te darán una pista sobre cómo resolver este problema cuando surja).

1
class classA : InterfaceA, InterfaceB {
2
    override fun funD() {
3
        super<InterfaceA>.funD()
4
    }
5
}

Aquí vamos a llamar al método funD() de InterfaceA. ¡Problema resuelto!

3. Herencia

Se crea una nueva clase (subclase) adquiriendo miembros de una clase existente (superclase) y tal vez redefiniendo su implementación predeterminada. Este mecanismo se conoce como herencia en la programación orientada a objetos (OOP). Una de las cosas que hacen a Kotlin tan impresionante es que abarca los paradigmas de programación funcional y OOP, todo en un solo lenguaje.

La clase base para todas las clases en Kotlin es Any.

1
class Person : Any {
2
}

El tipo Any es equivalente al tipo Object que tenemos en Java.

1
public open class Any {
2
    public open operator fun equals(other: Any?): Boolean
3
    public open fun hashCode(): Int
4
    public open fun toString(): String
5
}

El tipo Any contiene los siguientes miembros: equals(), hashcode(), y también métodos toString() (similares a Java).

Nuestras clases no necesitan extender explícitamente este tipo. Si no especifica explícitamente a qué clase se extiende una nueva clase, la clase extiende Any implícitamente. Por este motivo, normalmente no necesita incluir : Any ninguna en su código; lo hacemos en el código anterior para fines de demostración.

Veamos ahora cómo crear clases en Kotlin con la herencia en mente.

1
class Student {
2
3
}
4
5
class GraduateStudent : Student() {
6
7
}

En el código anterior, la clase GraduateStudent extiende la superclase Student. Pero este código no compilará. ¿Por qué? Porque las clases y los métodos son final por defecto en Kotlin. En otras palabras, no se pueden extender de forma predeterminada, a diferencia de Java, donde las clases y los métodos están abiertos de forma predeterminada.

La mejor práctica de ingeniería de software recomienda que comience a hacer que sus clases y métodos final de forma predeterminada, es decir. si no están específicamente destinados a ser redefinidos o anulados en las subclases. El equipo de Kotlin (JetBrains) aplicó esta filosofía de codificación y muchas otras mejores prácticas de desarrollo en el desarrollo de este lenguaje moderno.

Para permitir que se creen subclases a partir de una superclase, tenemos que marcar explícitamente la superclase con el modificador open. Este modificador también se aplica a cualquier propiedad o método de superclase que las subclases deberían invalidar.

1
open class Student {
2
3
}

Simplemente colocamos el modificador open antes de la palabra clave class. Ahora hemos dado instrucciones al compilador para que permita que nuestra clase Student esté abierta para extensión.

Como se indicó anteriormente, los miembros de una clase de Kotlin también son finales de forma predeterminada.

1
open class Student {
2
    
3
    open fun schoolFees(): BigDecimal {
4
       // do implementation

5
    }
6
}

En el código anterior, marcamos la función schoolFees como open, de modo que las subclases pueden anularla.

1
open class GraduateStudent : Student() {
2
3
    override fun schoolFees(): BigDecimal {
4
        return super.schoolFees() + calculateSchoolFees()
5
    }
6
7
    private fun calculateSchoolFees(): BigDecimal {
8
        // calculate and return school fees

9
    }
10
}

En este caso, la función open schoolFees de la superclase Student se anula por la clase GraduateStudent, agregando el modificador de override antes de la palabra clave fun. Tenga en cuenta que si reemplaza a un miembro de una superclase o interfaz, el miembro sobrescrito también estará open de forma predeterminada, como se muestra en el siguiente ejemplo:

1
class ComputerScienceStudent : GraduateStudent() {
2
3
    override fun schoolFees(): BigDecimal {
4
        return super.schoolFees() + calculateSchoolFess()
5
    }
6
7
    private fun calculateSchoolFess(): BigDecimal {
8
        // calculate and return school fees

9
    }
10
}

Aunque no marcamos el método schoolFees() en la clase GraduateStudent con el modificador open, todavía podemos anularlo, como hicimos en la clase ComputerScienceStudent. Para que podamos evitar esto, tenemos que marcar el miembro principal como final.

Recuerde que podemos agregar una nueva funcionalidad a una clase, incluso si es definitiva, mediante el uso de funciones de extensión en Kotlin. Para obtener una actualización de las funciones de extensión, echa un vistazo a mis funciones avanzadas en la publicación Kotlin. Además, si necesita un repaso sobre cómo dar a una clase final incluso nuevas propiedades sin heredarlas, lea la sección sobre Propiedades de extensión en mi publicación de Propiedades y clases avanzadas.

Si nuestra superclase tiene un constructor primario como este:

1
open class Student(val firstName: String, val lastName: String) {
2
    // ... 

3
}

Entonces, cualquier subclase tiene que llamar al constructor principal de la superclase.

1
open class GraduateStudent(firstName: String, lastName: String) : Student(firstName, lastName) {
2
    // ... 

3
}

Podemos simplemente crear un objeto de la clase GraduateStudent como de costumbre:

1
val graduateStudent = GraduateStudent("Jon", "Snow")
2
println(graduateStudent.firstName) // Jon

Si la subclase quiere llamar al constructor de superclase desde su constructor secundario, usamos la palabra clave super (similar a cómo se invocan los constructores de superclase en Java).

1
open class GraduateStudent : Student {
2
    // ...

3
    private var thesis: String = ""
4
    
5
    constructor(firstName: String, lastName: String, thesis: String) : super(firstName, lastName) {
6
        this.thesis = thesis
7
    }
8
}

Si necesita una actualización sobre los constructores de clases en Kotlin, visite la publicación de Mis clases y objetos.

4. Bonus: Tipo Alias

Otra cosa increíble que podemos hacer en Kotlin es dar un alias a un tipo.

Veamos un ejemplo.

1
data class Person(val firstName: String, val lastName: String, val age: Int)

En la clase anterior, podemos asignar los tipos String e Int  para los alias de propiedades Person utilizando el modificador typealias en Kotlin. Este modificador se utiliza para crear un alias de cualquier tipo en Kotlin, incluidos los que ha creado.

1
typealias Name = String
2
typealias Age = Int
3
4
data class Person(val firstName: Name, val lastName: Name, val age: Age) 

Como puede ver, hemos creado un alias Name y una Age para los tipos String y Int respectivamente. Ahora hemos reemplazado el tipo de propiedadfirstName y lastName por nuestro alias Name, y también el tipo Int al alias Age. Tenga en cuenta que no creamos ningún tipo nuevo, sino que creamos un alias para los tipos.

Estos pueden ser útiles cuando desea proporcionar un mejor significado o semántico a los tipos en su base de código de Kotlin. ¡Así que úsalos sabiamente!

Conclusión

En este tutorial, aprendiste más sobre la programación orientada a objetos en Kotlin. Cubrimos lo siguiente:

  • clases abstractas
  • interfaces
  • herencia
  • alias type

Si ha estado aprendiendo Kotlin a través de nuestra serie Kotlin Desde Cero, asegúrese de haber estado escribiendo el código que ve y ejecutándolo en su IDE. Un gran consejo para comprender realmente un nuevo lenguaje de programación (o cualquier concepto de programación) que esté aprendiendo es asegurarse de no solo leer el recurso o la guía de aprendizaje, sino también escribir el código real y ejecutarlo.

En el siguiente tutorial de la serie Kotlin Desde Cero, se le presentará el manejo de excepciones en Kotlin. ¡Te veo pronto!

Para obtener más información sobre el idioma Kotlin, recomiendo visitar la documentación de Kotlin. O echa un vistazo a algunas de nuestras otras publicaciones de desarrollo de aplicaciones para Android aquí en Envato Tuts!


Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.