Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Kotlin
Code

Kotlin Desde Cero: Clases y Objetos

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Kotlin From Scratch.
Kotlin From Scratch: Advanced Functions
Kotlin From Scratch: Advanced Properties and Classes

Spanish (Español) translation by Elías Nicolás (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.

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.

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.

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:

Mirando nuestro modelo de clase Book aquí arriba, tenemos lo siguiente:

  • dos campos: titleisbn
  • 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:

¡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:

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:

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.

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.

¡Una clase muy ordenada, debo decir!

Ahora podemos crear una instancia de clase de la siguiente manera:

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.

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 =.

Veamos otro ejemplo:

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.

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.

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.

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).

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.

Dentro del método get, simplemente retornamos un field modificado—en nuestro caso, retornamos el título del libro en mayúsculas.

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:

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.

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).

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.

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.

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.

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.

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.

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.

¡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.

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.

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?.

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.

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.

Si eres un codificador de Java, así es como normalmente creamos singletons:

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:

Este código en Java se puede convertir a Kotlin más sucintamente así:

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.

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.

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:

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.

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.

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.

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.

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.

El resultado de ejecutar el código anterior será:

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.

Aquí, le dimos un nombre llamado Factory. Entonces podemos llamarlo así en Kotlin:

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.

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.

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).

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+!

Advertisement
Advertisement
Advertisement
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.