11.Novedades

Novedades

Clase Optional

La Clase Optional aparece a partir de Java 9, y simplemente hace referencia a un objeto o un valor null. Es decir hay situaciones en que ciertas sentencias nos podrá devolver algo o no. Siempre los “null” nos puede generar problemas, si usamos esta clase no. Ya que contempla la referencia a nada, intenta substituir a comprobaciones de tipo: “if (nota!=null) {}”

Resumen de cuatro métodos principales:

  • boolean isPresent(): devuelve si tiene valor o no.
  • get(): devuelve el objeto.
  • Optional.of(objeto): constructor de Optional estático (no acepta null).
  • Optional.ofNullable(objeto): mejor opción que la anterior ya que si acepta nulls.
  • boolean Optional.empty(): devuelve false si no existe el objeto.

Ejemplo:

public class Nota {

  private String asignatura;
  private double valor;
  public String getAsignatura() {
    return asignatura;
  }
  public void setAsignatura(String asignatura) {
    this.asignatura = asignatura;
  }
  public double getValor() {
    return valor;
  }
  public void setValor(double valor) {
    this.valor = valor;
  }
  public Nota(String asignatura, double valor) {
    super();
    this.asignatura = asignatura;
    this.valor = valor;
  }
}

public class Principal {

  public static void main(String[] args) {

    List<Nota> notas= new ArrayList<Nota>();
    notas.add(new Nota("matematicas",3));
    notas.add(new Nota("lengua",10));
    notas.add(new Nota("ingles",5));
    notas.add(new Nota("fisica",7));
    
    Nota nota= buscarNotaSobresaliente(notas);

    // Estas sentencias nos pueden generar el típico java.lang.NullPointerExcepcion
    System.out.println(nota.getValor());
    System.out.println(nota.getAsignatura());
  }

  public static Nota buscarNotaSobresaliente(List<Nota> notas) {

    Nota nota=null;
    for (Nota unaNota:notas) {

      if (unaNota.getValor()>=9) {
        nota= unaNota;
      }
    }
    
    return nota;
 }
}

Esto se soluciona con un !=null

if (nota!=null) {
      System.out.println(nota.getValor());
      System.out.println(nota.getAsignatura());
    }

O usando la clase Optional:

Optional<Nota> oNota= buscarNotaSobresaliente(notas);
    if (oNota.isPresent()) {
      Nota nota=oNota.get();
      System.out.println(nota.getValor());
      System.out.println(nota.getAsignatura());
    }
    
  }
  public static Optional<Nota> buscarNotaSobresaliente(List<Nota> notas) {
    
    for (Nota unaNota:notas) {
      
      
      if (unaNota.getValor()>=9) {
        return Optional.of(unaNota);
      }
    }
    return Optional.empty();
  
  }

Ejemplo con Optional.of():

public Optional<Capitulo> buscarCapitulo (String nombre)  {
        for (Capitulo c: capitulos) {
            if (c.getNombre().equals("nombre")) {   
                return Optional.of(c);
            }    
        }
        return Optional.empty();
    }

//main...

Optional<Capitulo> busqueda;
busqueda = l.buscarCapitulo("capitulo2");
busqueda.ifPresent((x)->System.out.println(x.getLongitud())); // lambda con ifPresent()

Recordemos que un Optional es un tipo que permite almacenar dos valores (valor concreto/valor nulo).

Entonces los Optionals nos permiten manejar valores que podrían no existir. Esto nos ayuda a reducir la cantidad de excepciones generadas por valores nulos (NullPointerExceptions). La clase Optional cuenta con varios métodos útiles que nos van a permitir manejar tanto la creación, la obtención y la verificación de los valores. Esta clase se ha ido ampliando con todas las posibilidades lógicas/booleanas posibles. más posibilidades

Ejemplo de un método que se utiliza con lambda

Un método muy cómodo de usar de Optional es ifPresentOrElse(), y cómo os imáginais es un if else dependiendo si tiene objeto el optional o no. Sintáxis con el uso de lambda:

public void ifPresentOrElse(Consumer<T> action,
                            Runnable emptyAction)

Ejemplo:

 Optional<Integer> op = Optional.ofNullable(9455); // creamos el Optional
 op.ifPresentOrElse( 
            (value) -> { System.out.println( 
                         "Value is present, its: "
                         + value); }, 
            () -> { System.out.println( 
                         "Value is empty"); }); 

