Previous slide Next slide Toggle fullscreen Open presenter view
¿Quien soy?
Programo en Java desde Java 1.1.
Actualmente trabajo en https://clarity.ai como backender.
Mi primera charla en una conferencia.
Me encontraréis en:
Encuesta
Java 22?
Java 21?
Java 17?
Java 11?
Todavía Java 8?
Y anteriores a Java 8?
Jetbrains ecosystem survey
Java
Lanzado en Marzo 2014
No existía ni tiktok ni openai
Ese mismo año se lanzó spring-boot v1.0.
Docker y kubernetes daban sus primeros pasos
Agenda
El largo camino a Java 22.
Tipos de datos algebraicos.
Ejemplos.
Futuro.
Java Release Cadence
Dos release al año.
LTS.
Preview features.
They have a plan.
Switch expressions
var value = switch (input) {
case "a" -> 1 ;
case "b" -> 2 ;
default -> 0 ;
};
Incluido en Java 14.
Una nueva vida para switch
.
Expresión.
yield
.
yield
var value = switch (input) {
case "a" -> {
yield 1 ;
}
case "b" -> {
yield 2 ;
}
default -> {
yield 0 ;
}
};
Records
public record Movie (String title, int year, int duration) {
}
Incluido en Java 16.
Muy esperado por la comunidad.
Inmutables.
Constructor canónico.
Records: Constructor Canónico
public record Movie (String title, int year, int duration) {
public Movie {
if (title == null || title.isEmpty()) {
throw new IllegalArgumentException ();
}
}
}
Records: Constructor Canónico
public record Movie (String title, List<String> cast) {
public Movie {
cast = List.copyOf(cast);
}
}
Copia defensiva para evitar modificar el contenido del record.
Sealed classes and interfaces
public sealed interface Shape {
record Square (int side) implements Shape {}
record Rectangle (int weight, int height) implements Shape {}
record Circle (int radius) implements Shape {}
}
Incluido en Java 17.
Jerarquías de clases cerradas.
non-sealed
non-sealed
public sealed interface Shape {
record Square (int side) implements Shape {}
record Rectangle (int weight, int height) implements Shape {}
record Circle (int radius) implements Shape {}
non-sealed interface CustomShape extends Shape {}
}
Pattern matching for switch
var result = switch (obj) {
case Integer i -> String.format("int %d" , i);
case Long l -> String.format("long %d" , l);
case Double d -> String.format("double %f" , d);
case String s -> String.format("String %s" , s);
default -> obj.toString();
};
Null patterns
var result = switch (obj) {
case null -> "null" ;
case Integer i -> String.format("int %d" , i);
case Long l -> String.format("long %d" , l);
case Double d -> String.format("double %f" , d);
case String s -> String.format("String %s" , s);
default -> obj.toString();
};
Record patterns
var area = switch (this ) {
case Square (var side) -> side * side;
case Rectangle (var weight, var height) -> weight * height;
case Circle (var radius) -> Math.PI * Math.pow(radius, 2 );
};
Incluido en Java 21.
Deconstructores, nos permite acceder a los componentes de los objectos.
Exhaustiveness.
Guarded patterns
var result = switch (point) {
case Point (var x, var y) when y == 0 -> processX(x);
case Point (var x, var y) -> processXY(x, y);
};
Podemos añadir condiciones adicionales usando when
Nested patterns
var result = switch (this ) {
case Square (Point(var x, var y) , var side) -> ...;
case Rectangle (Point(var x, var y) , var weight, var height) -> ...;
case Circle (Point(var x, var y) , var radius) -> ...;
};
Unnamed variables and patterns
var result = switch (obj) {
case Integer _ -> "int" ;
case Long _ -> "long" ;
case Double _ -> "double" ;
case String _ -> "string" ;
default -> "other" ;
};
Incluido en Java 22.
Mejora para el pattern matching.
Eliminar verbosidad.
Tipos de datos algebraicos
AKA ADTs (algebraic data types).
Viene de las matemáticas.
Recursivo.
Productos y sumas de tipos.
a + b
a * b
Tienen propiedades algebraicas.
Otros lenguajes con soporte ADTs
JVM:
Rust
C#
TypeScript
Otros lenguajes funcionales:
ADTs
¿Cómo podemos representarlos en Java?
Un record
es un producto de tipos.
Un sealed interface
es una suma de tipos.
¿Pero eso para qué me sirve?
Estructuras de datos.
Estructuras de control.
DSLs
Manejo de errores.
ADTs: List
sealed interface List <T> {
record NonEmpty <T>(T head, List<T> tail) implements List <T> {}
record Empty <T>() implements List <T> {}
}
ADTs: List (map)
sealed interface List <T> {
default <R> List<R> map (Function<T, R> mapper) {
return switch (this ) {
case NonEmpty<T>(var head, var tail)
-> new NonEmpty <>(mapper.apply(head), tail.map(mapper));
case Empty<T> _ -> new Empty <>();
};
}
}
ADTs: List (filter)
sealed interface List <T> {
default List<T> filter (Predicate<T> filter) {
return switch (this ) {
case NonEmpty<T>(var head, var tail) when filter.test(head)
-> new NonEmpty <>(head, tail.filter(filter));
case NonEmpty<T>(var head, var tail)
-> tail.filter(filter);
case Empty<T> _ -> new Empty <>();
};
}
}
ADTs: List (fold)
sealed interface List <T> {
default T fold (T initial, BinaryOperator<T> operator) {
return switch (this ) {
case NonEmpty<T>(var head, var tail)
-> tail.fold(operator.apply(initial, head), operator);
case Empty<T> _ -> initial;
};
}
}
ADTs: Tree
sealed interface Tree <T> {
record Node <T>(T value, Tree<T> left, Tree<T> right) implements Tree <T> { }
record Leaf <T>(T value) implements Tree <T> {}
}
ADTs: Optional
sealed interface Optional <T> {
record Empty <T>() implements Optional <T> { }
record Present <T>(T value) implements Optional <T> {}
}
ADTs: Optional (map)
sealed interface Optional <T> {
default <R> Optional<R> map (Function<T, R> mapper) {
return switch (this ) {
case Present<T>(var value) -> new Present <>(mapper.apply(value));
case Empty<T> _ -> new Empty <>();
};
}
}
ADTs: Optional (filter)
sealed interface Optional <T> {
default Optional<T> filter (Predicate<T> filter) {
return switch (this ) {
case Present<T>(var value) when filter.test(value) -> this ;
case Present<T> _ -> new Empty <>();
case Empty<T> _ -> new Empty <>();
};
}
}
ADTs: Optional (fold)
sealed interface Optional <T> {
default <R> R fold (Supplier<R> onEmpty, Function<T, R> onPresent) {
return switch (this ) {
case Present<T>(var value) -> onPresent.apply(value);
case Empty<T> _ -> onEmpty.get();
};
}
}
ADTs: Either
sealed interface Either <L, R> {
record Left <L, R>(L left) implements Either <L, R> { }
record Right <L, R>(R right) implements Either <L, R> {}
}
ADTs: DSLs
sealed interface Expr {
record Val (int value) implements Expr {}
record Sum (Expr left, Expr right) implements Expr {}
record Diff (Expr left, Expr right) implements Expr {}
record Times (Expr left, Expr right) implements Expr {}
}
ADTs: DSLs
sealed interface Expr {
default int evaluate () {
return switch (this ) {
case Val (var value) -> value;
case Sum (var left, var right) -> left.evaluate() + right.evaluate();
case Diff (var left, var right) -> left.evaluate() - right.evaluate();
case Times (var left, var right) -> left.evaluate() * right.evaluate();
};
}
}
ADTs: DSLs
sealed interface Expr {
default String asString () {
return switch (this ) {
case Val (var value) -> String.valueOf(value);
case Sum (var left, var right) -> "(" + left.asString() + "+" + right.asString() + ")" ;
case Diff (var left, var right) -> "(" + left.asString() + "-" + right.asString() + ")" ;
case Times (var left, var right) -> "(" + left.asString() + "*" + right.asString() + ")" ;
};
}
}
ADTs: DSLs
sealed interface Expr {
default void print () {
System.out.println(asString() + "=" + evaluate());
}
}
ADTs: DSLs
static void main () {
sum(val(1 ), val(2 )).print();
times(diff(val(10 ), val(8 )), val(2 )).print();
}
(1+2)=3
((10-8)*2)=4
ADTs: Json
sealed interface Json {
enum JsonNull implements Json { NULL }
enum JsonBoolean implements Json { TRUE, FALSE }
record JsonString (String value) implements Json {}
record JsonNumber (Number value) implements Json {}
record JsonObject (Map<String, Json> value) implements Json {}
record JsonArray (List<Json> value) implements Json {}
}
ADTs: Json
sealed interface Json {
default String asString () {
return switch (this ) {
case JsonNull _ -> "null" ;
case JsonBoolean b -> switch (b) {
case TRUE -> "true" ;
case FALSE -> "false" ;
};
...
};
}
}
ADTs: Json
sealed interface Json {
default String asString () {
return switch (this ) {
...
case JsonString (var value) -> "\"" + value + "\"" ;
case JsonNumber (var value) -> String.valueOf(value);
...
};
}
}
ADTs: Json
sealed interface Json {
default String asString () {
return switch (this ) {
...
case JsonObject (var map)
-> map.entrySet().stream()
.map(entry -> "\"" + entry.getKey() + "\":" + entry.getValue().asString())
.collect(joining("," , "{" , "}" ));
...
};
}
}
ADTs: Json
sealed interface Json {
default String asString () {
return switch (this ) {
...
case JsonArray (var array)
-> array.stream()
.map(Json::asString)
.collect(joining("," , "[" , "]" ));
};
}
}
ADTs: Json
static void main () {
var json = array(
object(
entry("name" , string("Toni" )),
entry("age" , number(46 )),
entry("old" , JsonBoolean.TRUE)),
object(
entry("name" , string("Baby" )),
entry("age" , JsonNull.NULL),
entry("old" , JsonBoolean.FALSE))
);
System.out.println(json.asString());
}
ADTs: Json
[ { "old" : true , "name" : "Toni" , "age" : 46 } , { "old" : false , "name" : "Baby" , "age" : null } ]
ADTs: Errores
interface MovieRepository {
MovieResponse create (Movie movie) ;
}
sealed interface MovieResponse permits MovieCreated, MovieError {}
record MovieCreated (UUID id) implements MovieResponse {}
ADTs: Errores
sealed interface MovieError extends MovieResponse {
record DuplicatedMovie (UUID id) implements MovieError {}
record InvalidDuration (int duration) implements MovieError {}
record InvalidYear (int year) implements MovieError {}
record InvalidStars (int stats) implements MovieError {}
record EmptyTitle () implements MovieError {}
record EmptyDirector () implements MovieError {}
record EmptyCast () implements MovieError {}
record DuplicatedActor (String actor) implements MovieError {}
}
Definir errores de dominio.
ADTs: Errores
@PostMapping("/movies")
public ResponseEntity<UUID> create (@RequestBody Movie movie) {
var result = repository.create(movie);
return switch (result) {
case MovieCreated (var id) -> ResponseEntity.ok(id);
case MovieError e -> ResponseEntity.of(toProblem(e)).build();
};
}
ADTs: Errores
static ProblemDetail toProblem (CreateMovieResponse.MovieError error) {
var detail = switch (error) {
case DuplicatedMovie (int id) -> "duplicated movie with id: " + id;
case InvalidDuration (var duration) -> "invalid duration: " + duration;
case InvalidYear (var duration) -> "invalid year: " + duration;
case InvalidStars (var stars) -> "invalid stars: " + stars;
case EmptyTitle () -> "title cannot be empty" ;
case EmptyDirector () -> "director cannot be empty" ;
case EmptyCast () -> "cast cannot be empty" ;
case DuplicatedActor (var actor) -> "duplicated actor: " + actor;
};
return ProblemDetail.forStatusAndDetail(BAD_REQUEST, detail);
}
ADTs: Either
interface MovieRepository {
Either<MovieError, UUID> create (Movie movie) ;
}
@PostMapping("/movies")
public ResponseEntity<UUID> create (@RequestBody Movie movie) {
var result = repository.create(movie);
return result.fold(
error -> ResponseEntity.of(toProblem(error)).build(),
ResponseEntity::ok);
}
Próximamente
Primitive types in patterns
var result = switch (obj) {
case int i -> String.format("int %d" , i);
case long l -> String.format("long %d" , l);
case double d -> String.format("double %f" , d);
case String s -> String.format("String %s" , s);
default -> obj.toString();
};
Preview en Java 23 (sep 2024).
Primitive types in patterns
jshell> int i = 0 ;
i ==> 0
jshell> i instanceof byte b
$2 ==> true
jshell> int i = 123213 ;
i ==> 123213
jshell> i instanceof byte b
$4 ==> false
Bola de cristal
Derived Record Creation
Point newPoint = oldPoint with {
x *= 2 ;
y *= 2 ;
};
Static patterns
var result = switch (optional) {
case Optional.of(var value) -> value.toString();
case Optional.empty() -> "empty" ;
};
Early work.
AKA deconstructors.
Mejora para pattern matching.
Cualquier clase.
Constant patterns
var result = switch (optional) {
case Point (0 , var y) -> process(y);
case Point (var x, var y) -> process(x, y);
};
¿Qué falta todavía?
Tail recursion.
Soporte de tipos de datos primitivos en genéricos.
¡Gracias!
por favor, sed benevolentes con mis errores
preguntar a la audiencia qué version de Java usan en pro
Java 20 no es LTS
Java 21 todavía no aparece en las respuestas porque cuando se realizó
la encuesta no se había lanzado todavía
Todavía Java 8 es el más usado
Rajoy Presidente
JuanCar I abdicó en Felipe VI
Obama
Papa Francisco I desde 2013
en Marzo y Septiembre
en los últimos 10 años han ido introduciendo una serie de cambios que como resultado han dado a poder modelar e implementar ADTs.
probablemente en java 23 la preview de string interpolation se eliminará.
ha recibido un feedback muy negativo y se están planteando rediseñar completamente la nueva funcionalidad.
se puede:
asignar a una variable
devolver en un return
usarlo en una lambda
anidar un switch dentro de un case de otro switch
eso significa que tiene que ser exhaustivo
no son un remplazo para los POJOs tradicionales
son contenedores de datos inmutables
NO USAR COMO Entidades de Hibernate
incluso durante la deserialización.
en un POJO normal, al deserializar el objeto no se llama al constructor nunca,
los atributos del objeto se rellenan desde fuera de manera insegura
si tuviéramos una validación en el constructor se la saltaría
alguien desde fuera podría modificar la lista
o si la lista contuviera otros objetos que no son
inmutables
todavía no soporta primitivos
antes no era posible usarlo
antes la regla era siempre añadir un default en los switch
para no llevarnos sorpresas, pero ahora se convierte en un
antipatrón. Debemos dejar trabajar al compilador y que verifique si
el switch es exhaustivo y si modificamos la definición de Shape
añadiendo nuevos tipos, el compilador fallará en este switch para
indicar que nos falta cubrir un caso.
las mismas propiedades que aprendimos en la escuela primaria
no voy a entrar en más detalle en esto simplemente para que
sepáis que tiene una base matemática
No estoy diciendo que usemos este tipo de dato tal cual
es solo un ejemplo
la implementación es recursiva
por lo que si la lista es muy larga tendremos un
StackOverflowException
podemos implementar map, fold y otros muchos métodos
es un DSL muy simple para definir expresiones matemáticas
usando pattern matching podemos implementar la evaluación de la expresión
y generar un string que describa la expresión
en OOP tradicional habríamos necesitado sobre escribir el método asString
en cada subclase o utilizar el patron visitor.
Pattern Matching prácticamente jubila el patrón visitor.
Es otra manera de hacer Polimorfismo, usando pattern matching
imaginemos que tenemos una aplicación para gestionar
nuestras películas preferidas
en el controlador aplicamos pattern matching para devolver
el identificador generado,
o un error describiendo con todo el detalle posible qué ha ocurrido
aquí uso una novedad de spring-boot 3.x que son los ProblemDetail
en caso de ser necesario podría devolver un status code diferente
usualmente esto evoluciona al uso de Either
ya que tiene muchas más posibilidades
tienes dos canales uno para errores (Left) y otro para el resultado <Right>
Either en si también es una suma de tipos, porque puede ser o Left o Right
en el controlador podemos usar el método fold para construir la respuesta
si tenéis sdkman instalado podéis instalar una version EA
de java 23 que ya incluye esta funcionalidad
podemos saltarnos esta parte si vamos mal de tiempo
los ADT son recursivos por lo que muchos algoritmos se implementan usando recursividad
y java todavía no es un lenguaje que se pueda considerar stack safe
aunque en java 23 será posible usar tipos primitivos en pattern matching, todavía no esta
soportado para tipos genéricos
recomiendo leerlos ya que son muy instructivos y están muy bien redactados
TODO:
why use pattern matching?
- reduce cognitive load.
- pattern exhaustiveness.
meta:
- target audience
- manage expectations
- content
- confirm expectations
- links to follow