Propiedades y campos
Declaración de propiedades
Las clases en Kotlin pueden tener características. Estos pueden ser declarados como mutable, utilizando la palabra clave var o de sólo lectura utilizando la palabra clave val.
class Address {
var name: String = ...
var street: String = ...
var city: String = ...
var state: String? = ...
var zip: String = ...
}
Para usar una propiedad, simplemente nos referimos a ella por su nombre, como si fuera un campo en Java:
fun copyAddress(address: Address): Address {
val result = Address() // there's no 'new' keyword in Kotlin
result.name = address.name // accessors are called
result.street = address.street
// ...
return result
}
Getters y Setters
La sintaxis completa para declarar una property es
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
El inicializador, getter y setter son opcionales. El tipo de propiedad es opcional si se puede inferir desde el inicializador (o desde el tipo de retorno getter, como se muestra a continuación).
var allByDefault: Int? // Error: Inicializador explícito requerido, getter y setter implícito
var initialized = 1 // Tiene tipo Int, por defecto getter y setter
La sintaxis completa de una declaración de propiedad de sólo lectura difiere de una mutable de dos maneras: comienza con val en lugar de var y no permite a un setter:
val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter
Podemos escribir descriptores personalizados, muy parecidos a las funciones ordinarias, dentro de una declaración de propiedad. He aquí un ejemplo de un getter personalizado:
val isEmpty: Boolean
get() = this.size == 0
Un setter personalizado se ve así:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
Por convención, el nombre del parámetro setter es value, pero puede elegir un nombre diferente si lo prefiere.
Desde Kotlin 1.1, puede omitir el tipo de property sí se puede inferir desde el getter:
val isEmpty get() = this.size == 0 // por inferencia, tipo Boolean
Si necesita cambiar la visibilidad de un descriptor o anotarlo, pero no necesita cambiar la implementación predeterminada, puede definir el descriptor sin definir su cuerpo:
var setterVisibility: String = "abc"
private set // the setter is private and has the default implementation
var setterWithAnnotation: Any? = null
@Inject set // annotate the setter with Inject
Campos de apoyo
Las clases en Kotlin no pueden tener campos. Sin embargo, a veces es necesario tener un campo de respaldo cuando se utilizan descriptores personalizados. Para estos propósitos, Kotlin proporciona un campo de respaldo automático al que se puede acceder mediante el identificador de field:
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}
El identificador de field sólo se puede utilizar en los accesores de la propiedad. Se generará un campo de respaldo para una propiedad si utiliza la implementación predeterminada de al menos uno de los accesores o si un accesor personalizado lo hace a través del identificador de campo.
Por ejemplo, en el caso siguiente no habrá campo de respaldo:
val isEmpty: Boolean
get() = this.size == 0
Propiedades de apoyo
Si desea hacer algo que no encaja en este esquema de campo de respaldo implícito, siempre puede volver a tener una propiedad de respaldo:
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // Type parameters are inferred
}
return _table ?: throw AssertionError("Set to null by another thread")
}
En todos los aspectos, esto es lo mismo que en Java, ya que el acceso a propiedades privadas con getters y setters predeterminados está optimizado para que no se introduzca ninguna sobrecarga de llamada de función.
Constante de tiempo de compilación
Las propiedades cuyo valor se conoce en tiempo de compilación se pueden marcar como constantes de tiempo de compilación usando el modificador const. Tales propiedades deben cumplir los siguientes requisitos:
Nivel superior o miembro de un objeto
Inicializado con un valor del tipo String o un tipo primitivo
Ningún getter personalizado
Tales propiedades se pueden utilizar en anotaciones:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
Propiedades con inicio tardío
Normalmente, las propiedades declaradas como de tipo no nulo deben ser inicializadas en el constructor. Sin embargo, con bastante frecuencia esto no es conveniente. Por ejemplo, las propiedades se pueden inicializar mediante inyección de dependencia o en el método de configuración de una prueba de unitaria. En este caso, no puede proporcionar que no sea NULL en el constructor, pero aún así desea evitar las comprobaciones nulas al referenciar la propiedad dentro del cuerpo de una clase.
Para manejar este caso, puede marcar la propiedad con el modificador lateinit:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
El modificador sólo se puede utilizar en propiedades var declaradas dentro del cuerpo de una clase (no en el constructor principal), y sólo cuando la propiedad no tiene un getter o Setter personalizado. El tipo de la propiedad debe ser no nulo y no debe ser un tipo primitivo.
El acceso a una propiedad lateinit antes de que se haya inicializado lanza una excepción especial que identifica claramente la propiedad a la que se accede y el hecho de que no se ha inicializado.
Sobrecarga de Propiedades
Propiedades delegadas
El tipo más común de propiedades simplemente lee desde (y tal vez escribe a) un campo de respaldo. Por otro lado, con getters y setters personalizados se puede implementar cualquier comportamiento de una propiedad. En algún lugar en el medio, hay ciertos patrones comunes de cómo una propiedad puede funcionar. Algunos ejemplos: valores perezosos, lectura de un mapa por una clave determinada, acceso a una base de datos, notificando al oyente en el acceso, etc.
Tales comportamientos comunes se pueden implementar como bibliotecas usando propiedades delegadas, que veremos mśa tarde.