Mostrando entradas con la etiqueta Java SE 8. Mostrar todas las entradas
Mostrando entradas con la etiqueta Java SE 8. Mostrar todas las entradas

sábado, 5 de octubre de 2019

Java Annotations: Types y Bean Validation

Hoy vamos a hablar de las Type Annotations y como hacer las nuestras propias validaciones de codiga a traves de ellas. 

Basicamente las Type Annotations es la posibilidad a partir de Java 8 de utilizarlas cada vez que indiquemos un tipo de clase. Es decir en: operadores new, cast de objetos, clausulas de implementacion o lanzamiento de excepciones, etc. Y esto es posible gracias al enumerado TYPE_USE, indicadolo en el apartado target.

@Target({ ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { DNIValidator.class })
public @interface DNI {
 Class<?>[] groups() default {};
 String message() default "DNI must be valid";
 Class<? extends Payload>[] payload() default {};
}

Y para ver un ejemplo practico, anadiremos a esta informacion la de las ConstraintValidator (basadas en la especificacion JSR-380, tambien conociada como Bean Validation 2.0). La cual nos permitira validar el contenido de nuestros objetos en tiempo de ejecucion. 

Para llevar a cabo el ejemplo necesitaremos las librerias
  • hibernate-validator: Que contiene la implementacion de JSR-380.
  • hibernate-validator-annotation-processor: Que permite el procesamiento de las anotaciones.
  •  javax.el: Que habilita el uso de EL para el manejo de mensajes en las anotaciones.
El ejemplo lo basaremos en la anotacion mostrada anteriormente pero para ello antes hay que explicar el resto del codigo que es obligatorio para el correcto funcionamiento.
  • Anotacion Retention: Donde le indicaremos la permanencia de la anotacion. Puede ser @Source que descarta la anotacion durante la compilacion, @Class descarta la anotacion durante la carga de la clase y @Runtime que mantiene siempre la anotacion.
  • Anotacion Constraint: Aqui indicaremos la clase o clases de validacion que queremos asociarle. 
  • Atributo groups: Donde le indicamos la agrupacion jerarquica de la anotacion, permitiendo a la hora de indicar la anotacion, a que grupos se aplica. 
  • Atributo message: Donde indicamos el mensaje de validacion asociado. 
  • Atributo payload: A traves del cual indicamos metadatos asociados a la naotacion. 
El siguiente paso sera la validacion asociada a dicha anotacion. Y la crearemos a traves de una clase que extienda de ConstraintValidator e implemente el metodo isValid. 

import java.util.regex.*;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class DNIValidator implements ConstraintValidator<DNI, String> {
 private static String REGEX = "\\d{8}[A-HJ-NP-TV-Z]";
 @Override
 public boolean isValid(final String arg0, final ConstraintValidatorContext arg1) {
  Pattern p = Pattern.compile(REGEX);
  Matcher m = p.matcher(arg0);
  return m.matches();
 }
}

Ahora solo nos quedaria la prueba. Para ellos crearemos una clase denominada group que contenga un grupo de personas (un mapa) donde indicaremos la anotacion tipada @DNI. Y posteriormente probaremos la clase y su validacion a traves de JUnit y ValidatorFactory.

public class Group {
 private Map<@DNI String, String> people = new HashMap<>();
 public void addPeople(final String dni, final String name) {
  people.put(dni, name);
 }
}
// ...
public class DNIValidatorTest {
 private Validator validator;
 @Test
 public void ko() {
  Group group = new Group();
  group.addPeople("01234567U", "Daniel");
  Set<ConstraintViolation<Group>> violations = validator.validate(group);
  assertThat(violations.isEmpty(), is(false));
 }
 @BeforeEach
 public void setUp() {
  ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
  validator = factory.getValidator();
 }
}

Un unltimo detall, debemos tener en cuenta que en la propia especificacion se indican multiples anotaciones, como pueden ser: @NotBlank, @Email, @NegativeOrZero, etc. Por lo que conviene echarles un vistazo previamente, no vaya a ser que ya exista aquella que querramos implementar.

jueves, 18 de julio de 2019

Spark: Como construir un microservicio con lambda rapidamente

Esta claro que los micro servicios estan al alza y aqui traemos otro framework que nos ayude a crear microservicios de forma sencilla y rápida. En este post veremos que lo podemos hacer, haciendo el tipo ejemplo de 'Books' que he hecho otras veces. 

Para empezar debemos indicar que Spark viene con un servidor jetty includo, por lo que para el ejemplo simplemente incluiremos el código deseado dentro de un método static void y al utilizarse cualquier método de Spark se iniciará el servidor. O podemos arrancarlo con init y pararlo con stop. Además también podremos indicar el puerto por el que arranca a través del método port, siendo el  puerto por defecto, 4567.

Un método normal se crea de forma facilmente. Por un lado se utiliza un método de Spark para la operación REST que deseemos: get, post, put, delete o options. Estos métodos tienen principalmente las siguientes 3 caracteristicas:
  • El primer parámetro indicará cual es el path necesario para acceder al método.
  • El segundo parámetro puede variar segun la implementación que elijamos, pero la habitual será una interfaz funcional compuesta por dos objetos: request y response.
  • Al usar una interfaz funcional devolveremos un objeto, que habitualmente será un JSON string.
get("", (req, res) -> new Gson().toJson(
bookMap.values().stream().collect(Collectors.toList())));

Hasta aquí es todo más o menos lo habitual, y si tenemos algo de practica será sencillo dominar esta librería. A continuación indicaremos algunos métodos básicos que nos ayudarán a tener un poco más de control sobre nuestro microservicio.

Para evitar escribir partes del path que se repiten en varios métodos REST, podemos agruparlos dentro del método path que indicaremos la parte que es repetida en ellos.

path("/book", () -> {
    get("", (req, res) -> new Gson().toJson(
        bookMap.values().stream().collect(Collectors.toList())));
});

También podemos indicar parámetros en el path y recogerlos a través del objeto request.

post("/:id", (req, res) -> {
    Integer id = Integer.valueOf(req.params(":id"));
    Book book = new Gson().fromJson(req.body(), Book.class);
    bookMap.put(id, book);
    return new Gson().toJson(book);
});

Podemos mejorar la conversión a JSON mediante dos formas, o creando un objeto del tipo ResponseTransformer o con Lambda.

get("/:id", (req, res) -> 
    bookMap.get(Integer.valueOf(req.params(":id"))), new JsonTransformer());
delete("/:id", (req, res) -> 
    bookMap.remove(Integer.valueOf(req.params(":id"))), new Gson()::toJson);

También existen los métodos after y before que nos permite realizar operaciones comunes a diversos métodos.

before("/*", (q, a) -> log.info("Received api call"));

Todo el código lo podemos ver aquí. 

domingo, 30 de diciembre de 2018

Como renombrar ficheros con Java 8

Bueno esta es una entrada más para temas personales que aprender algo propiamente dicho, pero puede ser util igualmente. En este post veremos un pequeño código que nos permitirá renombrar los ficheros: Lo haremos con Java 8 y haciendo uso de Stream y Interfaces funcionales. 

Aquí esta el código de ejemplo:


package es.home.renamer;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
public class ChangeImageName {
 static String MAIN_PATH = "/home/user/Pictures";
 static String IMAGE_REGEX_1 = "IMG-([0-9]{4})([0-9]{2})([0-9]{2})-WA([0-9]{4}).([\\w]{3})";
 static String PATTERN_FILENAME = "%s-%s-%s %s.%s.%s.%s";
 static Integer COUNT = 0;
 static Pattern PATTERN = Pattern.compile(IMAGE_REGEX_1);
static Consumer<Path> renameFile = new Consumer<Path>() { @Override public void accept(Path path) { Matcher matcher = PATTERN.matcher(path.getFileName().toString()); if (matcher.find()) { String finalName = String.format(PATTERN_FILENAME, matcher.group(1), matcher.group(2), matcher.group(3), "00", "00", String.format("%02d", COUNT++), matcher.group(5)); Path target = Paths.get(path.getParent().toString(), finalName); if(!Files.exists(target)){ try { Files.move(path, target); } catch (IOException e) { e.printStackTrace(); } } } } }; static Predicate<Path> matchRegex = new Predicate<Path>() { @Override public boolean test(Path path) { return path.getFileName().toString().matches(IMAGE_REGEX_1); } }; public static void main(String[] args) { try { try (Stream<Path> paths = Files.walk(Paths.get(MAIN_PATH))) { paths.filter(Files::isRegularFile).filter(matchRegex).forEach(renameFile); } } catch (Exception except) { except.printStackTrace(); } } }

sábado, 22 de septiembre de 2018

Crear una emptyList, UnsupportedOperationException y como hacerlo bien


El otro día estaba intentando iniciarlizar una lista y se me ocurrió la feliz idea de hacerlo con alguna clase de utilidad de las muchas existentes. Así que hice lo siguiente:

List<Book> exit = Collections.emptyList();
exit.add(new Book(1, "Alfred Bester", "The starts my destination"));

Obteniendo al ejecutar una preciosa excepción del tipo 'java.lang.UnsupportedOperationException'. Y es que aunque el método 'emptyList' nos pueda llevar a la impresión de que va a crear una lista vacía y luego reutilizarla, como al menos a mi me llevo. Esta lista es de caracter 'inmutable', es decir que no la podemos modificar posteriormente. 

Para este caso yo al menos conozco dos posibles soluciones. El primero sería sin hacer uso de ninguna librería de utilidad y por tanto sería a través de un constructor:

List<Book> exit = new ArrayList<>();
exit.add(new Book(1, "Alfred Bester", "The starts my destination"));

La segunda forma sería a través de alguna librería de utilidad, como puede ser Guava, de la cual ya hablamos en su día aqui.

import com.google.common.collect.Lists;
//....
List<Book> exit = Lists.newArrayList();
exit.add(new Book(1, "Alfred Bester", "The starts my destination"));

Si lo que ya se quiere es crearla e iniciarla si también multiples formas. A continuación ponemos algunas de las más comunes.

// usando java.util.Arrays
List<String> firstEx = Arrays.asList("one", "two");
firstEx.stream().forEach(System.out::println);
// usando Java 8 y Stream
List<String> secondEx = Stream.of("one", "two").collect(Collectors.toList());
secondEx.stream().forEach(System.out::println);
// usando com.google.common.collect.Lists
List<String> thirdEx = Lists.newArrayList("one", "two");
thirdEx.stream().forEach(System.out::println);

martes, 11 de septiembre de 2018

Stream has already been operated upon or closed

El origen del blog erá crear un librería de post con soluciones de errores comunes que suelo tener, pero ultimamente solo doy la chapa con cosas mas o menos nuevas. Hoy volveremos a retomar esa idea original.

Vamos a hablar de un error que  me dio cuando estuve hacien el post sobre Java 8 y los Streams. Haciendo lo que para mí era algo lógico con los Stream en base a mis conocimientos anteriores, al ejecutar el código me dio el mensaje de error: stream has already been operated upon or closed.

Lo que intentaba era lo siguiente, iterar en un segundo ejemplo sobre el mismo strem:

List<String> list = Arrays.asList("uno", "dos", "tres");
Stream<String> stream = list.stream();
stream.forEach(element -> System.out.println(element));
System.out.println("-------- second example -------");
Stream<String> streaFiltered = stream.filter(element -> element.equals("uno"));
streaFiltered.forEach(element -> System.out.println(element));

El problema viene dado en el funcionamiento de los Steam. Y es que solo tienen permitida una única operación sobre si mismna. Es decir, el Steam será utilizado y cerrado como recurso una vez que realicemos una operación terminal sobre el mismo. Y por tanto no podrá volver a ser utilizado.

la solucion es hacer uso de la interfaz funcional 'Supplier' que nos proveerá del mismo stream todas las veces que queramos.

Supplier<Stream<String>> streamSupplier = () -> Arrays.asList("uno", "dos", "tres").stream();
streamSupplier.get().forEach(element -> System.out.println(element));
System.out.println("-------- second example -------");
Stream<String> streaFiltered = streamSupplier.get().filter(element -> element.equals("uno"));
streaFiltered.forEach(element -> System.out.println(element));

lunes, 3 de septiembre de 2018

Java 8 : las interfaces funcionales, lambda y su generación

Ya hicimos post sobre Java 8 y los steam o las interfaces funcionales. Ahora vamos a intentar hacer uno sobre lambda y su asociación con las intefaces funcioanles.

Cuando hablamos de Lambda, hablamos de expresiones lambda, generadas a través del operador '->'. Estas expresiones lambda a fin de cuenta nos permiten crear funciones anónimas. Y estas funciones a la vez, generan o instancian lo que ya conocemos como interfaces funcionales.

En base a la expresión lambda, generaremos uno u otro tipo de interfaz funcional, pudiendo ser interfaces de características diferentes. Hasta tal punto que en Java 8 se han creado distintos tipo de interfaces funcionales, las cuales puedes ver aquí y vamos a explicar una a una.
  • Consumer
Interfaz funcional que admiten un o dos parámetros y de diferentes tipos primitivos. Estas interfaces devuelven un valor en basé a la implementación interna.

Consumer<String> lambdaObject = (x) -> System.out.println(x.toLowerCase());
lambdaObject.accept("LAMBDA");
//produces : lambda
BiConsumer<String, String> lambdaBiConsumer = (x,y) -> System.out.println(x.toLowerCase() + " " + y.toUpperCase());
lambdaBiConsumer.accept("LAMBDA","lambda");
//produces : lambda, LAMBDA
DoubleConsumer lambdaDoubleConsumer = (x) -> System.out.println(Double.toString(x));
lambdaDoubleConsumer.accept(Double.MIN_VALUE);
//produces : 4.9E-324
  • Functions
Admiten un valor y el retorno no tien porque ser del mismo tipo.

Function<Double, String> lambdaFunction = (d) -> Double.toString(d);
System.out.println(lambdaFunction.apply(1.2));
//produces : 1.2
  • Provider
Aceptan un parámetro y devuelven un valor lógico

Predicate<String> lambdaPredicate = (s) -> "Lambda".equals(s);
System.out.println(lambdaPredicate.test("Lambda"));
// produces : true
  • Supplier
Interfaces que devuelven un valor pero no admiten parámetros

Supplier<String> lambdaSupplier = () ->  Double.toString(1.1);
System.out.println(lambdaSupplier.get());
//produces : 1.1

Con todo esto ya hemos visto un ejemplo básico de que es lambda, para que se usa y que produce. Más adelante haremos otro post con ejemplos más prácticos que le saquen el parte a las expresiones lambda.

lunes, 2 de julio de 2018

Java 8 y los stream: iniciación


Siguiendo con los posts de Java 8, vamos a continuar con otra de las grandes innovaciones de que trae. En este caso los java.util.stream, que incluye clases para sequencias de elementos. Veremos varios ejemplos y así paso a paso veremos de una forma pŕactica su utilidad y el sentido de para que se creo. 

La interfaz 'Collection' ha sido modificada para incluir un método 'stream()' que permita la creación a partir de una. 

List<String> list = Arrays.asList("uno", "dos", "tres");
Stream<String> stream = list.stream();
Stream<String> streamBis = Stream.of("uno", "dos", "tres");

Los streams nos permitirán iterar disminuyendo el código a escribir y aportando legitibilidad.

stream.forEach(element -> System.out.println(element));
//returns uno, dos, tres

También nos provee de un método que nos permite filtrar y generar otro stream con los valores del primero que cumplan los requisitos del filtro

Stream<String> filterStream = stream.filter(element -> element.equals("uno"));
filterStream.forEach(element -> System.out.println(element));
//return uno

También podemos crear un stream de otro tipo en base a un stream existente. Para ello haremos uso del método 'map' que nos permitirá hacer la conversión.

Stream<Boolean> booleanStream = stream.map(element -> element.equals("uno"));
booleanStream.forEach(element -> System.out.println(element));
//returns true, false, false

También nos provee varios métodos para encontrar coincidencias en una sequencia. Dichos métodos nos devolverán unicamente un valor booleano.

stream = Stream.of("uno", "dos", "tres"); //return true
System.out.println(stream.anyMatch(element -> element.contains("o")));
stream = Stream.of("uno", "dos", "tres"); //return false
System.out.println(stream.allMatch(element -> element.contains("o")));
stream = Stream.of("uno", "dos", "tres"); //return false
System.out.println(stream.noneMatch(element -> element.contains("o")));

Nos provee de un método denominado 'reduce' que permite realizar una operación con cada elemento de la sequencia, añadiendo además un valor inicial.

System.out.println(stream.reduce("Numeros: ", (a, b) -> a + b + " "));
// return 'Numeros: uno dos tres '

A trav'es del método 'collect' podremos convertir un stream en un objeto del tipo 'Collection'.

List<String> list = stream.collect(Collectors.toList());
System.out.println(list);
//return [uno, dos, tres]

Por último una característica muy notable de estos nuevos stream. Y es que podemos iterar por la secuencia de forma paralela.

list.parallelStream().forEach(element -> System.out.println(element));
//posible salida: dos, uno, tres

lunes, 25 de junio de 2018

Java 8: Las Interfaces nuevos métodos y tipos

Vamos a intentar hacer una serie de post sobre Java 8 que se centren algo más en cada una de las que según para mí son las caracteristicas más interesantes. En este post nos centraremos en las interfaces y sus nuevos métodos estáticos y por defecto además de las nuevas interfaces funcionales.
  • Métodos estáticos
Podemos crear interfaces con métodos estáticos como lo hemos hecho siempre con las clases. Y tantos como queramos. Estos métodos no pueden sobrescribirse, podrás crear otro con el mismo nombre en la clase que la extienda, pero la lógica no será la misma. 

public interface Aleatorio {
    static int dameUnoDe50() {
 return new Random().nextInt(50);
    }
    static int dameUnoDe20() {
 return new Random().nextInt(20);
    }
}
//....
public static void main(final String[] args) {
    System.out.println(Aleatorio.dameUnoDe50());
}
  • Métodos por defecto
Estos proveerán de una implementación por defecto a todas las clases que implementen la interfaz. Pero a la vez podrá ser sobrescrito por la clase y en este caso no podrá ser invocador por la interfaz como se realiza en el caso anterior.

public interface Aleatorio {
    static int dameUnoDe50() {
 return new Random().nextInt(50);
    }
    static int dameUnoDe20() {
 return new Random().nextInt(20);
    }
    default int dameUnoDe10() {
 return new Random().nextInt(10);
    }
}
//...
public class EnteroAleatorio implements Aleatorio {
    @Override
    public int dameUnoDe10() {
 return 10;
    }
    public static void main(final String[] args) {
 Aleatorio objAle = new EnteroAleatorio();
 System.out.println(objAle.dameUnoDe10());
    }
}
  • Interfaces funcionales
Esta nueva caracteristica de Java 8 gana relevancia y utilidad al usarse conjuntamente al lenguaje lambda. Y se dira que es una interfaz funcional toda interfaz que independientemente del número de métodos por defecto que tenga, tenga a su vez un único método abstracto. 

Y esto es porque se nos permitirá a través del lambda invocar a una interfaz con su método abstracto asociado e implementarlo y ejecutarlo en el momento de la invocación. 

public interface Aleatorio {
    int dameLoQueQuieras(Integer number);
    static int dameUnoDe50() {
 return new Random().nextInt(50);
    }
    static int dameUnoDe20() {
 return new Random().nextInt(20);
    }
    default int dameUnoDe10() {
 return new Random().nextInt(10);
    }
}
//...
public class EnteroAleatorio implements Aleatorio {
    @Override
    public int dameUnoDe10() {
 return 10;
    }
    @Override
    public int dameLoQueQuieras(final Integer number) {
 return 0;
    }
    public static void main(final String[] args) {
 Aleatorio intAle = (numero) -> new Random().nextInt(numero);
 Aleatorio objAle = new EnteroAleatorio();
 System.out.println(intAle.dameLoQueQuieras(200));
 System.out.println(objAle.dameLoQueQuieras(200));
    }
}

Si incluimos más de un  método abstracto tendremos un error en tiempo de compilación pero al intentar usarla (en el método static void main). Para evitar esto y que nos salte en la propia interfaz podemos usar la anotación @FunctionalInterface.