Ejercicio 1

Crear un programa que simula un sistema de registro de usuarios (sin persistencia guardar en colecciones). El programa tendrá las siguientes funcionalidades:

  • Registrar un nuevo usuario con nombre, apellido y correo electrónico.
  • Buscar un usuario por su nombre y apellido.
  • Actualizar el correo electrónico de un usuario existente.
  • Eliminar un usuario existente. Para gestionar la ausencia de valores, utilizaremos la clase Optional.

Uso de Var

¿Para que sirve la palabra reservada var?

Crear objetos y variable tipo primitivo sin indicar el tipo. Es decir hay una inferencia de tipo, java internamente gestiona el tipo de dato. La sintaxis es la siguiente:

ArrayList<Persona> lista= new ArrayList<>();
lista.add(new Persona("pedro","perez",20));
// Pasamos a esto
var lista= new ArrayList<Persona>();
lista.add(new Persona("pedro","perez",20));

Condiciones

  • Siempre variable inicializadas.
  • Se puede usar con String, char, long, float, double, boolean y Objetos.
  • Con byte y short necesario Cast.
  • Null no es inferible, lo mismo con arrays.
  • Con lambda se puede usar, pero si tiene más de un argumento hay que usarlo en todos. Aspecto muy cambiante en las últimas versiones de Java:
(var s1, var s2) -> s1 + s2
// No permitido
(var s1, s2) -> s1 + s2
// Tampoco esto
(var s1, String s2) -> s1 + s2

Aclaración importante

Java sigue siendo fuertemente tipado en comparación a otros lenguajes de programación como JavaScript. Es decir una variable no puede cambiar de tipo al que referencia.

Ejemplo

//datos primitivos
var texto = "abc";
var caracter = '\n';
var largo = 42L;
var flotante = 3.14f;
var doble = 3.14d;
var logico = true;

//necesario cast con este tipo
var octeto = (byte)1;
var corto = (short)2;
var entero = 3;

var p=new Object();

var list = new ArrayList<String>(); // infiere ArrayList<String>

//Siempre algo inicializado
//var count=null;// Compilation error  

//Uso cómodo en bucles for
var numbers = List.of("a", "b", "c");
//for corto
for (var nr : numbers)
    System.out.print(nr + " ");
//for largo
for (var i = 0; i < numbers.size(); i++)
    System.out.print(numbers.get(i) + " ");

Clases Record

Las clases Record son un nuevo tipo de clases en el lenguaje Java, ayudan a modelar agregados de datos con menos ceremonia que las clases normales. Son clases que actúan como contenedores para datos inmutables, pueden ser considerados como tuplas. La declaración de un record mayormente consiste en la declaración de su estado. No es su objetivo resolver los problemas de las clases mutables que usan las convenciones de nombres de los JavaBeans. La palabra reservada es record y se usa en vez de class Un ejemplo de clase normal:

public class Point {
   private final int x;
   private final int y;

   Point(int x, int y) {
       this.x = x;
       this.y = y;
   }

   int x() { return x; }
   int y() { return y; }

   public boolean equals(Object o) {
       if (!(o instanceof Point)) return false;
       Point other = (Point) o;
       return other.x == x && other.y == y;
   }

   public int hashCode() {
       return Objects.hash(x, y);
   }

   public String toString() {
       return String.format("Point[x=%d, y=%d]", x, y);
   }
}

Ejemplo con record:

public record Point(int x, int y) {}

Aspectos relevantes

Las clases Record adquieren automáticamente los siguientes elementos:

  • Un campo privado y final por cada “atributo” o componente.
  • Constructor: implica un constructor por parámetros a partir de los argumentos establecidos. Este aspecto puede ser modificable.
public record Person (String name, String address) {}
Person person = new Person("John Doe", "100 Linda Ln.");
  • Getters y Setters: también existen pero se acceden con el nombre del atributo.
  • Equals y hashCode: se genera un método equals() normal, devuelve true si los objetos son del mismo tipo y los campos de los objetos son iguales.
  • toString: una implemetación de toString que incluye una representación de todos los componentes del registro con sus nombres.

Ejercicio 1

¿Qué ocurre con hashCode() y toString()? Compruébalo.

Método estáticos y variables

Permite crear variables estáticas en la declaración del record:

