¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Sergio Delgado 24/05/2017 Cargando comentarios…
Cuando empecé a escribir este artículo, hace unas semanas, os hablaba de la cantidad de frameworks, arquitecturas y lenguajes nuevos que salen cada día, y de la necesidad de separar el polvo de la paja antes de invertir nuestro tiempo en tratar de asimilar nuevas tecnologías.
Me lo había currado, pero el 17 de mayo de 2017, hace justo una semana, Google anunció que adoptaba Kotlin como lenguaje de primer nivel para el desarrollo Android. O sea, que ahora el mundillo se ha dividido entre los que gritan “lo sabía” y los que susurran “¿pero eso de qué va?”.
Sé que os pido un acto de fe, pero no puedo dejar de señalar que en Paradigma éramos del bando de los visionarios. Y, sin más introducción, empezamos por lo más básico…
Kotlin es un lenguaje de programación de tipado estático para la JVM, Android, el navegador y próximamente LLVM, 100% interoperable con Java, creado por Jetbrains, la compañía responsable, entre otros, de Intellij Idea.
Dicho así, parece otro lenguaje más para la JVM de los que a estas alturas ya hay tantos, ¿verdad? En cierto modo, así es, pero el diablo está en los detalles...
¿Quién no ha hecho alguna vez algo en JavaScript? Vamos, levantad la mano los que alguna vez hayais acariciado la idea de poner en el currículum eso de Full Stack Developer. ¿Y quién no la ha liado parda confundiéndose entre igualdades dobles y triples, o convirtiendo algún valor aleatorio en truthy y flipándolo al depurar las condiciones de sus if?
No me hace falta veros para saber que casi todas las manos siguen arriba (y el que la ha bajado es por vergüenza, ¡a mí no me engaña!).
JavaScript puede ser muchas cosas, pero si lo tuviera que definir con una palabra, ésta sería confuso. Y, antes de que se me enfaden los de front, ¿cómo se explica, sino, que el libro más recomendado sobre el lenguaje sea éste?
Mención especial al subtítulo: desenterrando la excelencia en JavaScript. Desenterrando. Poca broma.
El quid de la cuestión es que un lenguaje de programación, como cualquier otra herramienta, tiene que hacer lo correcto sencillo, y lo erróneo, más complicado. Como el señor Crockford, y otros quinientos vídeos en YouTube, nos demuestran parece que JavaScript se empeña a veces en hacerlo al revés. Y así nos va.
Pero que no se rían los javeros, que nosotros también tenemos nuestros diez mandamientos de cómo programar sin echarnos la soga al cuello…
El amigo Joshua Bloch se marcó aquí un libro imprescindible. Y el que lleve programando en Java diez o quince años y no se lo haya leído, que no se crea que no tiene nada que aprender de él, sino todo lo contrario. Ahora es cuando va a poder sacarle todo el jugo, cada vez que vea una solución a alguna metedura de pata de esas que todos tenemos en nuestra conciencia.
¿Qué significa para un lenguaje que le salgan libros tan imprescindibles como éstos? Que algo huele a podrido en Dinamarca. Sinceramente, creo que la máquina virtual de Java es la mejor opción para el desarrollo de software actualmente, un ecosistema insuperable y un lenguaje que recogió muchas buenas ideas y nos ha permitido llegar muy lejos. Pero, desengañémonos, el tiempo no pasa en balde y ahora tenemos más que claro que, además de buenas ideas, también hay decisiones de diseño claramente superadas.
Nuestras opciones son dos: aprendemos a programar estupendamente, invirtiendo tiempo y esfuerzo extra en ello, y evitamos coger nuestra herramienta por el lado que corta, o buscamos una herramienta mejor. Porque no basta con que tú hagas las cosas bien, sino que tienes que confiar en que el resto de tu equipo también lo haga… y que vuestras versiones de bien coincidan.
Que se puede, ojo, y lo hacemos muchos día a día. (En Paradigma, por supuesto, lo hacemos todos, ¡qué demonios!) No obstante, ¿por qué esforzarse más de lo necesario habiendo opciones mejores y, me atreveré a decirlo, más divertidas?
Eso es cierto, y sin ni siquiera salirnos de la JVM podríamos hablar de Jython, Groovy, Scala, Clojure, Ceylon, Xtend… O decidir que no nos importa atarnos a Microsoft y dedicarnos a C#, que salió de la sombra de Java para convertirse en un muy buen lenguaje con una plataforma con muy pocas posibilidades por desgracia.
Pero los de Jetbrains fueron muy listos cuando decidieron que el mantenimiento del código de sus productos era demasiado farragoso y que necesitaban un lenguaje mejor, ya que vieron un hueco en el mercado que nadie estaba cubriendo. Había sitio para una alternativa siempre que fuera:
Y, como colofón de la historia, en la keynote de la Google I/O 2017 saltó el bombazo: Google da soporte oficial a Kotlin como lenguaje de primer nivel para el desarrollo en Android.
Los gritos y los aplausos de los que la veíamos en streaming desde el Campus Madrid se tuvieron que oír desde la calle, y Twitter y Slack se convirtieron en una fiesta. Gente aplaudiendo a un lenguaje de programación, ojo. Casi nada.
Luego también anunciaron que, además de ser open source, donan Kotlin a una fundación sin ánimo de lucro y demás. Pero eso ya son cosas de abogados...
Vale, igual todavía no habéis visto ni una línea de código. Pero creo que era importante conocer el porqué de las cosas y, si he hecho bien mi trabajo, ahora estaréis deseosos de ver algo de chicha en lugar de leeros los ejemplos en diagonal.
¿Cómo es Kotlin? Kotlin es como coger el libro de Effective Java e implementar casi todas sus recomendaciones en el lenguaje. Pensad en todo lo que os dio dolores de cabeza en vuestro último proyecto y las vueltas que tuvisteis que dar para dejarlo bien. Ahora ved cómo dejarlo niquelado desde el minuto uno.
Disclaimer: El siguiente código no va a ser siempre tan idiomático como podría. La idea es que se entienda la diferencia, pero haceos un favor y no lo cojáis como ejemplo para ponerlo en producción.
¿A alguien más le pasa que más de la mitad de los errores que acaba resolviendo son NullPointerException en algún punto? ¿Cómo se sabe que un método puede devolver null o no? ¿Y se lo puedo pasar a un parámetro? Hoare dice sin complejos que la invención de null fue su error de los mil millones de dólares, y cincuenta años después sigue siendo una verdad como un templo.
En Java hay muchas formas de comprobarlo, incluyendo dos o tres librerías que compiten entre ellas con anotaciones @Null, @Nullable, @NotNull, etc. que, si te acuerdas de usarlas y el IDE que utilizas las parsea, te pueden salvar de algún patinazo. ¿Las usas en tus proyectos? ¿Y no son… um… un peñazo?
Podía ser peor, podía ser la opción oficial de Java 8, el tipo Optional. (Opción… Optional… ok, lo dejo.) O, como yo lo llamo, la gran oportunidad perdida de Java. Señores de Oracle, ¿tan difícil era darle un poco de azúcar sintáctico a esto para hacerlo un poco sencillo de usar? O, yo que sé, hacerlo Serializable. Llamadme loco, pero cuando se han montado flamewars sobre cuándo y cómo se debe usar Optional y, sobre todo, cuándo no, es que igual esto no está todo lo pulido que podría.
En fin, que al final lo que hacemos todos es mirar el Javadoc, rezar para que esté actualizado y, por último, sembrar el código de if.
No con Kotlin. Kotlin pasa de todo esto.
/**
* Típico ejemplo.
*
* @param nombre nombre a saludar, no puede ser null
*/
public void helloWorld(String nombre) {
// Le ponemos el trim para darle emoción al asunto
System.out.println("Hola, " + nombre.trim()); // NPE si viene null
}
public void helloWorldDefensivo(String nombre) {
if (nombre != null) {
System.out.println("Hola, " + nombre.trim());
}
}
public void helloWorldAnotado(@NotNull String nombre) {
System.out.println("Hola, " + nombre.trim());
}
public void helloWorldOpcional(Optional<String> nombre) {
// ¡Ojo, si usas Idea hasta el IDE se queja por usar Optional en un parámetro!
System.out.println("Hola, " + nombre.map(String::trim).orElse("mundo"));
}
// Un String no puede ser nulo jamás
fun helloWorld(nombre: String) {
println("Hola, " + nombre.trim())
}
// String? sí, pero el compilador obliga a comprobarlo antes de usarlo
fun helloWorldConIf(nombre: String?) {
if (nombre != null) {
println("Hola, " + nombre.trim())
}
}
// Hay llamadas seguras, que devuelven null si la variable es null
// Y un operador Elvis, que devuelve un valor por defecto si lo de la izquierda es null
fun helloWorldLlamadaSegura(nombre: String?) {
println("Hola, " + (nombre?.trim() ?: "mundo"))
}
// Por interoperabilidad, si un valor viene de Java se presupone nullable, pero el
// programador puede jurar que está seguro de que no... y si no es así, que se lance
// una NullPointerException
// Allá tú, si te parece buena idea
fun helloWorldPeligroso(nombre: String?) {
println("Hola, " + nombre!!.trim())
}
Otro clásico de los días perdidos depurando código. ¿Una variable se supone que se puede sobreescribir o no? Tenemos la opción de poner final a todo, pero es poner una palabra extra a, efectivamente, todo y la mayoría de las veces no se hace. Mal por nosotros.
Con Kotlin te tienes que decidir desde el principio. Y quizás te des cuenta de que la mutabilidad es el demonio, así por regla general. Bien por Kotlin.
final String inmutable = "Esto no se puede cambiar";
String mutable = "Esto sí, pero igual habíamos pensado que no, ¿no?";
val inmutable = "No se puede cambiar"
var mutable = "Esto sí, y digo explícitamente que espero que cambie"
Odio los JavaBeans. Los odio. Hay que escribir las variables, los getters y los setters y todo en Javadoc, copiando y pegando lo mismo cuatro veces. Pero se hace por si alguien quiere alguna lógica, me diréis. Vale, ¿y no podría haber una notación un poco más concisa?
public class BeanJava {
// El nombre
// Tranquilo si no te has enterado, ahora te lo repito unas cuantas veces
private String nombre;
/**
* Constructor.
*
* @param nombre nombre
*/
public BeanJava(String nombre) {
this.nombre = nombre;
}
/**
* Devuelve el nombre.
*
* @return nombre
*/
public String getNombre() {
return nombre;
}
/**
* Escribe el nombre.
*
* @param nombre nombre
*/
public void setNombre(String nombre) {
this.nombre = nombre;
}
}
/\*\*
\* Bean con un \[nombre\].
\*/
class BeanKotlin {
var nombre: String? = null
}
/\*\*
\* Bean con un \[nombre\] cuya primera letra se devuelve en mayúscula.
\*/
class BeanKotlinLogica {
var nombre: String? = null
// ¿Queremos añadir lógica? Sin problema
get() = nombre?.capitalize()
}
/\*\*
\* Bean con un \[nombre\] que se inicializa con un parámetro del constructor.
\*/
class BeanKotlinConstructor(var nombre: String)
fun pruebaDeUso() {
// Se usa así de fácil
val bean: BeanKotlin = BeanKotlin()
bean.nombre = "sergio"
println(bean.nombre) // sergio
// ¿El getter tiene lógica? ¡Y a mí qué!
// Os dais cuenta de que podéis meterla a posteriori sin tocar ni una coma del
// resto del código, ¿verdad?
val bean2: BeanKotlinLogica = BeanKotlinLogica()
bean2.nombre = "sergio"
println(bean2.nombre) // Sergio
val bean3: BeanKotlinConstructor = BeanKotlinConstructor("Sergio")
println(bean3.nombre)
}
En Java hay tipos primitivos y objetos. Esto lo hicieron en Sun por optimización. ¿Sabéis quiénes son mejores que las personas a la hora de analizar código y optimizarlo? Los compiladores, eso es. En Kotlin todo dato es un objeto, y deriva de Any?. Los arrays también. No hay casos especiales.
¿Es obvio de qué tipo es una variable o una función? Pues no lo pongas si no quieres.
var cadena = "Soy un String"
Llevas programando años y años, y todavía te equivocas a veces poniendo == en lugar de equals, o viceversa. En Kotlin ambas cosas son lo mismo.
AbstractMap.SimpleEntry<Integer, String> peras =
new AbstractMap.SimpleEntry<>(2, "frutas");
AbstractMap.SimpleEntry<Integer, String> manzanas =
new AbstractMap.SimpleEntry<>(2, "frutas");
System.out.println(peras.equals(manzanas)); // True
System.out.println(peras == manzanas); // False
val peras = Pair(2, “frutas”)
val manzanas = Pair(2, “frutas”)
println(peras.equals(manzanas)) // True
println(peras == manzanas) // True
println(peras === manzanas) // El == de Java, False en este caso
¿Queremos en Java un valor por defecto en un parámetro? ¡Bienvenidos a la fiesta del polimorfismo!
public void helloWorld(String sustantivo, String adjetivo) {
// Todos sabemos que la concatenación es poco eficiente
System.out.println(String.format("Hola, %s %s", sustantivo, adjetivo));
}
public void helloWorld() {
helloWorld("mundo", "cruel");
}
public void helloWorld(String sustantivo) {
helloWorld(sustantivo, "anodino");
}
// ¿Quieres una versión con sólo el adjetivo? Eso sería otro método con un parámetro String.
// No se puede.
// Hay que cambiarle el nombre al método.
public void helloWorldFeo(String adjetivo) {
helloWorld("cosa", adjetivo);
}
fun helloWorld(sustantivo: String ="mundo", adjetivo: String="cruel") {
println("Hola, " + sustantivo + " " + adjetivo)
}
fun pruebaDeUso() {
helloWorld("planeta") // Hola, planeta cruel
}
Espera, pero al ejemplo anterior le faltaba algo. ¿Se puede llamar al método para dejar el sustantivo como está y sólo cambiar el adjetivo? Claro que sí, guapi.
fun pruebaDeUso() {
helloWorld(adjetivo = "feliz") // Hola, mundo feliz
}
Ok, hemos visto propiedades, inferencia de tipos, valores por defecto y parámetros por nombre. Era todo parte de un plan para enseñaros las clases de datos ya que, si ordenara estos ejemplos por importancia, para mí estaría claramente en el Top 3 de Kotlin. Atentos.
Si ya definir en una clase propiedades te ahorra líneas y líneas de burocracia con getters, setters y documentación, pensemos en que al típico JavaBean, que queremos que se identifique por los datos que lleva dentro y ya está, hay que escribirle su equals, su hashCode y su toString.
Porque nunca se nos escapa comprobar que se respeta el contrato entre equals y hashCode, obviamente. Al fin y al cabo, hay librerías que te ayudan, y prácticamente todos los IDEs traen funciones para generarlos.
Eso sí, luego añadimos un campo nuevo, se nos olvida mantener estos dos métodos, colocamos una instancia en una colección y empieza la fiesta…
No nos quedemos aquí. ¿Los campos han de ser mutables o inmutables? ¿Creamos un constructor o un Builder? ¿Y un método para hacer copias? Podríamos decir que no nos hace falta y que ya veremos más adelante, pero eso es una receta segura para acabar haciendo spaghetti con el código, y lo sabéis. Mejor acordar una convención al principio, ¿no? ¿O Kotlin nos la podría ahorrar?
Ojo a esta comparativa, que es de las que duelen. Para ser honesto, podría usar Guava o Apache Commons, pero apenas me quitaría dos o tres líneas...
public class BeanJava {
private String nombre;
private String apellido;
public BeanJava(String nombre, String apellido) {
this.nombre = nombre;
this.apellido = apellido;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getApellido() {
return apellido;
}
public void setApellido(String apellido) {
this.apellido = apellido;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BeanJava)) return false;
BeanJava beanJava = (BeanJava) o;
if (nombre != null ? !nombre.equals(beanJava.nombre) : beanJava.nombre != null) return false;
return apellido != null ? apellido.equals(beanJava.apellido) : beanJava.apellido == null;
}
@Override
public int hashCode() {
int result = nombre != null ? nombre.hashCode() : 0;
result = 31 * result + (apellido != null ? apellido.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "BeanJava{" +
"nombre='" + nombre + '\'' +
", apellido='" + apellido + '\'' +
'}';
}
public BeanJavaBuilder copy() {
return new BeanJavaBuilder(nombre, apellido);
}
public static final class BeanJavaBuilder {
private String nombre = "José";
private String apellido = "García";
public BeanJavaBuilder() {
}
private BeanJavaBuilder(String nombre, String apellido) {
this.nombre = nombre;
this.apellido = apellido;
}
public static BeanJavaBuilder aBeanJava() {
return new BeanJavaBuilder();
}
public BeanJavaBuilder withNombre(String nombre) {
this.nombre = nombre;
return this;
}
public BeanJavaBuilder withApellido(String apellido) {
this.apellido = apellido;
return this;
}
public BeanJava build() {
BeanJava beanJava = new BeanJava(nombre, apellido);
return beanJava;
}
}
}
data class BeanKotlin(var nombre = "José", var apellido= "García")
Lo repito, la palabra data ha añadido ha añadido a la clase lo siguiente:
A lo que le sumamos, por pura sintaxis de Kotlin, los valores por defecto en los parámetros y la facilidad de llamar a los parámetros por nombre.
fun quieroUnHermanito() {
var sergio = BeanKotlin(“Sergio”, “Delgado”)
var bebe = sergio.copy(nombre=”Raúl”)
}
¡Todo esto en una línea! ¡Sin posibilidad de bugs! ¡Y siempre estará al día!
Por cierto, dijimos que la concatenación no molaba. Pero hasta ahora, lo he hecho en todos los ejemplos de Kotlin. Ha sido sólo por ir poco a poco. En realidad lo que escribiría es esto:
System.out.println(String.format("Hola, %s %s", sustantivo, adjetivo));
println("Hola, $sustantivo $adjetivo")
Recordad, hacer sencillo lo correcto. ¿Cuántas veces no pasamos del String.format porque luego no se entiende o simplemente por pereza?
No es sólo la interpolación, es que definir cadenas en Java se hace muy farragoso. Entre estas dos opciones, ¿qué versión os parece más legible?
String cadenaLarga = "Una línea\nDos líneas";
String otraCadenaLarga = "Para que se noten las líneas\n" +
"Partimos en varios literales\n" +
"Y concatenamos";
String json = “{\”nombre\”:\”Sergio\”,\n\”apellido\”:\”Delgado\”}”;
var cadenaLargaKotlin = """
Aquí no hay que escapar
Los saltos de línea están permitidos
Pero los espacios a la izquierda
Se cuelan en la variable
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.