marp: true title: Una mirada funcional a Java 22 theme: gaia footer: #commitconf 2024 author: Antonio Muñoz color: #fff transition: fade-out
Antonio Muñoz
var value = switch (input) { case "a" -> 1; case "b" -> 2; default -> 0; };
switch
.yield
.var value = switch (input) { case "a" -> { yield 1; } case "b" -> { yield 2; } default -> { yield 0; } };
public record Movie(String title, int year, int duration) { }
public record Movie(String title, int year, int duration) { public Movie { if (title == null || title.isEmpty()) { throw new IllegalArgumentException(); } } }
public record Movie(String title, List<String> cast) { public Movie { cast = List.copyOf(cast); } }
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
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 {} }
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(); };
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(); };
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); };
var result = switch (point) { case Point(var x, var y) when y == 0 -> processX(x); case Point(var x, var y) -> processXY(x, y); };
when
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) -> ...; };
var result = switch (obj) { case Integer _ -> "int"; case Long _ -> "long"; case Double _ -> "double"; case String _ -> "string"; default -> "other"; };
a + b
a * b
record
es un producto de tipos.sealed interface
es una suma de tipos.sealed interface List<T> { record NonEmpty<T>(T head, List<T> tail) implements List<T> {} record Empty<T>() implements List<T> {} }
// NonEmpty[head=1, tail=NonEmpty[head=2, tail=NonEmpty[head=3, tail=Empty[]]]] List<Integer> list = new List.NonEmpty<>( 1, new List.NonEmpty<>( 2, new List.NonEmpty<>( 3, new List.Empty<>())));
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<>(); }; } }
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<>(); }; } }
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; }; } }
var list = list(1, list(2, list(3, empty()))); var result = list.fold(0, Integer::sum); // 6
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> {} }
sealed interface Optional<T> { record Empty<T>() implements Optional<T> { } record Present<T>(T value) implements Optional<T> {} }
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<>(); }; } }
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<>(); }; } }
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(); }; } }
optional.fold(() -> "unknown", Object::toString); // usando java.util.Optional optional.map(Object::toString).orElseGet(() -> "unknown");
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> {} }
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 {} }
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(); }; } }
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() + ")"; }; } }
sealed interface Expr { default void print() { System.out.println(asString() + "=" + evaluate()); } }
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
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 {} }
sealed interface Json { default String asString() { return switch (this) { case JsonNull _ -> "null"; case JsonBoolean b -> switch (b) { case TRUE -> "true"; case FALSE -> "false"; }; ... }; } }
sealed interface Json { default String asString() { return switch (this) { ... case JsonString(var value) -> "\"" + value + "\""; case JsonNumber(var value) -> String.valueOf(value); ... }; } }
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(",", "{", "}")); ... }; } }
sealed interface Json { default String asString() { return switch (this) { ... case JsonArray(var array) -> array.stream() .map(Json::asString) .collect(joining(",", "[", "]")); }; } }
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()); }
[{"old":true,"name":"Toni","age":46},{"old":false,"name":"Baby","age":null}]
interface MovieRepository { MovieResponse create(Movie movie); } sealed interface MovieResponse permits MovieCreated, MovieError {} record MovieCreated(UUID id) implements MovieResponse {}
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 {} }
@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(); }; }
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); }
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); }
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(); };
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
Point newPoint = oldPoint with { x *= 2; y *= 2; };
var result = switch (optional) { case Optional.of(var value) -> value.toString(); case Optional.empty() -> "empty"; };
var result = switch (optional) { case Point(0, var y) -> process(y); case Point(var x, var y) -> process(x, y); };