public record Person(String name, String address) {
    public static String UNKNOWN_ADDRESS = "Unknown";
}
Person.UNKNOWN_ADDRESS

Y por último método estáticos:

public record Person(String name, String address) {
    public static Person unnamed(String address) {
        return new Person("Unnamed", address);
    }
}
Person.unnamed("100 Linda Ln.");

## Limitaciones

  • No pueden extender ninguna otra clase y no pueden declarar campos que no sean los privados automáticos que corresponden a los componentes de la descripción del estado en la descripción.
  • Los registros son implícitamente final y no pueden ser abstract. Esto significa que no pueden ser mejorados por otra clase o registro.
  • Los componentes de un registro son implícitamente final. Esta restricción hace que sean inmutables.

Nota final: también acepta patern matching que veremos a continuación.

Patern Matching

Nota: es necesario un JDK 21 o superior. Sino queréis o podéis instalar una versióm última del JDK podéis usar este enlace: link.

Una de las características ampliadas con Java 9 es el Patern Matching que nos permite eliminar algunos cast explícitos, esto ya ha sido aplicado en expresiones como condiciones. Uso en el switch:

// Antes
Object o = ...; // any object
String formatted = null;
if (o instanceof Integer i) {
    formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
    formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
    formatted = String.format("double %f", d);
} else {
    formatted = String.format("Object %s", o.toString());
}

// Despues
Object o = ...; // any object
String formatter = switch(o) {
    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 Object o2  -> String.format("Object %s", o.toString());
};

Uso del patern matching con instance of: Versión clásica del equals:

public class Point {
    private int x;
    private int y;

    public boolean equals(Object o) {
        if (!(o instanceof Point)) {
            return false;
        }
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }

    // constructor, hashCode method and accessors have been omitted
}

Posible forma con patern matching:

public boolean equals(Object o) {
    return o instanceof Point point &&
            x == point.x &&
            y == point.y;
}

Ahora también se puede usar con records Antes:

record Point(int x, int y) {}

static void printSum(Object o) {
    if (o instanceof Point p) {
        int x = p.x();
        int y = p.y();
        System.out.println(x + y);
    }
}

Ahora:

void printSum(Object o) {
    if (o instanceof Point(int x, int y)) {
        System.out.println(x + y);
    }
}

Al escribir la expresión de pattern matching para un record permite al mismo tiempo desestructurar los elementos y extraerlos a variables. La expresión resultante para desestructurar los elementos es muy verbosa pero hace más simple el acceso posterior a las variables de record.

Bloques de texto

En Java embeber en el código un trozo de código HTML, XML, SQL o JSON en un literal como un String requiere editarlo de forma significativa con caracteres de escape y concatenación para que el código compile. La cadena transformada resultante es poco legible y difícil de mantener.

Un bloque de texto HTML en código Java requiere de múltiples caracteres de escape y concatenaciones de cadenas:

String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";

Usando bloques de texto se eliminan los caracteres de escape y las concatenaciones. El código resultante es mucho más legible y fácil de mantener. Debes iniciar la cadena de carácteres con triple dobles comillas:

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

Ejercicio 1

Probar bloques de texto para json y xml por ejemplo.

Los bloque de texto nos permite identación (—), se escapan e interpretan los caracteres especiales, se normaliza independientente del sistema operativo, no tenemos que escapar etc… Ofrece una buena manera de trabajar con cadenas multilínea y con caracteres especiales.

Más información -> enlace enlace2 enlace3

Clases Sealed

Las clases Sealed están relacionadas con herencia de clases. Estas clases o interfaces selladas nos permiten restringir que otras clases e interfaces puede extender de ellas. El objetivo de las clases o interfaces sealed es proporcionar una forma de permitir que una clases sea ampliamente accessible pero a la vez no ampliamente extensible. Estas pueden ser extendidas o implementadas solamente por aquellas clases e interfaces que son explícitamente permitidas. Las clases sealed no tiene como objetivo ser un reemplazo a la palabra reservada final. Palabra reservada: sealed, non-sealed y permits

Ejemplo:

public abstract sealed class Shape
    permits Circle, Rectangle, Square, WeirdShape { ... }

public final class Circle extends Shape { ... }
public final class Rectangule extends Shape { ... }
public final class Square extends Shape { ... }
public final class WeirdShape extends Shape { ... }

