Acerca de mí
Pedro Joya
Docente de Ciclos Formativos de Formación Profesional de Informática en el IES Saladillo de Algeciras
Profesor de desarrollo de aplicaciones en Android
Apasionado de Kotlin
twitter: @pedrojoyamanez
¿Qué es Kotlin?
Lenguaje de programación creado por JetBrains
Inspirado en otros lenguajes: Java, C#, Scala, Groovy, Ruby
Podemos usarlo para desarrollo mobile, web front-end y back-end (Spring, Android, web, nativo)
¿Por qué Kotlin?
Sintaxis simple y concisa
Curva de aprendizaje suave
Interoperable con Java. Compila hacia JVM bytecode
Lenguaje moderno, conciso y con características de lenguajes funcionales
Google lo ha elegido como lenguaje prioritario para programación en Android
Show me the code
El ; es opcional
String name = "Baldomero" ;
val name: String = "Baldomero"
En Kotlin el ; es opcional, excepto en un caso específico de los enums
val vs. var
final String name = "Baldomero" ;
int age = 45 ;
val name: String = "Baldomero"
var age: Int = 45
val define una variable que no puede referenciar ningún otro objeto en el futuro.
var define una varible que puede referenciar distintos objetos a lo largo de su vida
Inferencia de tipos
final String name = "Baldomero" ;
var name = "Baldomero" ;
Tipo inferido (deducido) a partir del tipo de la expresión de inicialización
Lenguaje fuertemente tipado de tipo estático, aunque haya sido inferido (tiempo de compilación)
Tipos referenciales, no primitivos
val value: Int = 146
val percent = value.coerceIn(0 , 100 )
No exiten los tipos primitivos, todos son clases.
Internamente se usará un tipo primitivo cuando sea posible, con boxing y unboxing automático
No hay conversión automática de tipos numéricos, sino funciones de conversión explícita en los tipos, como toLong(), toInt(), etc.
Template Strings (interpolación)
final String s = "abc" ;
System.out.println(s + ".length is " + s.length());
val s = "abc"
println("$s .length is ${s.length} " )
$variable para interpolar el valor de una variable
${expresion} para interpolar el valor de cualquier expresión
Raw Strings
final String artist = "Chiquito de la Calzada" ;
final String saying = "No te digo trigo...\n" +
"por no llamarte Rodrigo\n" +
"(" + artist + ")" ;
val artist = "Chiquito de la Calzada"
val saying = """
|No te digo trigo
|por no llamarte Rodrigo
|($artist )
""" .trimMargin()
Literal de cadena sin caracteres de escape (\n, \t, … )
Puede contener interpolación.
Se propuso para Java 12, pero fue pospuesto.
if else es una expresión
val max = if (a > b) {
print("a es el máximo" )
a
} else {
print("b es el máximo" )
b
}
Las estructura if else es una expresión, que se evalúa al valor al que se evalúa la rama ejecutada
Una rama se evalúa al valor al que se evalúa la última expresión de la rama
No hay operador ternario
final int max = a > b ? a : b;
val max = if (a > b) a else b
En este caso Kotlin puede resultar más verboso que Java
Estructura for
for (int i = 1 ; i <= 10 ; i++)
System.out.println(i);
for (i in 1. .10 )
println(i)
Kotlin no posee la estructura for tradicional de Java
Sólo for sobre iterador (for each)
Podemos usar un literal de rango de valores para iterar sobre él
Range
for (int i = 1 ; i < 10 ; i++)
System.out.println(i);
for (int i = 6 ; i >= 0 ; i-=2 )
System.out.println(i);
for (i in 1 until 10 )
println(i)
for (i in 6 downTo 0 step 2 )
println(i)
La clase Range modela un rango de datos y posee un iterador
Podemos usar los métodos until, downTo y step para crear el rango adecuado
when como switch mejorado
when (input) {
1 -> println("Uno" )
7 , 8 -> println("Siete u ocho" )
in 10. .19 -> println("Decena" )
is String -> println("Cadena de ${input.length} " )
else -> {
println("Otra cosa" )
println("Lo sentimos" )
}
}
No necesita break
Más de un valor de comparación en cada rama
Comprobación de pertenencia o no a un rango, array o lista
Comprobación de pertenencia o no a un tipo
when es una expresión
val semester = when (month) {
in 1. .6 -> "Primer semestre"
in 7. .12 -> "segundo semestre"
else -> "Mes no válido"
}
Devuelve el valor al que se evalúa la rama seleccionada, que corresponde al valor al que se evalúa su última expresión
Debe ser exhaustivo (abarcar todas las posibilidades)
Java 12 incorpora una versión preliminar de switch mejorado con funcionalidad similar
when como if else if
if (x.isOdd())
System.out.println("x es impar" );
else if (x.isEven())
System.out.println("x es par" );
else
System.out.println("x está indecisa" );
when {
x.isOdd() -> println("x is impar" )
x.isEven() -> println("x is par" )
else -> println("x está indecisa" )
}
Las ramas se consideran expresiones booleanas y se ejecutará la primera que sea verdadera
También puede usarse como expresión
Hydration break
Definición de funciones
String greet (String name, String message) {
return message + ", " + name;
}
fun greet (name: String , message: String ) : String {
return "$message , $name "
}
fun is
El tipo de retorno se coloca al final, pero puede ser inferido
Funciones que no retornan "nada"
void greet (String name, String message) {
System.out.println(message + ", " + name);
}
fun greet (name: String , message: String ) {
println("$message , $name " )
}
El tipo de retorno es Unit, pero podemos omitirlo
Unit es un tipo, no una palabra reserva como void
La función retorna en realidad el objeto Unit (singleton)
Funciones con una única expresión
String greet (String name, String message) {
return message + ", " + name;
}
fun greet (name: String , message: String ) = "$message , $name "
El tipo de retorno es inferido a partir del tipo de la expresión, aunque puede ser especificado explícitamente
Argumentos con valores por defecto
String greet (String name) {
return greet(name, "Hola" );
}
String greet (String name, String message) {
return message + ", " + name;
}
fun greet (name: String , message: String = "Hola" ) =
"$message , $name "
Permiten mejorar una API existente fácilmente
El valor por defecto de un parámetro puede usar el valor de un argumento anterior
En Java se simulan mediante sobrecarga de métodos
Named arguments
fun greet (name: String = "Baldomero" ,
message: String = "Hola" ) = "$message , $name "
println(greet(message="Quillo que" ))
println(greet(message="Quillo que" , name="Germán Ginés" ))
println(greet("Germán Ginés" , message="Quillo que" ))
Permite especificar sólo algunos argumentos
Permite cambiar el orden de los argumentos
Puede usarse en conjunción con el paso de argumentos posicional, empezando por éste
Esta funcionalidad NO existe en Java
varargs y spread operator
fun printStrings (vararg strings: String ) {
for (string in strings)
println(string)
}
val names = arrayOf("Baldomero" , "Germán Ginés" )
printStrings("Quillo que" , *names, "Na aquí" )
El operador * (operador de dispersión) delante de un array retorna la lista de valores del array separados por coma ,
Top level functions and properties
const val PI = 3.14
var isUserLoggedIn: Boolean = false
fun greet (name: String ) {
println("Hola $name " )
}
fun main () {
greet("Baldomero" )
isUserLoggedIn = true
println("El número PI vale $PI " )
}
Funciones fuera de cualquier clase. Ejemplo: main
Propiedades fuera de cualquier clase.
const para constantes conocidas en tiempo de compilación, que el compilador usará de modo inline
Extension functions
public static String shout (String receiver) {
return "¡" + receiver.toUpperCase() + "!" ;
}
String greet = "Quillo que" ;
System.out.println(StringsUtils.shout(greet));
fun String.shout () : String = "¡${this.toUpperCase()} !"
val greet = "Quillo que"
println(greet.shout())
Llamada como si fuera un miembro de una determinada clase.
El IDE sugiere la función al escribir un . tras una variable de dicho tipo
Dentro de la función, this corresponde al receptor
Lambdas
fun operateAndPrint (x: Int = 0 , action: (Int ) -> Int ) : Int {
val result = action(x)
println("Result: $result " )
return result
}
val increm3 = { x: Int -> x + 3 }
operateAndPrint(2 , increm3)
Tipos función (vs. interf. func. de Java)
Expresión lambda = literal de objeto función, almacenable y pasable como argumento
Una lamba se ejecuta mediante el operador () o llamando a su método invoke()
La expresión lambda se evalúa al valor al que se evalúe la última expresión de su cuerpo
Lambdas como argumento
fun operateAndPrint (x: Int = 0 , action: (Int ) -> Int ) : Int {
val result = action(x)
println("Result: $result " )
return result
}
operateAndPrint(2 , { x -> x + 3 })
operateAndPrint(2 ) { x -> x + 3 }
operateAndPrint { x -> x + 3 }
operateAndPrint { it + 3 }
Inferencia de tipos en los parámetros de la lambda
Si lambda es último argumento, sacar de los ()
Si lambda es único argumento, nos ahorramos los ()
Si lambda tiene un solo argumento, usar it en cuerpo
Smart cast
if (obj instanceof String)
System.out.println(((String) obj).length());
if (obj is String)
println(obj.length)
El compilador hace el cast internamente dentro de la rama
Sólo puede aplicarse si la variable no puede cambiar entre la comprobación y el acceso
También en ramas de la estructura when y en comprobación de no null
Tipos nullable y no nullables
var name: String? = "Baldomero"
var sirname: String = "Llegate Ligero"
name = null
if (name != null )
println(name.length)
Nulabilidad dentro del sistema de tipos.
Detección de errores relacionados con null en tiempo de compilación
Tipo? permite null, Tipo no permite null. Tipo es un subtipo de Tipo?
Smart cast de Tipo? a Tipo tras comprobación de que la variable no es null
Jerarquía de tipos
Any es similar a Object
Cada tipo no nullable es subtipo del correspondiente nullable
Nothing es subtipo de todos los tipos
Nothing y throw como expresión
fun fail (message: String ) : Nothing {
throw IllegalStateException(message)
}
val fruitPerGlass: Int =
if (glassesSold > 0 ) orangesUsed / glassesSold
else throw NoGlassesSold()
El tipo Nothing indica que la función nunca terminará satisfactoriamente y por tanto no retornará
Es subtipo de todos los tipos
throw es una expresión que retorna Nothing
Operador de acceso seguro
final Integer length =
nickname != null ? nickname.length() : null ;
final String name =
nickname != null ? nickname.toUpperCase() : null ;
val length: Int ? = nickname?.length
val name = nickname?.toUpperCase()
No se permite usar el operador . con variables de tipo nullable (acceso seguro)
Se debe usar ?. en vez de . para acceder a propiedades y métodos
Se evalúa a null si la variable contiene null
Operador elvis
final int length =
nickname != null ? nickname.length() : 0 ;
val length: Int = nickname?.length ?: 0
Devuelve la expresión de la izquierda si ésta es distinta de null y la de la derecha en caso contrario
Funciona como un valor por defecto si la expresión es null
Operador bang bang
String name = "Baldomero" ;
final Integer length = name.length();
var name: String? = "Baldomero"
val lenght = name!!.length
Produce NullPointerException si la variable es null
Se recomienda sólo usarlo cuando estemos completamente seguros de que no es null.
let
final String name = "Baldomero" ;
if (name != null ) {
System.out.println("Name: " + name);
}
val name: String? = "Baldomero"
name?.let {
println("Name: $it " )
}
Extension function, sobre cualquier tipo, que recibe como parámetro una expresión lambda
La lambda es ejecutada pasándole como argumento el objeto sobre el que se ejecuta let
Retorna el valor retornado por la expresión lambda.
also
final Student student = new Student ();
student.setAge(45 );
student.setAddress("c/ Avda. Duque de Rivas, 1" )
val student: Student = Student().also {
it.age = 45
it.address = "c/ Avda. Duque de Rivas, 1"
}
Similar a let pero retorna el propio objeto sobre el que se ejecuta also
Se puede usar para configurar un objeto al crearlo antes de retornarlo (patrón builder)
Hydration break
Constructor primario
final class Student extends Person {
private final boolean isRepeater;
private int age;
public Student (@NotNull String name,
boolean isRepeater, int age) {
super (name);
this .isRepeater = isRepeater;
this .age = age;
}
}
class Student (name: String, val isRepeater: Boolean ,
var age) : Person(name)
Constructor primario definido en primera linea
Llamada al constructor primario de la superclase al definir la herencia
Bloque de inicialización
final class Student extends Person {
private final boolean isRepeater;
private int age;
public Student (@NotNull String name,
boolean isRepeater,
int age) {
super (name);
this .isRepeater = isRepeater;
this .age = age;
}
}
class Student (name: String,
val isRepeater: Boolean ,
var age: Int ) : Person(name) {
init {
}
}
Código de inicialización en bloque init.
Instanciación
final Student student = new Student ("Baldomero" ,
false , 23 );
val student = Student("Baldomero" , false , 23 )
No existe el operador new
Concepto de propiedad
final class Student extends Person {
private final boolean isRepeater;
private int age;
private int grade;
public Student (@NotNull String name,
boolean isRepeater,
int age) {
super (name);
this .isRepeater = isRepeater;
this .age = age;
}
public boolean isRepeater () { return isRepeater; }
public int getAge () { return age; }
public void setAge (int age) { this .age = age; }
public int getGrade () { return grade; }
public void setGrade (int grade) { this .grade = grade; }
}
Al conjunto formado por el campo (field), el getter y el setter se le conoce como propiedad
Propiedades en Kotlin
class Student (name: String,
val isRepeater: Boolean ,
var age: Int ) : Person(name) {
var grade: Int = 0
}
Definidas directamente en el constructor primario o en el cuerpo de la clase
Los parámetros del constructor sin val ni var NO crean propiedades. Son valores para inicialización de otras propiedades
Getter y setters personalizados
class Student (name: String, val isRepeater: Boolean , var age: Int ) : Person(name) {
var grade: Int = 0
set (value) {
if (value >= 0 ) field = value
else throw IllegalArgumentException()
}
val gradeDescription: String
get () = when (grade) {
in 0. .4 -> "Mal"
in 5. .7 -> "Aceptable"
in 8. .10 -> "Bien"
else -> "No válida"
}
}
field representa el campo interno creado
Podemos crear propiedades que no tienen ningún campo asociado
Acceso a propiedades
final Student student =
new Student ("Baldomero" , false , 25 );
System.out.println(student.getName());
student.setGrade(8 );
System.out.println(student.getGradeDescription());
val student = Student("Baldomero" , false , 25 )
println(student.name)
student.grade = 8
println(student.gradeDescription)
Se usa el operador . para acceder a una propiedad, tanto para lectura como para escritura.
Notación infix
public final class Pizza {
public final void add (@NotNull String ingredient) {
System.out.println(ingredient + " added to pizza" );
}
}
Pizza pizza = new Pizza ();
pizza.add("Cheese" );
class Pizza {
infix fun add (ingredient: String ) {
println("$ingredient added to pizza" )
}
}
val pizza = Pizza()
pizza add "Cheese"
Notación de operaciones aritméticas binarias
Método o extension function con un único parámetro
Concepto de data class
public final class User {
@NotNull private final String name;
private final int age;
public User (@NotNull String name, int age) { ... }
@NotNull public final String getName () { ... }
public final int getAge () { ... }
@NotNull public String toString () { ... }
public int hashCode () { ... }
public boolean equals (@Nullable Object var1) { ... }
}
Clase cuyo propósito principal es contener datos. Habitualmente implementan equals(), hashCode() y toString().
Data class en Kotlin
data class User (val name: String, val age: Int )
Implementaciones por defecto del constructor, getters de propiedades, setters para propiedades var, toString(), hashCode(), equals()
Se puede implementar explícitamente toString(), hashCode() y equals() en el cuerpo de la clase
Implementación del método copy() para crear un nuevo objeto a partir de uno existente
Implementación de métodos para desestructuración: component1(), component2(), etc.
Desestructuración
User jane = new User ("Jane" , 35 );
String name = jane.component1();
int age = jane.component2();
val jane = User("Jane" , 35 )
val (name, age) = jane
Asignación a lista de variables individuales, desde propiedades de un objeto (o elementos de colección)
Delegación
interface Base {
fun print ()
}
class Derived (b: Base) : Base by b {
}
Delegación y composición vs. herencia
El compilador generará automáticamente en la clase Derived el método print(), que internamente llamará a b.print()
Object
public final class RepositoryImp implements Repository {
public static final RepositoryImp INSTANCE =
new RepositoryImp ();
private RepositoryImp () { }
}
RepositoryImp repository = RepositoryImp.INSTANCE;
object RepositoryImp : Repository {
}
val repository = RepositoryImp.INSTANCE
Patrón singleton en modo eager con solo usar object en vez de class
Sealed classes
sealed class Post
data class Status (var text: String) : Post()
data class Image (var url: String, var caption: String) : Post()
data class Video (var url: String, var timeDuration: Int , var encoding: String): Post()
object Headline : Post()
Clase que sólo puede tener unas determinadas clases hijas predefinidas
Es abstracta por definición
Tanto la clase padres como las hijas deben estar definidas en el mismo fichero
No exite la palabra reservada static
class Person {
public static void callMe () { }
}
Person.callMe();
class Person {
companion object {
fun callMe () { }
}
}
Person.callMe()
Clases de utilidad no necesarias (extension functions y top level functions)
Companion object asociado a una clase
Sobrecarga de operadores
data class Point (var x: Double , var y: Double ) {
operator fun plus (point: Point ) =
Point(x + point.x, y + point.y)
}
val p1 = Point(2.9 , 5.0 )
val p2 = Point(2.0 , 7.5 )
val p3 = p1 + p2
Conjunto predefinido de operadores con representación simbólica; +, - , …
Al usar un operador internamente se llama al método asociado a él: a + b se traduce a a.plus(b)
Podemos definir el método asociado a un determinado operador en nuestras clases
Asociaciones de operadores
Expresión
Se traduce a
a + b, a - b, a * b
a.plus(b), a.minus(b), a.times(b)
a..b, a in b
a.rangeTo(b), b.contains(a)
a[i], a[i] = b
a.get(i), a.set(i, b)
a()
a.invoke()
a == b
a?.equals(b) ?: (b === null)
a > b
a.compareTo(b) > 0
a += b
a.plusAssign(b)
Colecciones mutables e inmutables
val inmutableList = listOf(1 , 2 , 3 )
val mutableList = mutableListOf(1 , 2 , 3 )
mutableList.add(4 )
Interfaces distintas para colecciones inmutables y mutables
Trabajo funcional con colecciones
List.of(4 , -1 , 2 , -8 )
.stream()
.filter(it -> it > 0 )
.map(it -> it * 2 )
.forEach(System.out::println);
listOf(4 , -1 , 2 , -8 )
.filter { it > 0 }
.map { it * 2 }
.forEach(::println)
Métodos de filtrado, transformación, reducción, etc. directamente en las colecciones
Por defecto eager aunque puede convertirse en lazy
lambdas y referencias a métodos
Otros cambios
Las clases y los métodos son públicos por defecto
Las clases y los métodos son final por defecto (usar open para abrirlos)
Las clases internas son static por defecto
protected es más restrictivo: sólo subclases
Modificador de acceso internal para visibilidad a nivel de módulo
Un fichero puede tener más de una clase pública
No existen las checked exceptions. Tampoco throws
Resume presentation
Kotlin Sacándole los colores a Java Pedro Joya Máñez https://hackmd.io/@pedrojoya/kotlin#/
{"metaMigratedAt":"2023-06-14T22:02:19.514Z","metaMigratedFrom":"YAML","title":"Kotlin sacándole los colores a Java","breaks":true,"description":"Presentación sobre Kotlin en comparación con Java","slideOptions":"{\"theme\":\"beige\",\"transition\":\"slide\",\"mouseWheel\":true,\"touch\":true,\"controlsLayout\":\"edges\",\"hideAddressBar\":true,\"keyboard\":true,\"overview\":false}","contributors":"[{\"id\":\"b3c7cbd8-c6bb-44e9-8380-34b896c77ea7\",\"add\":42685,\"del\":20618}]"}