() translation by (you can also view the original English article)
Kotlin es un lenguaje de programación moderno que compila a bytecode Java. Es libre y de código abierto, y promete hacer que programar para Android sea aún más divertido.
En el artículo anterior, aprendiste usos avanzados de funciones, como funciones de extensión, closures, funciones de orden superior, y funciones inline en Kotlin.
En este post recibirás una introducción a programación orientada a objetos en Kotlin, aprendiendo sobre clases: constructores y propiedades, casteo, y otras características de clases más avanzadas, que Kotlin logra simplificar.
1. Clases
Una clase es una unidad de un programa que agrupa funciones y datos para realizar ciertas tareas relacionadas. En Kotlin declaramos una clase utilizando la palabra reservada class
—de manera similar a Java.
1 |
class Book |
El código anterior es la forma más simple de declarar una clase—solamente creamos una clase vacía llamada Book
. Podemos instanciar esta clase aún si no contiene un cuerpo, utilizando su constructor por defecto.
1 |
val book = Book() |
Como puedes observar en el código aquí arriba, no utilizamos la palabra reservada new
para instanciar esta clase—como es usual en otros lenguajes de programación. new
no es una palabra reservada en Kotlin. Esto hace que nuestro código fuente sea conciso al crear una instancia de clase. Pero debes tener en cuenta que la instancia de una clase Kotlin en Java requerirá la palabra reservada new
.
1 |
// In a Java file
|
2 |
Book book = new Book() |
Constructores y Propiedades de Clase
Vamos a ver cómo agregarle un constructor y propiedades a nuestra clase. Pero primero, veamos una clase típica en Java:
1 |
/* Java */
|
2 |
public class Book { |
3 |
private String title; |
4 |
private Long isbn; |
5 |
public Book(String title, Long isbn) { |
6 |
this.title = title; |
7 |
this.isbn = isbn; |
8 |
}
|
9 |
public String getTitle() { |
10 |
return title; |
11 |
}
|
12 |
public void setTitle(String title) { |
13 |
this.title = title; |
14 |
}
|
15 |
public Long getIsbn() { |
16 |
return isbn; |
17 |
}
|
18 |
public void setIsbn(Long isbn) { |
19 |
this.isbn = isbn; |
20 |
}
|
21 |
}
|
Mirando nuestro modelo de clase Book
aquí arriba, tenemos lo siguiente:
- dos campos:
title
yisbn
- un único constructor
- getters y setters para ambos campos (afortunadamente, IntelliJ IDEA puede ayudarnos a generar estos métodos)
Ahora, echemos un vistazo a cómo podemos escribir el código precedente en Kotlin:
1 |
/* Kotlin */
|
2 |
class Book { |
3 |
var title: String |
4 |
var isbn: Long |
5 |
|
6 |
constructor(title: String, isbn: Long) { |
7 |
this.title = title |
8 |
this.isbn = isbn |
9 |
}
|
10 |
}
|
¡Una clase muy prolija! Hemos reducido el número de líneas de código de 20 a sólo 9. La función constructor()
se denomina constructor secundario en Kotlin. Este constructor es equivalente al constructor de Java que llamamos cuando instanciamos una clase.
En Kotlin, no existe en concepto de campo tal como lo conoces; en su lugar, emplea el concepto de "propiedades". Por ejemplo, tenemos dos propiedades mutables (lectura-escritura) declaradas con la palabra reservada var
: title
y isbn
en la clase Book
. (Si necesitas recordar el uso de variables en Kotlin, puedes visitar el primer post de esta serie: Variables, Tipos Básicos, y Arrays.
Algo maravilloso es que los getters y setters para estas propiedades son autogenerados para nosotros por el compilador Kotlin. Ten en cuenta que no especificamos ningún modificador de acceso para estas propiedades—así que por defecto, son públicas. En otras palabras, pueden ser accedidas desde cualquier parte.
Veamos otra versión de la misma clase en Kotlin:
1 |
class Book constructor(title: String, isbn: Long) { |
2 |
var title: String |
3 |
var isbn: Long |
4 |
|
5 |
init { |
6 |
this.title = title |
7 |
this.isbn = isbn |
8 |
}
|
9 |
}
|
En este código, removimos el constructor secundario. En su reemplazo, declaramos un constructor en el encabezado de la clase, denominado constructor primario. Un constructor primario no tiene lugar para insertar un bloque de código, por lo cual empleamos el modificador init
para inicializar parámetros de entrada del constructor primario. Fíjate que el bloque init
es ejecutado inmediatamente al crearse la instancia de clase.
Como puedes observar, nuestro código aún tiene bastante código innecesario. Vamos a recortarlo aún más:
1 |
class Book constructor(var title: String, var isbn: Long) |
Nuestra clase Book
ahora sólo tiene una línea de código. ¡Muy bueno! Debes notar que en la lista de parámetros del constructor primario, hemos definido nuestras propiedades mutables: title
y isbn
directamente dentro del constructor primario, con la palabra reservada var
.
También podemos agregar valores por defecto a cualquiera de las propiedades de la clase, directamente dentro del constructor.
1 |
class Book constructor(var title: String = "default value", var isbn: Long) |
De hecho, también podemos omitir la palabra constructor
, pero sólo si no tiene ningún modificador de acceso (public
, private
, o protected
), o ninguna annotation.
1 |
class Book (var title: String = "default value", var isbn: Long) |
¡Una clase muy ordenada, debo decir!
Ahora podemos crear una instancia de clase de la siguiente manera:
1 |
val book = Book("A Song of Ice and Fire", 9780007477159) |
2 |
val book2 = Book(1234) // uses the title property's default value |
Accediendo y Asignando Propiedades
En Kotlin, podemos obtener una propiedad con el objeto de clase book
, seguido de un punto separador .
, luego el nombre de la propiedad title
. Este estilo conciso de acceder propiedades se denomina sintaxis de acceso de propiedad. En otras palabras, no necesitamos llamar al método getter de la propiedad para accederla, o invocar al setter para asignar un valor a una propiedad en Kotlin—como hacemos en Java.
1 |
println(book.title) // "A Song of Ice and Fire" |
Como la propiedad isbn
está declarada con la palabra reservada var
(lectura-escritura), también podemos modificar el valor de la propiedad usando el operador de asignación =
.
1 |
book.isbn = 1234 |
2 |
println(book.isbn) // 1234 |
Veamos otro ejemplo:
1 |
class Book ( |
2 |
var title: String, |
3 |
val isbn: Long |
4 |
)
|
5 |
|
6 |
val book = Book("A Song of Ice and Fire", 9780007477159) |
7 |
book.isbn = 1234 // error: read-only property |
8 |
book.title = "Things Fall Apart" // reassigned title with value |
Aquí, actualizamos el parámetro isbn
para que sea inmutable (sólo lectura)—mediante el uso de la palabra val
. Creamos una instancia de clase book
y reasignamos la propiedad title
con el valor "Things Fall Apart". Fíjate que cuando intentamos reasignar el valor de la propiedad isbn
a 1234
, el compilador generó un error. Esto ocurre porque la propiedad es inmutable, al haber sido definida con la palabra val
.
Interoperabilidad con Java
Ten en cuenta que al declarar un parámetro con el modificador var
dentro del constructor primario, el compilador Kotlin (detrás de escena) nos ha ayudado a generar ambos accesores de propiedad: getter y setter, Si utilizas val
, sólo generará el getter.
1 |
/* Kotlin */
|
2 |
class Book ( |
3 |
var title: String, |
4 |
val isbn: Long |
5 |
)
|
Esto significa que cualquier invocador Java puede simplemente obtener o asignar la propiedad, invocando a los métodos getter o setter de la propiedad, respectivamente. Recuerda, esto depende del modificador empleado para definir la propiedad Kotlin: var
o val
.
1 |
/* Java */
|
2 |
Book book = new Book("A Song of Ice and Fire", 9780385474542) |
3 |
println(book.getTitle()) // "A Song of Ice and Fire" |
4 |
book.setTitle("Things Fall Apart") // sets new value |
5 |
println(book.getTitle()) // "Things Fall Apart" |
6 |
|
7 |
book.getIsbn() // 9780385474542 |
8 |
book.setIsbn(4545454) // won't compile |
Getters y Setters Personalizados
En esta sección, te enseñaré cómo crear accesores personalizados (getters y setters) para una propiedad en Kotlin, si así lo deseas. Crear un setter personalizado puede ser útil si deseas validar o verificar un valor antes de asignárselo a una propiedad de la clase. Y un getter personalizado puede servir cuando deseas cambiar o modificar el valor que debería ser devuelto.
Creando un Setter Personalizado
Como deseamos crear nuestro propio getter o setter personalizado para una propiedad, debemos definir dicha propiedad en el cuerpo de la clase, en lugar del encabezado del constructor.
1 |
class Book (val isbn: Long) { |
2 |
var title = "default value" |
3 |
}
|
Por este motivo hemos movido la propiedad mutable (lectura-escritura) title
dentro del cuerpo de la clase, y le hemos dado un valor por defecto (de otra manera, no compilaría).
1 |
class Book (val isbn: Long) { |
2 |
var title = "default value" |
3 |
set(value) { |
4 |
if (!value.isNotEmpty()) { |
5 |
throw IllegalArgumentException("Title must not be empty") |
6 |
}
|
7 |
field = value |
8 |
}
|
9 |
}
|
Puedes ver que hemos definido nuestro propio método setter set(value)
para title
justo debajo de la definición de la propiedad—ten en cuenta que no puedes modificar la firma de este método set()
porque es lo que el compilador espera como función setter personalizada de la propiedad.
El parámetro value
pasado al método set
representa el valor real que fue asignado a la propiedad por los usuarios—puedes modificar el nombre del parámetro si así lo deseas, pero value
es preferido. Validamos value
comprobando si el valor está vacío. Si lo está, detenemos la ejecución y arrojamos una excepción; de otra manera, reasignamos el valor a una variable especial field
.
Esta variable especial field
dentro del método set
es un alias para el campo de respaldo de la propiedad—un campo de respaldo es simplemente un campo utilizado por propiedades cuando deseas modificar o utilizar los datos de ese campo. Contrariamente a value
, no puedes renombrar esta variable especial field
.
Creando un Getter Personalizado
Es muy sencillo crear un getter personalizado para una propiedad en Kotlin.
1 |
class Book (val isbn: Long) { |
2 |
var title = "default value" |
3 |
//... set method
|
4 |
get() { |
5 |
return field.toUpperCase() |
6 |
}
|
7 |
}
|
Dentro del método get
, simplemente retornamos un field
modificado—en nuestro caso, retornamos el título del libro en mayúsculas.
1 |
val book = Book(9780007477159) |
2 |
book.title = "A Song of Ice and Fire" |
3 |
println(book.title) // "A SONG OF ICE AND FIRE" |
4 |
println(book.isbn) // 9780007477159 |
Ten en cuenta que cada vez que le asignamos un valor a la propiedad title
, se ejecuta su método set
—lo mismo sucede para el método get
cada vez que pedimos el valor de la propiedad.
Si deseas aprender más acerca de funciones miembro de las clases Kotlin (el tipo de función que se define dentro de una clase, objeto, o interfaz), visita el post Más Diversión Con Funciones perteneciente a esta serie.
Más Sobre Constructores
Como discutimos previamente, tenemos dos tipos de constructores en Kotlin: primarios y secundarios. Tenemos la libertad de combinar ambos en una sola clase—como puedes ver en el siguiente ejemplo:
1 |
class Car(val name: String, val plateNo: String) { |
2 |
var new: Boolean = true |
3 |
|
4 |
constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) { |
5 |
this.new = new |
6 |
}
|
7 |
}
|
Ten en cuenta que no podemos declarar propiedades dentro de un constructor secundario, como hicimos en el constructor primario. Si queremos hacerlo, tenemos que declararlas dentro del cuerpo de la clase y luego inicializarlas en el constructor secundario.
En el código de arriba, definimos el valor por defecto de la propiedad new
para la clase Car
(recuerda, new
no es una palabra reservada en Kotlin)—luego podemos utilizar el constructor secundario para cambiarlo si así lo queremos. En Kotlin, cada constructor secundario debe invocar al constructor primario—utilizamos la palabra reservada this
para lograrlo.
Fíjate además que podemos tener múltiples constructores secundarios dentro de una clase.
1 |
class Car(val name: String, val plateNo: String) { |
2 |
var new: Boolean? = null |
3 |
var colour: String = "" |
4 |
|
5 |
constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) { |
6 |
this.new = new |
7 |
}
|
8 |
|
9 |
constructor(name: String, plateNo: String, new: Boolean, colour: String ) : |
10 |
this(name, plateNo, new) { |
11 |
this.colour = colour |
12 |
}
|
13 |
}
|
Si una clase extiende una superclase, entonces podemos utilizar la palabra super
(de forma similar a Java) para invocar el constructor de la superclase (en un post futuro discutiremos la herencia en Kotlin).
1 |
// directly calls primary constructor
|
2 |
val car1 = Car("Peugeot 504", "XYZ234") |
3 |
// directly calls 1st sec. constructor
|
4 |
val car2 = Car("Peugeot 504", "XYZ234", false) |
5 |
// directly calls last sec. constructor
|
6 |
val car3 = Car("Peugeot 504", "XYZ234", false, "grey") |
Como dije anteriormente, para que podamos incluir explícitamente un modificador de visibilidad para un constructor de nuestra clase, tenemos que incluir la palabra reservada constructor
—por defecto, los constructores son públicos.
1 |
class Car private constructor(val name: String, val plateNo: String) { |
2 |
//...
|
Aquí, hicimos que el constructor sea privado—esto significa que los usuarios no pueden instanciar un objeto utilizando su constructor directamente. Esto puede ser útil si deseas que los usuarios llamen otro método en su lugar (un método factory) que se encargue de crear los objetos indirectamente.
2. Tipos Any y Nothing
En Kotlin, el tipo más alto en la jerarquía de tipos se llama Any
. Es equivalente al tipo Object
en Java. Esto significa que todas las clases en Kotlin heredan explícitamente del tipo Any
, incluyendo String
, Int
, Double
, y así sucesivamente. El tipo Any
contiene tres métodos: equals
, toString
, y hashcode
.
También podemos utilizar la clase Nothing
en Kotlin para funciones que siempre retornan una excepción—en otras palabras, para funciones que no terminan de forma normal. Cuando una función retorna Nothing
, sabemos que retornará una excepción. No existe un tipo equivalente en Java.
1 |
fun throwException(): Nothing { |
2 |
throw Exception("Exception message) |
3 |
}
|
Esto puede ser útil cuando testeamos el comportamiento del manejo de errores en tests unitarios.
3. Modificadores de Visibilidad
Los modificadores de visibilidad nos ayudan a restringir la accesibilidad de nuestra API al público. Podemos proveer diferentes modificadores de visibilidad a nuestras clases, interfaces, objetos, métodos, o propiedades. Kotlin nos provee cuatro modificadores de visibilidad:
Public
Este es el modificador por defecto, y cualquier clase, función, propiedad, interfaz, u objeto que posea este modificador puede ser accedido desde cualquier parte.
Private
Una función de nivel superior, interfaz, o clase que se declare como private
puede ser accedida solamente desde el mismo archivo.
Cualquier función o propiedad que se declare private
dentro de una clase, objeto, o interfaz, es visible solamente para otros miembros de la misma clase, objeto, o interfaz.
1 |
class Account { |
2 |
private val amount: Double = 0.0 |
3 |
}
|
Protected
El modificador protected
sólo puede aplicarse a propiedades o funciones dentro de una clase, objeto, o interfaz—no puede aplicarse a funciones de nivel superior, clases, o interfaces. Las propiedades o funciones que llevan este modificador sólo son accesibles desde la clase que las define y cualquier subclase.
Internal
En un proyecto que posee un módulo (Gradle o Maven), una clase, objeto, interfaz o función especificada con el modificador internal
declarado dentro de dicho módulo, sólo es accesible desde ese mismo módulo.
1 |
internal class Account { |
2 |
val amount: Double = 0.0 |
3 |
}
|
4. Casteo Inteligente
Casting significa tomar un objeto de cierto tipo y convertirlo en un objeto de otro tipo. Por ejemplo, en Java, utilizamos el operador instanceof
para determinar si un tipo de objeto particular es de otro tipo antes de castearlo.
1 |
/* Java */
|
2 |
if (shape instanceof Circle) { |
3 |
Circle circle = (Circle) shape; |
4 |
circle.calCircumference(3.5); |
5 |
}
|
Como puedes ver, verificamos si la instancia shape
es Circle
, y luego tenemos que castear explícitamente la referencia a shape
al tipo Circle
para que podamos invocar los métodos del tipo Circle
.
Otra cosa asombrosa de Kotlin es la inteligencia de su compilador cuando se trata de casting. Veamos un ejemplo en Kotlin.
1 |
/* Kotlin */
|
2 |
if (shape is Circle) { |
3 |
shape.calCircumference(3.5) |
4 |
}
|
¡Muy prolijo! El compilador es suficientemente inteligente para detectar que el bloque if será ejecutado solamente si el objeto shape
es una instancia de Circle
—por lo cual el mecanismo de casting se ejecuta detrás de escena para nosotros. Ahora podemos invocar fácilmente propiedades o funciones pertenecientes al tipo Circle
dentro del bloque if
.
1 |
if (shape is Circle && shape.hasRadius()) { |
2 |
println("Circle radius is {shape.radius}") |
3 |
}
|
Aquí, la última condición después de que &&
en el encabezado if
se invocará solo cuando la primera condición sea true
. Si shape
no es un Circle
, entonces la última condición no será evaluada.
5. Casting explícito
Podemos usar el operador as
(o el operador de conversión inseguro) para convertir explícitamente una referencia de un tipo a otro tipo en Kotlin.
1 |
val circle = shape as Circle |
2 |
circle.calCircumference(4) |
Si la operación de conversión explícita es ilegal, tenga en cuenta que se lanzará una ClassCastException
. Para
evitar que se produzca una excepción al lanzar, podemos usar el operador de conversión segura (o el operador de conversión con nulos) como as?
.
1 |
val circle: Circle? = shape as? Circle |
El operador as?
intentará convertir al tipo deseado, y devuelve null
si el valor no se puede convertir en lugar de arrojar una excepción. Recuerde que se discutió un mecanismo similar en la sección Nulability en la publicación Nulabilidad, Bucle y Condiciones en esta serie. Lea allí para un refresco.
6. Objetos
Los objetos en Kotlin son más similares a los objetos de JavaScript que los objetos de Java. Tenga en cuenta que un objeto en Kotlin no es una instancia de una clase específica.
Los objetos son muy similares a las clases. Estas son algunas de las características de los objetos en Kotlin:
- Pueden tener propiedades, métodos y un bloque
init
. - Estas propiedades o métodos pueden tener modificadores de visibilidad.
- No pueden tener constructores (primarios o secundarios).
- Pueden extender otras clases o implementar una interfaz.
Veamos ahora cómo crear un objeto.
1 |
object Singleton { |
2 |
|
3 |
fun myFunc(): Unit { |
4 |
// do something
|
5 |
}
|
6 |
}
|
Colocamos la palabra clave object
antes del nombre del objeto que queremos crear. De hecho, estamos creando singletons cuando creamos objetos en Kotlin utilizando la construcción object
, porque solo existe una instancia de un objeto. Aprenderá más sobre esto cuando analicemos la interoperabilidad de objetos con Java.
Un singleton es un patrón de diseño de software que garantiza que una clase solo tenga una instancia y que esa clase proporcione un punto de acceso global. Cada vez que varias clases o clientes solicitan la clase, obtienen la misma instancia de la clase. Puedes consultar mi publicación sobre el patrón singleton en Java para obtener más información al respecto.
Puede acceder al objeto o singleton en cualquier lugar de su proyecto, siempre que importe su paquete.
1 |
Singleton.myFunc() |
Si eres un codificador de Java, así es como normalmente creamos singletons:
1 |
public class Singleton { |
2 |
|
3 |
private static Singleton INSTANCE = null; |
4 |
|
5 |
// other instance variables can be here
|
6 |
|
7 |
private Singleton() {}; |
8 |
|
9 |
public static synchronized Singleton getInstance() { |
10 |
if (INSTANCE == null) { |
11 |
INSTANCE = new Singleton(); |
12 |
}
|
13 |
return(INSTANCE); |
14 |
}
|
15 |
|
16 |
// other instance methods can follow
|
17 |
}
|
Como puede ver, usar el constructo object
de Kotlin lo hace conciso y más fácil de crear singletons.
Los objetos en Kotlin se pueden utilizar también para crear constantes. Por lo general, en Java, creamos constantes en una clase convirtiéndola en un campo final público estático como este:
1 |
public final class APIConstants { |
2 |
|
3 |
public static final String baseUrl = "https://www.myapi.com/"; |
4 |
|
5 |
private APIConstants() {} |
6 |
}
|
Este código en Java se puede convertir a Kotlin más sucintamente así:
1 |
package com.chike.kotlin.constants |
2 |
|
3 |
object APIConstants { |
4 |
val baseUrl: String = "http://www.myapi.com/" |
5 |
}
|
Aquí declaramos los APIConstants
constantes con una propiedad baseUrl
dentro de un paquete com.chike.kotlin.constants
. Bajo el capó, un miembro final estático privado de Java baseUrl
se crea para nosotros y se inicializa con la cadena URL.
Para usar esta constante en otro paquete en Kotlin, simplemente importe el paquete.
1 |
import com.chike.kotlin.constants.APIConstants |
2 |
|
3 |
APIConstants.baseUrl |
Interoperabilidad Java
Kotlin convierte un objeto en una clase final de Java bajo el capó. Esta clase tiene un campo estático privado INSTANCE
que contiene una única instancia (un singleton) de la clase. El siguiente código muestra cómo simplemente los usuarios pueden llamar a un objeto Kotlin desde Java.
1 |
/* Java */
|
2 |
Singleton.INSTANCE.myFunc() |
Aquí, una clase Java llamada Singleton
se generó con un miembro público estático INSTANCE
, que incluye una función final pública myFunc()
.
Para hacer que la función o propiedad del objeto en Kotlin sea un miembro estático de la clase Java generada, usamos la anotación @JvmStatic
. He aquí cómo usarlo:
1 |
object Singleton { |
2 |
|
3 |
@JvmStatic fun myFunc(): Unit { |
4 |
// do something
|
5 |
}
|
6 |
}
|
Al aplicar la anotación @JvmStatic
a myFunc()
, el compilador lo convirtió en una función estática.
Ahora los llamadores de Java pueden llamarlo como una llamada de miembro estática normal. Tenga en cuenta que usar el campo estático INSTANCE
para llamar a los miembros seguirá funcionando.
1 |
/* Java */
|
2 |
Singleton.myFunc() |
7. Objetos complementarios
Ahora que hemos llegado a entender qué objetos hay en Kotlin, vamos a sumergirnos en otro tipo de objetos llamados objetos complementarios.
Debido a que Kotlin no admite clases, métodos o propiedades estáticos como los que tenemos en Java, el equipo de Kotlin nos proporcionó una alternativa más poderosa llamada objetos complementarios. Un objeto complementario es básicamente un objeto que pertenece a una clase; esta clase se conoce como la clase acompañante del objeto. Esto también significa que las características que mencioné para los objetos también se aplican a los objetos acompañantes.
Creando un objeto acompañante
De forma similar a los métodos estáticos en Java, un objeto complementario no está asociado con una instancia de clase, sino con la clase misma, por ejemplo, un método estático de fábrica, que tiene la función de crear una instancia de clase.
1 |
class Person private constructor(var firstName: String, var lastName: String) { |
2 |
|
3 |
companion object { |
4 |
fun create(firstName: String, lastName: String): Person = Person(firstName, lastName) |
5 |
}
|
6 |
}
|
Aquí hicimos que el constructor sea private
; esto significa que los usuarios fuera de la clase no pueden crear una instancia directamente. Dentro de nuestro bloque de objeto compañero, tenemos una función create()
, que crea un objeto Person
y lo devuelve.
Invocar una función de objeto complementario
La instanciación de objeto companion
es floja. En otras palabras, se creará una instancia solo cuando sea necesario la primera vez. La creación de instancias de un objeto companion
se produce cuando se crea una instancia de la clase companion
o se accede a los miembros companion
del objeto.
Veamos cómo invocar una función de objeto complementaria en Kotlin.
1 |
val person = Person.create("Cersei", "Lannister") |
2 |
println(person.firstName) // prints "Cersei" |
Como puede ver, esto es como invocar un método estático en Java como siempre. En otras palabras, simplemente llamamos a la clase y luego llamamos al miembro. Tenga en cuenta que, aparte de las funciones, también podemos tener propiedades dentro de nuestro objeto compañero.
1 |
class Person private constructor(var firstName: String, var lastName: String) { |
2 |
init { |
3 |
count++ |
4 |
}
|
5 |
|
6 |
companion object { |
7 |
var count: Int = 0 |
8 |
fun create(firstName: String, lastName: String): Person = Person(firstName, lastName) |
9 |
|
10 |
init { |
11 |
println("Person companion object created") |
12 |
}
|
13 |
}
|
14 |
|
15 |
}
|
Tenga en cuenta también que la clase companion
tiene acceso irrestricto a todas las propiedades y funciones declaradas en su objeto complementario, mientras que un objeto complementario no puede acceder a los miembros de la clase. Podemos tener un bloque de código init
dentro de un objeto companion
; esto se llama inmediatamente cuando se crea el objeto complementario.
1 |
Person.create("Arya", "Stark") |
2 |
Person.create("Daenerys", "Targaryen") |
3 |
println(Person.count) |
El resultado de ejecutar el código anterior será:
1 |
Person companion object created |
2 |
2 |
Recuerde, solo puede existir una sola instancia de un objeto companion
de clase.
También somos libres de proporcionarle un nombre a nuestro objeto compañero.
1 |
// ...
|
2 |
companion object Factory { |
3 |
var count: Int = 0 |
4 |
fun create(firstName: String, lastName: String): Person = Person(firstName, lastName) |
5 |
}
|
6 |
// ...
|
Aquí, le dimos un nombre llamado Factory
. Entonces podemos llamarlo así en Kotlin:
1 |
Person.Factory.create("Petyr", "Baelish") |
Este estilo es detallado, por lo que es preferible seguir el camino anterior. Pero esto podría ser útil al llamar a una función o propiedad de objeto complementaria desde Java.
Como dije anteriormente, al igual que los objetos, los objetos complementarios también pueden incluir propiedades o funciones, implementar interfaces e incluso extender una clase.
1 |
interface PersonFactory { |
2 |
fun create(firstName: String, lastName: String): Person |
3 |
}
|
4 |
|
5 |
class Person private constructor(var firstName: String, var lastName: String) { |
6 |
|
7 |
companion object : PersonFactory { |
8 |
override fun create(firstName: String, lastName: String): Person { |
9 |
return Person(firstName, lastName) |
10 |
}
|
11 |
}
|
12 |
}
|
Aquí, tenemos una interfaz PersonFactory
con una sola función create ()
. Al mirar nuestro nuevo objeto companion
modificado, ahora implementa esta interfaz (aprenderá sobre interfaces y herencia en Kotlin en una publicación posterior).
Interoperabilidad Java
Debajo del capó, los objetos complementarios se compilan de forma similar a como se compila un objeto Kotlin. En nuestro caso, se generan dos clases para nosotros: una clase final Person
y una clase final interna estática Person$Companion
.
La clase Person
contiene un miembro estático final llamado Companion
: este campo estático es un objeto de la clase interna Person$Companion
. La clase interna Person$Companion
también tiene sus propios miembros, y
uno de ellos es una función final pública llamada create()
.
Tenga en cuenta que no le dimos un nombre a nuestro objeto compañero, por lo que la clase interna estática generada fue Companion
. Si le hubiéramos dado un nombre, entonces el nombre generado sería el nombre que le dimos en Kotlin.
1 |
/* Java */
|
2 |
Person person = Person.Companion.create("Jon", "Snow"); |
Aquí, el objeto complementario en Kotlin no tiene nombre, por lo que usamos el nombre Companion
proporcionado por el compilador para que los llamadores de Java lo llamen.
La anotación @JvmStatic
aplicada en un miembro de objeto complementario funciona de manera similar a cómo funciona para un objeto normal.
Extensiones de objetos complementarios
De forma similar a cómo las funciones de extensión pueden ampliar la funcionalidad de una clase, también podemos extender la funcionalidad de un objeto complementario. (Si desea una actualización sobre las funciones de extensión en Kotlin, visite el tutorial de Funciones avanzadas en esta serie).
1 |
class ClassA { |
2 |
|
3 |
companion object { |
4 |
|
5 |
}
|
6 |
}
|
7 |
|
8 |
fun ClassA.Companion.extFunc() { |
9 |
// ... do implementation
|
10 |
}
|
11 |
|
12 |
ClassA.extFunc() |
Aquí, definimos una función de extensión extFunc()
en el objeto compañero ClassA.Companion
. En otras palabras, extfunc()
es una extensión del objeto complementario. Entonces podemos llamar a la extensión como si fuera una función miembro (¡no lo es!) Del objeto complementario.
Detrás de escena, el compilador creará una función de utilidad estática extFunc()
. El objeto receptor como argumento para esta función de utilidad es ClassA$Companion
.
Conclusión
En este tutorial, aprendió sobre clases y objetos
básicos en Kotlin. Cubrimos lo siguiente sobre clases:
- creación de clase
- constructores
- propiedades
- modificadores de visibilidad
- casteo inteligente
- casteo explícito
Además, aprendió cómo los objetos y objetos complementarios en Kotlin pueden reemplazar fácilmente sus métodos estáticos, constantes y códigos únicos codificados en Java. ¡Pero eso no es todo! Todavía hay más para aprender sobre las clases en Kotlin. En la próxima publicación, te mostraré aún más características interesantes que Kotlin tiene para la programación orientada a objetos. ¡Te veo pronto!
Para aprender más sobre el idioma de Kotlin, recomiendo visitar la documentación de Kotlin. ¡O echa un vistazo a algunas de nuestras otras publicaciones de desarrollo de aplicaciones de Android aquí en Envato Tuts+!
- SDK de AndroidJava vs. Kotlin: ¿Deberías estar usando Kotlin para el desarrollo de Android?Jessica Thornsby
- SDK de AndroidIntroducción a los componentes de la arquitectura de AndroidTin Megali
- SDK de AndroidCómo usar la API de Google Cloud Vision en aplicaciones de AndroidAshraff Hathibelagal
- SDK de Android¿Qué son las aplicaciones instantáneas de Android?Jessica Thornsby