Como podéis observar esta posibilidad nos permite controlar más la propagación de la herencia. Cada subclase que herede debe usar un modificador para describir como propagar el sellado iniciado por su super-clase:

  • Una sub-clase permitida puede aplicar el modificar final para evitar extenderse más.
  • Una sub-clase permitida puede aplicar el modificador sealed seguido de la cláusula permits para extender a otras sub-clases en su jerarquía.
  • Una sub-clase permitida puede aplicar el modificador non-sealed de manera que revierte en su propia jerarquía el “sellado” de la clase super clase, y abriendo la extensión a otros clases desconocidas por la super clase. Ejemplos:
public abstract sealed class Shape
    permits Circle, Rectangle, Square, WeirdShape { ... }

public final class Circle extends Shape { ... }

public sealed class Rectangle extends Shape 
    permits TransparentRectangle, FilledRectangle { ... }
public final class TransparentRectangle extends Rectangle { ... }
public final class FilledRectangle extends Rectangle { ... }

public final class Square extends Shape { ... }

public non-sealed class WeirdShape extends Shape { ... }

El modificador sealed también se pueden usar tanto para interfaces como para records.

Ejercicio 1

Haz un pequeño ejemplo donde pruebes sealed para interfaces como para records.

Librería lombok

Introdución

Lombok es una librería para Java que a partir de anotaciones nos permite ahorrar código, crear unas clases más limpias. La web oficial es esta -> web Es fácil de usar y agregar a nuestro IDE y proyectos.

Instalación en Eclipse

  • Descargarse el jar correspondiente. enlace
  • Ejecuta dicho jar desde consola con: java -jar
  • Este ejecutable detecta los IDEs que tengas instalados, se selecciona Eclipse y se instala el plugin.
  • Debes reiniciar el IDE, y si todo ha ido bien en About Eclipse debe aparecer. imagen Eclipse

Instalación en proyecto

Con maven:

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

Agregandolo al classpath el jar de manera clásica: imagen classpath

Instalación en IntelliJ IDEA

Con este IDE podéis probarlo en los ordenadores del instituto. Agregáis al proyecto el jar necesario (como con Eclipse): Boton Derecho sobre proyecto -> Open Module Settings -> Libraries -> Y se agrega jar Es necesario agregar un pluging: pluging

Posibles anotaciones:

  • @Getter y @Setter: se colocan encima de la declaración de la clase o también en los atributos. Implica la generación automática de los getters y setters de todos los atributos.

  • @ToString y EqualsAndHashCode: es para la clase y se genera una sobrescrición del toString con los atributos de la clase, y se genera un equals en base al hash y el contenido de los atributos.

  • @AllArgsConstructor: automáticamente se genera un constructor con todos los parámetros de la clase, en el orden de declaración de los mismos. Campos variables

  • @NoArgsConstructor: lo mismo que lo anterior pero para el constructor por defecto.

  • @Data: equivale a todas las anotaciones de gets, sets, toString e equals y hashcode.

Hay más anotaciones, por ejemplo para pruebas, logs etc… Está aquí en enlace oficial con la api: api y con más anotaciones normales lombok.

Ejemplo:

Una vez hayas agregado la librería a tu proyecto ya puedes probarlo. Nota: activa la vista del Eclipse “Outline”, para ver en tiempo real lo que va generando las anotaciones.

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@EqualsAndHashCode
@ToString
@AllArgsConstructor
@NoArgsConstructor

public class Persona {

	private String nombre;
	private String apellidos;
	private int edad;
	
	
}
// Pruebalo a continuación

Persona p = new Persona("Juan","Fernandez",30); 
System.out.println(p.getNombre());
Persona p2 = new Persona();	
p2.setNombre("Juan");
p2.setApellidos("Fernandez");
p2.setEdad(30);
System.out.println("Funciona el equals:" + p.equals(p2));
System.out.println(p2);

Con el uso de @Data, aún se quita más codigo:

@Data
@AllArgsConstructor
public class Persona {

	private String nombre;
	private String apellidos;
	private int edad;
}

Por tanto es una librería de inyención de código en tiempo de compilación. Con las nuevas versiones de Java también podéis usar los records para motivos similares.

Consola JShell

Es una consola shell REPL (interactiva) para Java, al estilo a las que hay en Phython por ejemplo. Enlace -> JShell