Author Archives: Gustavo

Como usar Spring Boot Retry

Descripción

Spring Boot Retry ofrece una forma simple para reintentar alguna operación que ha fallado. Esto es útil sobre todo cuando se tratan errores temporales o transitorios como el acceso a un recurso externo.
Spring Boot Retry puede configurarse de forma declarativa mediante anotaciones o definiendo una config general.

Vamos a ver aquí cómo utilizar la funcionalidad Retry dentro de SpringBoot.

Dependencias necesarias para Spring Boot Retry

Las dependencias que necesitas son las siguientes

<dependencies>

   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter</artifactId>
   </dependency>

   <dependency>
       <groupId>org.springframework.retry</groupId>
       <artifactId>spring-retry</artifactId>
   </dependency>

   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-aop</artifactId>
   </dependency>

   <dependency>
       <groupId>org.aspectj</groupId>
       <artifactId>aspectjweaver</artifactId>
   </dependency>

</dependencies>

Como activar Retry en Spring Boot

Para activar Spring Retry debes anotar tu aplicación @EnableRetry

@SpringBootApplication
@EnableRetry
public class Application {

   public static void main(String[] args) {
       SpringApplication.run(Application.class, args);
   }

}

Como indicar que un método es ‘reintentable’

Para indicar que un método es reintentable usas esta anotación @Retryable de este modo. Puedes además establecer la máxima cantidad de intentos maxAttempts, y la demora entre intentos backoff entre otras opciones.

Para recuperarse de un método que, a pesar de los reintentos, sigue fallando puedes anotar un método de recuperación @Recover que será llamado por Spring al agotar todos los intentos. Este método debe devolver el mismo tipo de dato que el método original. Observa que puedes recibir como primer parámetro la excepción y subsecuentemente el resto de parametros del metodo que ha fallado.

@Service
public class RetryExampleService {

   private static Logger LOGGER = LoggerFactory.getLogger(RetryExampleService.class);

   @Retryable(value = {RuntimeException.class}, maxAttempts = 4, backoff = @Backoff(1000))
   public String retryExample(String s) {
       LOGGER.info(String.format("Retry retryTemplateExample %d", LocalDateTime.now().getSecond()));
       if (s == "error") {
           throw new RuntimeException("Error in RetryExampleService.retryExample ");
       } else {
           return "Hi " + s;
       }
   }

   @Recover
   public String retryExampleRecovery(RuntimeException t, String s) {
       LOGGER.info(String.format("Retry Recovery - %s", t.getMessage()));
       return "Retry Recovery OK!";
   }
}

Probar Retry con un test

Vamos a probar si nuestro método puede recuperarse con @Recovery.
Nuestro método de recuperación para este ejemplo simplemente devuelve un String “Retry Recovery OK!”

@RunWith(SpringRunner.class)
@SpringBootTest
public class RetryExampleServiceTest {

   @Autowired
   private RetryExampleService retryExampleService;

   @Test
   public void retryExampleWithRecoveryTest() throws Exception {
       String result = retryExampleService.retryExample("error");
       Assert.assertEquals("Retry Recovery OK!", result);
   }

}

La salida de este test RetryExampleServiceTest

INFO 74514 --- [main] com.gp.service.RetryExampleService       : Retry retryTemplateExample 52
INFO 74514 --- [main] com.gp.service.RetryExampleService       : Retry retryTemplateExample 53
INFO 74514 --- [main] com.gp.service.RetryExampleService       : Retry retryTemplateExample 54
INFO 74514 --- [main] com.gp.service.RetryExampleService       : Retry retryTemplateExample 55
INFO 74514 --- [main] com.gp.service.RetryExampleService       : Retry Recovery - Error in RetryExampleService.retryExample 

Con definir un template para Retry usando RetryTemplate

Además de usar las anotaciones @Retryable
y @Recover puedes crear un template en tu configuración para usarlo de manera general.

Para definir un template para los Retry en tu config defines un bean usando org.springframework.retry.support.RetryTemplate. Básicamente tienes dos atributos importantes: cantidad de intentos y el tiempo entre reintentos.

Si deseas puedes también definir un listener a partir de la clase RetryListenerSupport
y registrarlo en el template.

@Configuration
public class ApiConfig {

   @Bean
   public RetryTemplate retryTemplate() {

       RetryTemplate retryTemplate = new RetryTemplate();

       FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
       fixedBackOffPolicy.setBackOffPeriod(1000L);
       retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

       SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
       retryPolicy.setMaxAttempts(4);
       retryTemplate.setRetryPolicy(retryPolicy);

       retryTemplate.registerListener(new ApiRetryListener());

       return retryTemplate;
   }

}

Para el listener extiendes de RetryListenerSupport en donde tienes definido tres métodos, open cuando se inicia, onError al momento de producirse el error y reintentar y close cuando se agotaron todos los intentos.

public class ApiRetryListener extends RetryListenerSupport {

   private static Logger LOGGER = LoggerFactory.getLogger(ApiRetryListener.class);

   @Override
   public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
       LOGGER.info("ApiRetryListener.close");
       super.close(context, callback, throwable);
   }

   @Override
   public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
       LOGGER.info("ApiRetryListener.onError");
       super.onError(context, callback, throwable);
   }

   @Override
   public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
       LOGGER.info("ApiRetryListener.open");
       return super.open(context, callback);
   }

}

Luego para utilizar este template usas el bean que definiste llamando al método execute , org.springframework.retry.support.RetryTemplate#execute(org.springframework.retry.RetryCallback)

@Service
public class RetryTemplateExampleService {

   private static Logger LOGGER = LoggerFactory.getLogger(RetryTemplateExampleService.class);
   private final RetryTemplate retryTemplate;

   @Autowired
   public RetryTemplateExampleService(RetryTemplate retryTemplate) {
       this.retryTemplate = retryTemplate;
   }

   public String retryTemplateExample(String s) {
       String result;
       result = retryTemplate.execute(new RetryCallback<String, RuntimeException>() {
           @Override
           public String doWithRetry(RetryContext retryContext) {
               // do something in this service
               LOGGER.info(String.format("Retry retryTemplateExample %d", LocalDateTime.now().getSecond()));
               if (s == "error") {
                   throw new RuntimeException("Error in ExampleRetryTemplateService.retryTemplateExample ");
               } else {
                   return "Hi " + s;
               }
           }
       });
       LOGGER.info(String.format("Returning %s", result));
       return result;
   }

}

Probar RetryTemplate con un test

@RunWith(SpringRunner.class)
@SpringBootTest
public class RetryTemplateExampleServiceTest {

   @Autowired
   private RetryTemplateExampleService retryTemplateExampleService;

   @Test(expected = RuntimeException.class)
   public void retryTemplateExampleShouldThrowRuntime() throws Exception {
       retryTemplateExampleService.retryTemplateExample("error");
   }

   @Test
   public void retryTemplateExampleShouldReturnCorrectValue() throws Exception {
       String s = retryTemplateExampleService.retryTemplateExample("word");
       Assert.assertEquals("Hi word", s);
   }

}

La salida de este test

// retryTemplateExampleShouldThrowRuntime
INFO 75533 --- [main] com.gp.config.ApiRetryListener           : ApiRetryListener.open
INFO 75533 --- [main] c.g.service.RetryTemplateExampleService  : Retry retryTemplateExample 29
INFO 75533 --- [main] com.gp.config.ApiRetryListener           : ApiRetryListener.onError
INFO 75533 --- [main] c.g.service.RetryTemplateExampleService  : Retry retryTemplateExample 30
INFO 75533 --- [main] com.gp.config.ApiRetryListener           : ApiRetryListener.onError
INFO 75533 --- [main] c.g.service.RetryTemplateExampleService  : Retry retryTemplateExample 31
INFO 75533 --- [main] com.gp.config.ApiRetryListener           : ApiRetryListener.onError
INFO 75533 --- [main] c.g.service.RetryTemplateExampleService  : Retry retryTemplateExample 32
INFO 75533 --- [main] com.gp.config.ApiRetryListener           : ApiRetryListener.onError
INFO 75533 --- [main] com.gp.config.ApiRetryListener           : ApiRetryListener.close


// retryTemplateExampleShouldReturnCorrectValue
INFO 75533 --- [main] com.gp.config.ApiRetryListener           : ApiRetryListener.open
INFO 75533 --- [main] c.g.service.RetryTemplateExampleService  : Retry retryTemplateExample 32
INFO 75533 --- [main] com.gp.config.ApiRetryListener           : ApiRetryListener.close
INFO 75533 --- [main] c.g.service.RetryTemplateExampleService  : Returning Hi word

Conclusión

Hemos visto cómo usar Spring Retry para reintentar alguna operación en nuestro código si ha fallado y cómo crear un método de recuperación si lo necesitaramos.
Vimos que podemos definir esto simplemente con una anotación o con un template a usar.

Puedes ver este código completo

Compartir esto:

Java como cambiar el time zone a un Date

Vamos a examinar cómo cambiar el time zone de un date en java de distintos modos.

Como cambiar el timezone de un java.util.Date usando SimpleDateFormat

Debemos tener claro que la clase Date en Java no tiene time zone.
java.util.Date representa la cantidad de segundos transcurridos desde la medianoche 1 de enero de 1970 a las cero horas (hora UTC).

Esto significa que siempre el java.util.Date nos devolverá la hora para el time zone que este por default definida para el sistema.

Lo que necesitas hacer para cambiar el time zone a un Date es:
-Crear un DateFormat con el time zone para la zona que queremos
-Aplicar el DateFormat al Date obteniendo un string con modificado
-Volver a crear un object Date a partir de ese string

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class TimeZoneChangeDateExample {

    private static final String DATE_FORMAT = "dd-MM-yyyy hh:mm:ss a";

    public static void main(String[] args) throws ParseException {

        // the date
        Calendar cal = Calendar.getInstance();
        cal.set(2017, 06, 29, 8, 30);
        Date date = cal.getTime();

        // format with tz
        TimeZone timeZone = TimeZone.getTimeZone("Europe/Amsterdam");
        SimpleDateFormat formatterWithTimeZone = new SimpleDateFormat(DATE_FORMAT);
        formatterWithTimeZone.setTimeZone(timeZone);

        // change tz using formatter
        String sDate = formatterWithTimeZone.format(date);

        // string to object date
        SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
        Date dateWithTimeZone = formatter.parse(sDate); // string to Date Object


        System.out.println("The actual date is: " + formatter.format(date));
        System.out.println("The date in " + timeZone.getID() + " is: " + sDate);
        System.out.println("Object date: " + formatter.format(dateWithTimeZone));
    }
}

La salida de este ejemplo:

The actual date is: 29-07-2017 08:30:54 AM
The date in Europe/Amsterdam is: 29-07-2017 01:30:54 PM
Object date: 29-07-2017 01:30:54 PM

Como cambiar el timezone de un Date usando Calendar

Para cambiar el time zone usando calendar debes:
-Crear un calendar
-Setear el time zone al calendar
-Obtener los valores a través de los gets especificos. *

* Nota: si intentas obtener el date directamente calendar.getTime()) obtendrás la fecha siempre pasada al la tiempo local porque lo que devuelve es un java.util.Date y como dijimos antes este no tiene el tz.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

public class TimeZoneChangeCalendarExample {

    private static final String DATE_FORMAT = "dd-MM-yyyy hh:mm:ss a";

    public static void main(String[] args) throws ParseException {

        // calendar with tx
        TimeZone timeZone = TimeZone.getTimeZone("Europe/Amsterdam");
        Calendar calendar = Calendar.getInstance();
        calendar.set(2017, 06, 29, 8, 30);
        calendar.setTimeZone(timeZone);

        // get calendar using fields
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
        int hourOfDay = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);

        SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
        System.out.println("The actual date is: " + formatter.format(calendar.getTime()));
        System.out.print("The date in " + timeZone.getID() + " is: ");
        System.out.print(" Day:" + dayOfMonth);
        System.out.print(" Month:" + (month + 1)); // base zero
        System.out.print(" Year:" + year);
        System.out.print(" Hour:" + hourOfDay);
        System.out.print(" Min:" + minute);
        System.out.print(" Sec:" + second);

        System.out.println();

        // calendar getTime with DateFormat
        SimpleDateFormat formatterWithTimeZone = new SimpleDateFormat(DATE_FORMAT);
        formatterWithTimeZone.setTimeZone(timeZone);
        System.out.println("Calendar.getTime() - Date in Local TZ:" + formatterWithTimeZone.format(calendar.getTime()));

    }
}

La salida de este ejemplo:

The actual date is: 29-07-2017 03:30:22 AM
The date in Europe/Amsterdam is:  Day:29 Month:7 Year:2017 Hour:8 Min:30 Sec:22
Calendar.getTime() - Date in Local TZ:29-07-2017 08:30:22 AM

Como cambiar el timezone de un Date usando Java 8 en adelante

Para cambiar el time zone de una fecha en Java 8+:
-Crear un LocalDateTime
-Setear el time zone original al LocalDateTime
-Cambiar el time zone de la fecha original

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class TimeZoneChangeZonedDateTime {

    public static void main(String[] args) {

        LocalDateTime localDateTime = LocalDateTime.of(2018, 06, 29, 8, 30);

        // zone from Buenos Aires
        ZoneId zoneBuenosAires = ZoneId.of("America/Buenos_Aires");
        ZonedDateTime asiaZonedDateTime = localDateTime.atZone(zoneBuenosAires);

        // zone from Amsterdan
        ZoneId zoneAmsterdan = ZoneId.of("Europe/Amsterdam");
        ZonedDateTime nyDateTime = asiaZonedDateTime.withZoneSameInstant(zoneAmsterdan);

        // print dateTime with tz
        System.out.println("Date : " + asiaZonedDateTime);
        System.out.println("Date : " + nyDateTime);

    }
}

La salida de este ejemplo:

Date : 2018-06-29T08:30-03:00[America/Buenos_Aires]
Date : 2018-06-29T13:30+02:00[Europe/Amsterdam]

Puedes visitar este post con mayor detalle sobre la nueva api java.time

Conclusión

Este post muestra tres formas de cambiar el time zone para una fecha, partiendo sobre cómo cambiar el time zone para un java.util.Date, luego para un java.util.Calendar y por último en Java8+ haciendo uso de la nueva api java.time.

Compartir esto:

Java Convertir un List a un Map usando Stream

En este articulo veras como convertir un list a un map utilizando para ello un Stream disponible desde Java 8.

¿Cómo convertir un List a un Map?

Partiendo de una lista de productos creamos un Stream

public class Product {

   private long id;
   private String name;
   private String type;
   private Double price;

   public Product() {
   }

   public Product(long id, String name, String type, double price) {
       this.id = id;
       this.name = name;
       this.type = type;
       this.price = price;
   }

 //...

   @Override
   public String toString() {
       return "Product{" +
               "id=" + id +
               ", name='" + name + '\'' +
               ", type='" + type + '\'' +
               ", price=" + price +
               '}';
   }
}

List<Product> productList = new ArrayList<>();
productList.add(new Product(1, "SONY", "TV", 100));
productList.add(new Product(2, "SAMSUNG", "MOBILE", 200));

Stream<Product> productStream = productList.stream();

Usar Collectors.toMap para crear un map

Luego haces uso del Stream y “Collectors.toMap” en donde para este ejemplo usamos el id del producto como key del mapa y el nombre como valor.

// Stream to Map with Collectors.toMap
Map<Long, String> productMap = productStream.collect(Collectors.toMap(Product::getId, Product::getName));
productMap.forEach((k,v)->System.out.println("key : " + k + " value : " + v));

// output:
// key : 1 value : SONY
// key : 2 value : SAMSUNG
// key : 3 value : LG

Usar Function.identity() para guardar el objeto en el value del mapa

Si deseas usar como valor el mismo objeto puedes hacer uso de Function.identity() para el value

Map<Long, Product> productMapObject = productList.stream().collect(Collectors.toMap(Product::getId, Function.identity()));

// output
// key : 1 value : Product{id=1, name='SONY', type='TV', price=100.0}
// key : 2 value : Product{id=2, name='SAMSUNG', type='MOBILE', price=200.0}
// key : 3 value : Product{id=3, name='LG', type='MOBILE', price=300.0}

Evitando Duplicate key

Si en en la lista y por consiguiente en el Stream tienes dos elementos con un mismo id recibirás un IllegalStateException al tratar de crear el mapa.

// Exception 
productList.add(new Product(2, "SAMSUNG DUPLICATE", "MOBILE", 200));
try {
   Map<Long, String> productMapError = productList.stream().collect(Collectors.toMap(Product::getId, Product::getName));
} catch (IllegalStateException e) {
   e.printStackTrace();
}

El error de salida es confuso porque muestra el valor del mapa en vez de mostrar el key que se duplica.

Exception in thread "main" java.lang.IllegalStateException: Duplicate key SAMSUNG
//..

Para resolver esto pasas una función que realice el tratamiento de los duplicados para evitar este error:

// handle duplicate ids
Map<Long, String> list =
       productStream
               .collect(Collectors.toMap(
                       Product::getId,
                       Product::getName,
                       (oldValue, newValue) -> oldValue
               ));


// output:
// key : 1 value : SONY
// key : 2 value : SAMSUNG
// key : 3 value : LG

Básicamente lo que haces es decidir entre los dos valores duplicados con cual deseas quedarte. (oldValue, newValue) -> oldValue

Creando un mapa ordenado

Para crear un mapa ordenado solo debes indicarle al Stream el ordenamiento que deseas pasándole el comparador que deseas, en este ejemplo ordenamos con el key (id del producto).

// creating a ordered map
Map orderMap = productList.stream()
       .sorted(Comparator.comparingLong(Product::getId).reversed())
       .collect(
               Collectors.toMap(
                       Product::getId, Product::getName,
                       (oldValue, newValue) -> oldValue,  // if duplicate
                       LinkedHashMap::new  // use LinkedHashMap to keep order
               ));

orderMap.forEach((k, v) -> System.out.println("key : " + k + " value : " + v));

// output:
// key : 3 value : LG
// key : 2 value : SAMSUNG
// key : 1 value : SONY

Conclusión: usando Stream para colectar el resultado con Collectors.toMap creas un mapa a partir de tu lista. Luego puedes evitar duplicados pasándole una función para decidir qué hacer en caso de encontrarse con un key duplicado. .

Mira este código completo

Compartir esto:

Go como usar Goroutines

Un Goroutine es un hilo de ejecución ligero manejado por GO.

Gorutine son funciones o métodos que se ejecutan simultáneamente.
Podemos entenderlas como “hilos ligeros” con un costo mucho menor que un hilo tradicional.

Supongamos que tienes dos funciones

func numbers() {
	for i := 0; i < 5; i++ {
		fmt.Printf("%d ", i)
		time.Sleep(100 * time.Millisecond)
	}
}

func characters() {
	for i := 'a'; i <= 'f'; i++ {
		fmt.Printf("%c ", i)
		time.Sleep(200 * time.Millisecond)
	}
}

Si llamas a estas funciones de la forma habitual tendrías este resultado.

func main() {

	// without goroutine
	numbers()

	characters()

	fmt.Scanln() // wait, press a key
	fmt.Println("done")
}

Observa cómo la ejecución de estas funciones es síncrona

go run rutine.go
0 1 2 3 4 a b c d e f
done

Como implementar Goroutine

Si deseas invocar estas funciones en una Goroutine debes usar “go” por delante de la llamada a la función de esta manera:

func main() {
	// goroutine
	go numbers()

	go characters()

	fmt.Scanln() // wait, press a key
	fmt.Println("done")
}

En la salida se ve como se van ejecutando ambas funciones de manera simultánea, lo que evidencia esta ejecución en paralelo de ambas rutinas.

go run rutine.go
0 a 1 2 b 3 c 4 d e f

Conclusión:

Cuando ejecutas este código sin Goroutine vemos que las funciones se ejecutan una primero y luego la otra, sincrónicamente.
Utilizando Goroutine, anteponiendo “go” por delante a la llamada, puedes observar que la salida de las funciones se va intercalando mostrando que se ejecutan al mismo tiempo.

Compartir esto:

Java 8 Stream map flatMap y filter

Stream map te permite leer cada elemento y transformar los valores que se encuentran en el Stream entregandote otro Stream con el resultado para seguir realizando operaciones.

Cómo usar Stream map

Como puedes ver en el ejemplo a continuación map() recibe una función como parámetro para realizar alguna transformación sobre cada elemento y devuelve otro Stream sobre el cual puedes seguir trabajando.

Observa en el ejemplo a continuación transformamos cada elemento en un String en mayúscula y luego lo colocamos en una nueva lista.

List<String> fruits = Arrays.asList(
    "apple", "banana", "cherry", "lemon", "peach");

List<String> fruitsUpper = fruits.stream().map(String::toUpperCase)
    .collect(Collectors.toList());
System.out.print(fruitsUpper);
// [APPLE, BANANA, CHERRY, LEMON, PEACH]

Otro ejemplo en donde el resultado será las tres primeras letras de cada String.

List<String> fruitsFirstLetters= fruits.stream().map(f -> f.substring(0,3)).collect(Collectors.toList());
System.out.println(fruitsFirstLetters);
// [app, ban, che, lem, pea]

Podemos realizar transformaciones de objetos en algo distinto, recibiendo en la lambda expresión el objeto como parámetro y haciendo algo con él. Por ejemplo, extraer valor de un atributo de un objeto.

List<User> users = Arrays.asList(new User("Hamilton"), new User("Thompson"),
       new User("Stallman"), new User("Torvalds"));

List<String> stringNames = users.stream().map(user -> user.getName()).map(String::toUpperCase)
       .collect(Collectors.toList());
System.out.println(stringNames);
// [HAMILTON, THOMPSON, STALLMAN, TORVALDS]

Cómo usar Stream flatMap

Podemos encontrarnos con estructuras algo más complejas. Por ejemplo, una lista con otra
lista.

List<List<String>> programmers = Arrays.asList(
       Arrays.asList("Margaret", "Hamilton"),
       Arrays.asList("Ken", "Thompson"),
       Arrays.asList("Richard", "Stallman"),
       Arrays.asList("Linus", "Torvalds"));

Si obtienes un Stream de esta lista obtendrías otro Stream con una lista

Stream<List<String>> streamWithList = programmers.stream();

Con flatMap lo que haces es un “flatten”, aplanar el resultado. Por ejemplo, aquí aplanamos List y nos quedamos con un Stream “plano”.

{ {"Margaret","Hamilton"}, {"Ken","Thompson"}, ... } -> flatMap -> {"Margaret","Hamilton","Ken","Thompson", ...}
List<String> flatList = programmers.stream().flatMap(Collection::stream).collect(Collectors.toList());
System.out.println(flatList);
// [Margaret, Hamilton, Ken, Thompson, Richard, Stallman, Linus, Torvalds]

Cómo usar Stream filter

Filter aplica un predicado sobre cada elemento y si cumple la condición lo agrega a un nuevo Stream.
Aquí transformas un Stream en otro nuevo Stream con solo elementos que empiezan con “b”

Stream<String> streamWithB = fruits.stream().filter(f -> f.startsWith("b"));
List<String> listWithB = streamWithB.collect(Collectors.toList());
System.out.println(listWithB);
// [banana]

Conclusión

Has visto cómo crear un Stream y transformar cada dato del Stream en algo distinto usando map. También cómo aplanar un Stream complejo para obtener un Stream plano y sobre él seguir realizando operaciones.
Por último, cómo filtrar resultados aplicando un predicado para los elementos que cumplan con tu condición.

Código de ejemplo:
/j8/streams

Compartir esto:

Java 8 Variable Scope en Lambda

Variable Scope en Lambdas

El acceso a variables dentro de lambdas puede ser algo confuso, pero es sencillo si lo ves con un ejemplo.

Observa este código en el cual accedemos a los parámetros “text” y “count” desde la expresión lambda.

public static void showText(String text, int count) {
   Runnable r = () -> {
       for (int i = 0; i < count; i++) {
           System.out.println(text);
       }
   };
   new Thread(r).start();
}

Cuando llamamos al método showText este ejecutará un nuevo hilo con la expresión lambda y retornará de inmediato. ¿Cómo es que estas variables no se pierden dentro de la expresión lambda? Decimos que la expresión lambda ha capturado estos valores de las variables.

Estas variables son llamadas variables libres porque no son parámetros de la expresión lambda en sí misma y no están tampoco definidas dentro del código lambda.

Una expresión lambda tiene tres ingredientes:
-El bloque de código
-Parámetros
-Valores de variables libres.

Restricciones sobre variables en Lambdas

El valor de estas variables libres puede ser capturado por la lambda expresión pero tienen la restricción de que estás variables NO pueden ser modificadas dentro de la expresión lambda.

En nuestro ejemplo no podríamos modificar el valor de la variable “count”. Piensa que si diferentes trabajos ejecutan este código de forma concurrente estarían todos modificando esta contador “count”. Debemos asegurar la expresión lambda siempre “threadsafe”.

public static void showText_notWork(String text, int count) {
   Runnable r = () -> {
       while (count > 0) {
           System.out.println(text + " " + count);
           count--; // Error.  Can't change !!
       }
   };
   new Thread(r).start();
}

Dijimos que NO podemos modificar una variable compartida pero observa que si podemos modificar el valor dentro de un objeto compartido. No tendremos problemas de compilación pero nuestro código no será threadsafe por lo que el resultado puede ser impredecible si múltiples ejecuciones ocurren de forma concurrente.

En este ejemplo la asignación de la variable “data” nunca la estamos cambiando pero si estamos modificando su contenido.

    public static void repeatMessage_noThreadSafe() {
        List<String> data = new ArrayList<>();
        data.add("file1");
        // more into data...
        for (String p : data) {
            new Thread(() -> {
                if (p.startsWith("file9")) {
                    data.add("xfile9"); // changing shared object
                }
            }).start();
        }
    }

Conclusión:

Vimos una breve explicación sobre el scope de las variables en expresiones lambda y porqué no es posible modificarlas.

Código de ejemplo:
/j8/variableScope

Compartir esto:

Java 8 Constructor por Referencia

Java Constructor por Referencia

En Java 8 se han introducido importantes cambios como lambdas, interfaces funcionales y métodos por referencia.

Veamos ahora cómo hacer uso de constructores por referencia.

Si no has leido el articulo sobre métodos por referencia te recomiendo que le des un vistazo porque tiene mucho en común con el artículo a continuación. La diferencia es que aquí llamas al “new” en vez de llamar a un método.

Partamos de un ejemplo para que se vea claramente.

Tenemos este User con tres constructores, uno por default, otro que recibe el “name” y el último que recibe “name” y “password”. Deseamos crear varios user partiendo de una lista de nombres.

public class User {

   private String name;
   private String password;

   public User() {
   }

   public User(String name) {
       this.name = name;
   }

   public User(String name, String pass) {
       this.name = name;
       this.password = pass;
   }
//...

La lista de nombres predefinidos que queremos utilizar para crear los User.

List<String> userNames = new ArrayList<>();
userNames.add("jose");
userNames.add("luis");
userNames.add("lucas");

Como llamar al constructor por referencia

Para este ejemplo nos valdremos de stream().map() que nos permite convertir un objeto en algo diferente. En nuestro “mapeamos” un String en un User.
Luego usaremos stream.collect para recoger todos los elementos en una lista de Users.
Por último los iteramos para mostrar el resultado.

Lo importante aquí es observar que estamos llamando al constructor por referencia cuando escribimos User::new.
¿Pero cuál constructor llamará .map(..) si tenemos varios constructores en la clase User.
Aquí el compilador elige por iferenencia el constructor que tenga un solo parámetro String porque es el único que aplica.

Stream<User> userStream = userNames.stream().map(User::new);
List<User> userList = userStream.collect(Collectors.toList());
userList.forEach(System.out::println);

//.. output..
User{name='jose'}
User{name='luis'}
User{name='lucas'}

En conclusión vemos que así como usamos métodos por referencia tambien podemos hacerlo para constructores del modo User::new. Además el compilador elige el constructor correspondiente por inferencia.

Código de ejemplo:
j8/constructReference

Referencias:
Lambdas
Interfaces funcionales Métodos por referencia

Compartir esto:

Java 8 Optional

Java 8 Optional

Evitar los Nullpointer es uno de los problemas con los que habitualmente nos encontramos los programadores en Java. Continuamente realizamos operaciones para verificar que el valor no sea null o tener la precaución de devolver valores not null y a pesar de esto es habitual el fallo por los null pointer.

Java 8 introduce Optional, este es un wrapper que nos ayuda a prevenir los nullpointer tan comunes en Java. Optional nos da un métodos adecuados para validar si el valor contenido esta null o no.

Java 8 Optional

Como crear un Optional para un objeto

La forma correcta de crear un Optional para un objeto no nulo es la siguiente.

User user = new User("gustavo"); 
Optional<User> optUser = Optional.of(user);

Si utilizas Optional.of con un valor null obtendras un nullpointer inmediatamente en el metodo of.
Esto resulta en la ventaja de no esperar hasta usar el User para encontrarse con la excepción.

Por ejemplo, aqui tendremos un null pointer en Optional, antes de hacer uso de User nulo.

User user = null; 
Optional<User> optUser = Optional.of(user); // NullPointerException here
… 
user.getName(); 

// ...
Exception in thread "main" java.lang.NullPointerException
	at java.util.Objects.requireNonNull(Objects.java:203)
	at java.util.Optional.<init>(Optional.java:96)
	at java.util.Optional.of(Optional.java:108)
	at j8.optional.OptionalExample.main(OptionalExample.java:9)

Si sabes que el objeto User está null o es posible que este nulo y quieres devolver un Optional debes utilizar el método Optional.ofNullable(..);

User user = null;
Optional<User> optUser = Optional.ofNullable(user);

Como utilizar Optional en Java

Dijimos que el uso de Optional es determinar si un objeto está nul o no, aunque es mejor decir que Optional te dirá si el objeto está presente.

A continuación vas a crear un Optional para un objeto User y vas a determinar si está presente.

   // logic to create User 
   User user = new User("john");

   // Create Optional for User
   Optional<User> optUser = Optional.ofNullable(user);

   if (optUser.isPresent()) {
       System.out.println(optUser.get().getName());
   }

   // using j8 lambdas
   optUser.ifPresent(u -> System.out.println(u.getName()));

}

Tambien puedes utilizar <a href="http://gustavopeiretti.com/java-8-usar-expresiones-lambda/">lambdas</a> con el metodo ifPresent(..)



   User user = new User("john");
   Optional<User> optUser = Optional.ofNullable(user);

   // using j8 lambdas
   optUser.ifPresent(u -> System.out.println(u.getName()));

}

Como utilizar Optional orElse / orElseGet en Java

Lo que hace orElse es devolver el valor si está presente o el valor que se pasa por parámetro si está ausente. Es decir usa un valor DEFAULT si el objeto no esta presente.

User user = null;
Optional<User> optUser = Optional.ofNullable(user);

System.out.println(optUser.orElse(User.EMPTY_USER).getName());

// output 
// EMPTY_USER

En el caso de orElseGet también se devuelve un valor por default si el valor no está presente, solo que aquí el valor se devuelve utilizando la interfaz funcional Supplier.
Lo positivo aquí es que esta función solo será llamada si el valor no esta presente evitando procesamiento. En nuestro ejemplo es bastante simple, pero en los casos en que la ejecución de la función sea costosa este método es recomendable.

// orElseGet
User user = null;
Optional<User> optUser = Optional.ofNullable(user);

System.out.println(optUser.orElseGet(new Supplier<User>() {
   @Override
   public User get() {
       return User.EMPTY_USER;
   }
}).getName());

// better with j8 lambdas !
System.out.println(optUser.orElseGet(() -> User.EMPTY_USER).getName());

Tirando una expection con Optional.orElseThrow

Vimos que usando orElse / orElseGet lo que buscas es devolver otro valor por default si el valor dentro de Optional está ausente. Usando orElseThrow lo que deseas en cambio es tirar una Exception si este valor no está.

// orElseThrow
User nullUser = null;
Optional<User> optUser = Optional.ofNullable(nullUser);
optUser.orElseThrow(IllegalArgumentException::new);

// output
Exception in thread "main" java.lang.IllegalArgumentException
	at java.util.Optional.orElseThrow(Optional.java:290)
	at j8.optional.OptionalExample.main(OptionalExample.java:35)

Como usar Optional.filter en Java

Optional.filter te permite filtrar elementos que no deseas. Suponiendo que solo deseas imprimir algún resultado para un determinado User, en este ejemplo el User cuyo nombre es “john”.

User user = new User("john");
Optional<User> optUser = Optional.ofNullable(user);

// filter + ifPresent
optUser.filter(u -> "john".equals(u.getName()))
       .ifPresent(u -> System.out.println(u.getName()));

// filter + isPresent
if (optUser.filter(u -> "john".equals(u.getName()))
       .isPresent()) {
   System.out.println("john is present");
}


Como usar Optional.map y Optional.flatMap

Optional.map transforma o ‘mapea’ un valor en otro devolviendote otro Optional para el tipo de dato.

Por ejemplo si deseamos obtener un Optional con la edad Integer) del User

Optional<Integer> userAge = optUser.map(User::getAge);

Ahora siendo el valor devuelto con “Optional.map” también otro Optional podemos aplicarle filtros

// filter + map
if(optUser.map(User::getAge).filter(age -> age >= 18).isPresent()) {
   System.out.print("The user is older");
}

¿Cuál es la diferencia entre map y flatMap?

Debes usar map si la función te retorna un objeto, en cambio si la función retorna un Optional usas flatMap

// filter + flatMap
optUser.flatMap(User::getOptionalEmail).ifPresent(email -> sendEmail(email));

private static void  sendEmail(String email) {
   System.out.println("Sending email to " + email);
}


Siempre es conveniente utilizar Optional en vez de validar los nulos en la forma tradicional
Optional nos provee de herramientas para evitar los típicos errores con nulos, aunque es necesario usarlo correctamente porque si no validamos antes que el objeto este presente podemos incurrir facilmente en otro tipo de error.

Si olvidamos verificar si el objeto está presente y hacemos uso de get directamente tendremos una exception.

String data = null;
Optional<String> opt = Optional.ofNullable(data);
opt.get().toString();

Exception in thread "main" java.util.NoSuchElementException: No value present
	at java.util.Optional.get(Optional.java:135)

Optional, además de facilitarnos el control de nulos o no presentes nos provee de funcionalidades como filter, map, orElse, orElseThrow que nos resuelven muchas de las tareas habituales. Puedes consultar este ejemplo en github, espero te sirva.

Compartir esto:

Java 8 como usar fechas y horas con la api java.time

Java 8 nos trae al fin una nueva api para el manejo de fechas. Nos encontramos dentro del paquete
java.time con nuevas clases para resolver los problemas con fechas como LocalDate, horas con LocalTime o la combinación de fecha y hora con LocalDateTime. También incluye como es debido dentro de esta api el uso de zonas horarios con ZonedDateTime.
Además los conceptos de Period para determinar el periodo entre dos fechas y Duration para determinar la duración entre dos horas.

Aquí te mostraré pequeños ejemplos para conocer esta api a fin de que luego en el uso ya te encuentres familiarizado con ellas.

Java 8 Date Time

LocalDate

LocalDate representa la fecha sin la hora.

LocalDate localDate = LocalDate.now();
System.out.println(localDate.toString());

LocalDate localDateOf = LocalDate.of(2017, 10, 10);
System.out.println(localDateOf.toString()); // 2017-10-10

Puedes además sumar o restar días facilmente,

LocalDate datePlus = localDateOf.plusDays(7);
System.out.println(datePlus.toString());  // 2017-10-17

LocalDate dateMinus = localDateOf.minusDays(7);
System.out.println(dateMinus.toString()); // 2017-10-03

Determinar cuál es fecha esta es anterior o posterior respecto a otra

boolean isBefore = LocalDate.of(2017, 10, 10)
                .isBefore(LocalDate.of(2017, 8, 20));
System.out.println(isBefore); // false

boolean isAfter = LocalDate.of(2017, 10, 10)
                .isAfter(LocalDate.of(2017, 8, 20));
System.out.println(isAfter); // true

LocalTime

LocalTime es similar a LocalDate en su uso y representa la hora sin la fecha.

LocalTime localTime = LocalTime.now();
System.out.println(localTime);

LocalTime hour = LocalTime.of(6, 30);
System.out.println(hour); // 06:30

Sumar o restar horas o cualquier otro tipo de unidad como segundos

LocalTime localTimePlus = hour.plus(1, ChronoUnit.HOURS);
System.out.println(localTimePlus); // 07:30
LocalTime localTimeMinus = hour.minus(60, ChronoUnit.SECONDS);
System.out.println(localTimeMinus); // 06:29

También podemos comparar para saber si alguna hora es mayor o no que otra.

boolean isBeforeHour = LocalTime.parse("08:30")
.isBefore(LocalTime.parse("10:20"));
System.out.println(isBeforeHour); // true

LocalDateTime

LocalDateTime es la combinación del la fecha y la hora. Al igual que con LocalDate y LocalTime puedes crear instancias

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
LocalDateTime localDateTimeOf = LocalDateTime.of(2017, Month.AUGUST, 20, 8, 30);
System.out.println(localDateTimeOf); // 2017-08-20T08:30

Igual que como vimos en LocalDate y LocalTime, puedes sumar o restar facilmente utilizando diferentes unidades de tiempo

LocalDateTime localDateTimePlus = localDateTimeOf.plusDays(5);
System.out.println(localDateTimePlus); // 2017-08-25T08:30
LocalDateTime localDateTimeMinus = localDateTimePlus.minusMinutes(10);
System.out.println(localDateTimeMinus); // 2017-08-25T08:20

ZonedDateTime

Si necesitas trabajar con zonas horarias puedes utilizar esta clase que te provee el manejo de fechas con hora para la zona que determines. La lista de zonas disponibles las puedes consultar desde la clase ZoneId

ZoneId.getAvailableZoneIds().forEach(z -> System.out.println(z)); // list of all zones
ZoneId zoneId = ZoneId.of("America/Panama");

Para ‘moverse’ a otra zona horaria, por ejemplo Tokyo haces uso de de LocalDateTime en conjunto con ZoneDateTime pasando la zona “Asia/Tokyo”

LocalDateTime localDateTimeOf = LocalDateTime.of(2017, Month.AUGUST, 20, 8, 30);
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTimeOf, zoneId);
System.out.println(zonedDateTime); // 2017-08-20T08:30-05:00[America/Panama]
ZonedDateTime tokyoDateTime = localDateTimeOf.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println(tokyoDateTime); // 2017-08-20T08:30+09:00[Asia/Tokyo]

Period

Con la clase Period puedes obtener la diferencia entre dos fechas o utilizarlo para modificar valores de alguna fecha.

LocalDate startLocalDate = LocalDate.of(2017, 10, 10);
LocalDate endLocalDate = startLocalDate.plus(Period.ofDays(10));  // 2017-10-20
int diffDays = Period.between(startLocalDate, endLocalDate).getDays();
System.out.println(diffDays); // 10

Duration

Duration es el equivalente a Period pero para las horas.

LocalTime startLocalTime = LocalTime.of(8, 30);
LocalTime endLocalTime = startLocalTime.plus(Duration.ofHours(3));  // 11:30

long diffSeconds = Duration.between(startLocalTime, endLocalTime).getSeconds();
System.out.println(diffSeconds); // 10800 seconds

Código completo

Te dejo copiado todo el ejemplo completo para tu uso. Te recomiendo lo copies y en tu ID para que lo veas más claro. Espero te sirva.

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;

public class DateTimeExample {

    public static void main(String[] args) {

        // LocalDate
        localDateExample();

        // LocalTime
        localTimeExample();

        // LocalDateTime
        localDateTimeExample();

        // ZonedDateTime
        zonedDateTimeExample();

        // Period
        periodExample();

        // Duration
        durationExample();

    }


    private static void localDateExample() {
        LocalDate localDate = LocalDate.now();
        System.out.println(localDate.toString());

        LocalDate localDateOf = LocalDate.of(2017, 10, 10);
        System.out.println(localDateOf.toString()); // 2017-10-10

        LocalDate datePlus = localDateOf.plusDays(7);
        System.out.println(datePlus.toString());  // 2017-10-17

        LocalDate dateMinus = localDateOf.minusDays(7);
        System.out.println(dateMinus.toString()); // 2017-10-03

        boolean isBefore = LocalDate.of(2017, 10, 10)
                .isBefore(LocalDate.of(2017, 8, 20));
        System.out.println(isBefore); // false


        boolean isAfter = LocalDate.of(2017, 10, 10)
                .isAfter(LocalDate.of(2017, 8, 20));
        System.out.println(isAfter); // true
    }

    private static void localTimeExample() {
        LocalTime localTime = LocalTime.now();
        System.out.println(localTime);

        LocalTime hour = LocalTime.of(6, 30);
        System.out.println(hour); // 06:30


        LocalTime localTimePlus = hour.plus(1, ChronoUnit.HOURS);
        System.out.println(localTimePlus); // 07:30

        LocalTime localTimeMinus = hour.minus(60, ChronoUnit.SECONDS);
        System.out.println(localTimeMinus); // 06:29

        boolean isBeforeHour = LocalTime.parse("08:30")
                .isBefore(LocalTime.parse("10:20"));
        System.out.println(isBeforeHour); // true
    }

    private static void localDateTimeExample() {
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println(localDateTime);


        LocalDateTime localDateTimeOf = LocalDateTime.of(2017, Month.AUGUST, 20, 8, 30);
        System.out.println(localDateTimeOf); // 2017-08-20T08:30


        LocalDateTime localDateTimePlus = localDateTimeOf.plusDays(5);
        System.out.println(localDateTimePlus); // 2017-08-25T08:30

        LocalDateTime localDateTimeMinus = localDateTimePlus.minusMinutes(10);
        System.out.println(localDateTimeMinus); // 2017-08-25T08:20
    }


    private static void zonedDateTimeExample() {
        ZoneId.getAvailableZoneIds().forEach(z -> System.out.println(z)); // list of all zones

        ZoneId zoneId = ZoneId.of("America/Panama");
        LocalDateTime localDateTimeOf = LocalDateTime.of(2017, Month.AUGUST, 20, 8, 30);
        ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTimeOf, zoneId);
        System.out.println(zonedDateTime); // 2017-08-20T08:30-05:00[America/Panama]

        ZonedDateTime tokyoDateTime = localDateTimeOf.atZone(ZoneId.of("Asia/Tokyo"));
        System.out.println(tokyoDateTime); // 2017-08-20T08:30+09:00[Asia/Tokyo]
    }


    private static void periodExample() {
        LocalDate startLocalDate = LocalDate.of(2017, 10, 10);
        LocalDate endLocalDate = startLocalDate.plus(Period.ofDays(10));  // 2017-10-20

        int diffDays = Period.between(startLocalDate, endLocalDate).getDays();
        System.out.println(diffDays); // 10
    }

    private static void durationExample() {
        LocalTime startLocalTime = LocalTime.of(8, 30);
        LocalTime endLocalTime = startLocalTime.plus(Duration.ofHours(3));  // 11:30

        long diffSeconds = Duration.between(startLocalTime, endLocalTime).getSeconds();
        System.out.println(diffSeconds); // 10800 seconds
    }

}

Compartir esto:

Java – como usar ExecutorService

Java ExecutorService

ExecutorService nos simplifica las tareas asincronas proveyendo un pool de hilos que son manejados por esta API abstrayendonos del trabajo de crearlos y asignarles tareas a estos hilos.

java executor service

Utilizando la factory class Executors

La forma más común para crear una instancia de ExecutorService es a través de la factory class Executors.

Por ejemplo si deseas crear pool con 10 threads lo puedes hacer de este modo:

ExecutorService executor = Executors.newFixedThreadPool(10);

Si quieres crear un pool con un solo hilo en los que las tareas queden en cola y se ejecuten de forma secuencial:

ExecutorService executor = Executors.newSingleThreadExecutor();

Creando instancias directamente

Puedes directamente crear instancias de cualquiera de las implementaciones de ExecutorService, observa que esta es una interfaz con múltiples implementaciones.

Por ejemplo para crear un pool de 10 hilos del mismo modo que lo hace la factory class Executors.newSingleThreadExecutor().

ExecutorService executor = new ThreadPoolExecutor(10, 10,
       0L, TimeUnit.MILLISECONDS,
       new LinkedBlockingQueue<Runnable>());

¿Cómo ejecutar tareas con ExecutorService?

Usando ExecutorService.execute

ExecutorService en su método execute ejecuta cualquier implementacion de Runnable, por lo que su ejecución la haces de este modo:

Runnable command = new Runnable() {
   @Override
   public void run() {
       doLongWork();
   }
};
executor.execute(command);

Usando lambdas puedes cambiar este código por uno más limpio. Copia este codigo completo y pruebalo localmente.

package executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorServiceExample {

   public static void main(String[] args) {

       ExecutorService executor = Executors.newFixedThreadPool(10);
       executor.execute(() -> doLongWork("hi 1"));
       executor.execute(() -> doLongWork("hi 2"));
       executor.execute(() -> doLongWork("hi 3"));
   }

   private static void doLongWork(String hola) {
       System.out.println("Running " + hola);
       try {
           Thread.sleep(1000l);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }

}

Usando ExecutorService.submit

EjecutorService.execute es un método void por lo que no puedes conocer el resultado, pero ExecutorService puede también retornar el resultado de tu ejecución si utilizas el método submit.

ExecutorService executor = Executors.newFixedThreadPool(10);
Future<String> future = executor.submit(() -> doLongWork("hi with future 1"));
future.get();

Future.get te devuelve el resultado de la ejecución de tu metodo luego de que este ya ha terminado.

Prueba este ejemplo completo

package executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorServiceFutureExample {

   public static void main(String[] args) throws Exception {

       ExecutorService executor = Executors.newFixedThreadPool(10);
       Future<String> future = executor.submit(() -> doLongWork("hi with future 1"));
       System.out.println(future.get());
      
   }

   private static String doLongWork(String msg) {
       System.out.println("Running " + msg);
       try {
           Thread.sleep(1000l);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       return "done " + msg;
   }

}

Usando ExecutorService.invokeAny

ExecutorService.invokeAny recibe una lista de Callable, ejecuta todas las task y devuelve el resultado de una ejecución correcta

ExecutorService executor = Executors.newFixedThreadPool(10);

Collection<Callable<String>> callables = new ArrayList<>();
callables.add(() -> doLongWork("hi! 1"));
callables.add(() -> doLongWork("hi! 2"));
String oneResult = executor.invokeAny(callables);

Usando ExecutorService.invokeAll

ExecutorService.invokeAll recibe una también una lista de Callable, ejecuta todas las task y pero devuelve el resultado de todas las ejecuciones

package executor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorServiceFutureAnyExample {

   public static void main(String[] args) throws Exception {

       ExecutorService executor = Executors.newFixedThreadPool(10);

       Collection<Callable<String>> callables = new ArrayList<>();
       callables.add(() -> doLongWork("hi! 1"));
       callables.add(() -> doLongWork("hi! 2"));

       List<Future<String>> result = executor.invokeAll(callables);

       for (Future f : result) {
           System.out.println("f.isDone :  " + f.isDone() + " f.get : " + f.get());
       }

   }

   private static String doLongWork(String msg) {
       System.out.println("Running " + msg);
       try {
           Thread.sleep(1000l);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       return "finish " + msg;
   }

}

Como terminar ExecutorService

Por defecto ExecutorService se queda ‘escuchando’ nuevas tareas, se queda vivo para recibir nuevas tareas y asignarlas a su pool. Si deseas finalizarlo para que no reciba nuevas tareas puedes ejecutar su método shutdown() para “solicitarle” la terminación. Una vez terminadas la tareas en ejecución se finalizará el servicio.
También puedes requerir la finalización inmediata usando shutdownNow(), aunque esto último no esta garantizado. Este método te devuelve la lista de tareas que no finalizaron si deseas conocerlas.

ExecutorService executor = Executors.newFixedThreadPool(10);
...
List<Runnable> runnableList = executor.shutdownNow();

Compartir esto:

Go Tipos Básicos

Go Types – Go Tipos básicos

¿Cuáles son los tipos básicos definidos en Go?

Go define diferentes tipos básicos entre los que puedes encontrar booleanos, string, numéricos enteros (con signo o sin signo), flotantes (números reales) y números complejos.

Este recuadro te muestra los tipos básicos definidos en Go

Tipo booleano:
bool

Tipo string:
string


Enteros con signo:
int         32-bit y 64-bit según el sistema
int8        8-bit (-128 to 127)
int16       16-bit (-32768 to 32767)
int32       32-bit (-2147483648 to 2147483647)
int64       64-bit (-9223372036854775808 to 9223372036854775807)

Enteros sin signo:
uint        32-bit y 64-bit según el sistema
uint8       8-bit (0 to 255)
uint16      16-bit (0 to 65535)
uint32      32-bit (0 to 4294967295)
uint64      64-bit integers (0 to 18446744073709551615)
uintptr    

Flotantes:
float32     32-bit 
float64     64-bit 

Complejos:
complex64   the set of all complex numbers with float32 real and imaginary parts
complex128  the set of all complex numbers with float64 real and imaginary parts

byte // alias for uint8
rune // alias for int32 represents a Unicode code point

Los tipos int, uint y uintptr suelen tener 32 bits de ancho en sistemas de 32 bits y 64 bits de ancho en sistemas de 64 bits.
En general cuando necesites usar un entero utiliza siempre int a menos que tengas un motivo específico para usar enteros de tamaño definido o sin signo.

package main

import (
	"fmt"
	"math"
)

func main() {

	// Boolean
	var b bool = true
	if (b) {
		fmt.Println("is true")
	}

	// String
	var a string = "Hello World"
	fmt.Println(a)

	// Integer number
	var i int = 100
	fmt.Println(i + 99)

	// Float number
	var f float64 = 1.343435345563459
	fmt.Println(f)

	var fmax = math.MaxFloat64
	fmt.Println(fmax)

	// Complex number
	var c complex64 = 1.04i
	fmt.Println(c)

}

El resultado de este ejemplo

$ go run basictypes.go
is true
Hello World
199
1.343435345563459
1.7976931348623157e+308
(0+1.04i)

Compartir esto:

Go Errors

Go Errors

¿Cómo manejar los errores en Go?

Go utilizar como concepto de error cualquier devolución a través de alguna implementación de la interfaz “error” provista por el lenguaje

type error interface {
      Error() string
}

Para usar esta interfaz y devolver un error puedes utilizar una implementación ya provista por Go sobre esta interfaz haciendo uso de la función errors.New() del paquete errors.

Una vez creado error lo retornas y luego evaluas si es no es nulo.
Por convención el error es el último valor retornado si devuelves más de un valor.

package main

import (
	"fmt"
	"errors"
)

func addTwo(value int) (int, error) {
	if (value < 0) {
		// business rules .. I can not work with negatives
		return value, errors.New(fmt.Sprintf("something went wrong value is %d:", value))
	} else {
		return value + 2, nil
	}
}

func main() {

	v, err := addTwo(-1);
	if err != nil {
		// do something with this error
		fmt.Print(err)
	} else {
		fmt.Print(v)
	}

}


El resultado de este código es

$ go run errors.go
something went wrong value is -1:

Compartir esto:

Go Punteros

Go Punteros (Pointers)

¿Qué son los punteros en Go?

Un puntero es una referencia al valor almacenado en memoria.

Para obtener la referencia al puntero del valor debes usar el símbolo “&” por delante y para obtener la desreferencia usas el símbolo “*

Por defecto Go envia los argumentos como valor, si deseas que los argumentos sean enviados por referencia debes usar un puntero.

Hagamos una prueba. Crea dos métodos, el primero “addOne” que reciba un valor entero al cual le sumas un valor. Este argumento será recibido por valor. No se usan punteros aquí.

func addOne(value int) {
      value = value + 1
}

El segundo método a crear es “addOnePointer” que haga lo mismo pero que reciba el argumento como como puntero para desreferenciarlo de su posición de memoria a la variable. Este argumento será recibido como referencia.

func addOnePointer(value *int) {
      *value = *value + 1
}

Haciendo una prueba simple puedes observar que en el primer método “func addOne(value int) “ el argumento se recibe por valor como una copia por lo cual la variable original no es modificada.

En el segundo método func addOnePointer(value *int) en donde indicamos usar el puntero, el valor se recibe por referencia y la variable original si es modifica, ya que estamos accediendo a la posición de memoria y modificandola allí.

package main

import "fmt"

// value param
func addOne(value int) {
      value = value + 1
}

// pointer param, desreference param
func addOnePointer(value *int) {
      *value = *value + 1
}

func main() {
      i := 1
      fmt.Println("Value of i is:", i)

      addOne(i)
      fmt.Println("addOne:", i)

      // using memory address of i (pointer)
      addOnePointer(&i)
      fmt.Println("addOnePointer:", i)

}

El resultado de la prueba es el siguiente:

go run pointers.go
Value of i is: 1
addOne: 1
addOnePointer: 2

Compartir esto:

Go Interfaces

Go Interfaces

¿Qué son las interfaces en Go?

En Go una Interface es un conjunto o colección de métodos que deberán ser implementados.

Por ejemplo, suponemos que deseas una interfaz para figuras geométricas IShape que defina un método de cálculo del área para todos los tipos de figuras

type IShape interface {
      area() float64
}

Ahora lo que haces es implementar esa interface que calcula el área para diferentes figuras.

Defines un struct Rectangle, Square y Triangle con sus campos:

// Create a Rectangle struct
type Rectangle struct {
      Lenght float64
      Width  float64
}

// Create a Square struct
type Square struct {
      SideLength float64
}

// Create a Triangle struct
type Triangle struct {
      Base   float64
      Height float64
}

Implementas el método de la interfaz para cada uno. Observa que la implementación de la interfaz está implícita. Si vienes de Java como es mi caso esto puede resultar confuso; en Java debes explícitamente indicar que tu clase implementa determinada interfaz.

El método tiene un receptor para Rectangle y el nombre del método es el de la interfaz, por lo que implícitamente estamos implementando para Rectangle el método de la interfaz. Lo mismo para el resto de figuras Square y Triangle.

// Implement IShape.area() for Rectangle
func (r Rectangle) area() float64 {
      return r.Lenght * r.Width
}

// Implement IShape.area() for Square
func (s Square) area() float64 {
      return s.SideLength * 2
}

// Implement IShape.area() for Triangle
func (t Triangle) area() float64 {
      return t.Base * t.Height / 2
}

Creas una función que reciba la interfaz y muestre y devuelva el resultado

func ShowArea(s IShape) string {
      return fmt.Sprintf("Area %f", s.area())
}

Tu código completo para este ejemplo luce así:

package main

import (
      "fmt"
)


// Interface
type IShape interface {
      area() float64
}

// Create a Rectangle struct
type Rectangle struct {
      Lenght float64
      Width  float64
}

// Create a Square struct
type Square struct {
      SideLength float64
}

// Create a Triangle struct
type Triangle struct {
      Base   float64
      Height float64
}

// Implement IShape.area() for Rectangle
func (r Rectangle) area() float64 {
      return r.Lenght * r.Width
}

// Implement IShape.area() for Square
func (s Square) area() float64 {
      return s.SideLength * 2
}

// Implement IShape.area() for Triangle
func (t Triangle) area() float64 {
      return t.Base * t.Height / 2
}

// Function to show Area
func ShowArea(s IShape) string {
      return fmt.Sprintf("Area %f", s.area())
}

func main() {
      // creating a Rectangle
      fmt.Println("Rectangle", ShowArea(Rectangle{10, 50}))
      fmt.Println("Square", ShowArea(Square{40}))
      fmt.Println("Triangle", ShowArea(Triangle{20,30}))
}

Ejecutando tu código:

$ go run interfaces.go
Rectangle Area 500.000000
Square Area 80.000000
Triangle Area 300.000000

Compartir esto:

Go conceptos básicos con ejemplos

Una de las cosas que más me gustan de Go es su simplicidad de código y su rapidez tanto en el aprendizaje como en su ejecución.

Te explico los conceptos básicos de Go para iniciarte en este lenguaje. Go es un lenguaje simple de aprender y si ya conoces cualquier otro lenguaje en pocas horas puedes asimilar su semántica.

Estas son las sintaxis iniciales que deberías comprender, con ejemplos muy simples, para iniciarte en Go.

Go Variables

¿Como crear variables en Golang?

La palabra reservada para crear variables en Golang es “var” a continuación va el nombre de la variable y le sigue el tipo.

var hello string = "hello word"

Puedes declarar múltiples variables para el mismo tipo

var hello1, hello2, hello3 string = "hello word"

En golang todas las variables tienen siempre un valor predefinido

var s string
fmt.Println(s=="") // imprime "true"
var i int
fmt.Println(i==0) // imprime "true

Golang cuenta con tipado dinámico por lo que es capaz de inferir el tipo de dato

i := 123  // entero
name := "Gustavo" // string

Ejemplos de variables:

package main

import "fmt"

func main() {

	// var string
	var hello string = "hello word"
	fmt.Println(hello)

	// var int
	var age int = 30;
	fmt.Println("Age ", age);

	// var float
	var fb float64 = 30.12345678901234567890;
	fmt.Println("Float fb ", fb);

	// multiple assignations
	var hi, word string = "hi", "word"
	fmt.Println(hi, word)

	// default value of variable int
	var defaultInt int
	fmt.Println("Value of defaultInt", defaultInt)

	// default value of boolean
	var defaultBoolean bool
	fmt.Println("Value of defaultBoolean", defaultBoolean)

	// short variable declarations
	great := "great work";
	fmt.Println(great);

	// type inference
	var b = true
	fmt.Println("Value of b", b)

}
$ go run variables.go
hello word
Age  30
Float fb  30.123456789012344
hi word
Value of defaultInt 0
Value of defaultBoolean false
great work
Value of b true

Go constantes

¿Cómo crear constantes en Golang?

En golang puedes crear constantes usando la palabra reservada “const”.

const s string = "hello"

Aquí van algunos ejemplos de uso de constantes:

package main

import "fmt"

// declare constant
const (
	MaxAge = 30
	MinAge = 15
)

func main() {

	// declare constant
	const months = 12

	// use constant
	var age int = 30
	fmt.Println("Age in months ", months * age);

	// use constant
	fmt.Println("Max age", MaxAge)
	fmt.Println("Min Age", MinAge)

}

$ go run constant.go
Age in months  360
Max age 30
Min Age 15

Go Arreglos

¿Cómo crear arreglos en golang?

En golang un array tiene esta forma.

var i [5]int

Primero el keyword var, luego el nombre de la variable y la cantidad de elementos para el tipo definido. Los arreglos tienen un tamaño predefinido y no es posible redimensionarlos, una vez creado el arreglo con su tamaño este conserva su dimensión y no es factible cambiarlo en tiempo de ejecución.

package main

import "fmt"

func main() {

	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println("Position 0 ", a[0])
	fmt.Println("Values of a", a)

	// array with assignment
	fibonacci := [11]int{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55}
	fmt.Println("Values of fibonacci", fibonacci)

	// array with 3 position and two assignment
	array := [3]int{0, 1}
	fmt.Println("Values of array", array)


	// array multi-dimensional
	var twoDimensionArray [2][3]int
	twoDimensionArray[0][0] = 1
	twoDimensionArray[0][1] = 2
	twoDimensionArray[0][2] = 3

	twoDimensionArray[1][0] = 4
	twoDimensionArray[1][1] = 5
	twoDimensionArray[1][2] = 6

	fmt.Println("Values of twoDimensionArray", twoDimensionArray)

}

Position 0  Hello
Values of a [Hello World]
Values of fibonacci [0 1 1 2 3 5 8 13 21 34 55]
Values of array [0 1 0]
Values of twoDimensionArray [[1 2 3] [4 5 6]]

Go Slices

¿Qué son los Slices en Golang?

Los slices podemos pensarlos como “tipo de arreglo” pero de tamaño dinámicos. A diferencia de los arreglos que no puedes redimensionar, los slices sí es factible cambiarlos en tamaño.

En un arreglo debes declarar el tamaño de antemano, en cambio un slice lo defines de esta forma:

var colors []string

Observa que no hemos indicado el tamaño.

package main

import "fmt"

func main() {

	// slide without data
	var colors []string
	fmt.Println("Colors without data", colors)

	colors = append(colors, "red")
	fmt.Println("Colors data", colors)

	// slice
	cities := []string{"buenos aires", "sao paulo", "new york"}
	fmt.Println("Cities", cities)

	// slice using make
	m := make([]int, 5)
	fmt.Printf("Slice m - lenght=%d capacity=%d values %v", len(m), cap(m), m)
	fmt.Println()

	mc := make([]int, 0, 5)
	fmt.Printf("Slice mc - lenght=%d capacity=%d values %v", len(mc), cap(mc), mc)
	fmt.Println()

}

..

$ go run slices.go
Colors without data []
Colors data [red]
Cities [buenos aires sao paulo new york]
Slice m - lenght=5 capacity=5 values [0 0 0 0 0]
Slice mc - lenght=0 capacity=5 values []

Go For

¿Cómo crear un for en Golang?

Go maneja los ciclos de manera muy simple en vista de que tiene solo un tipo de ciclo definido por el loop “for”.

El loop “for” en Golang luce parecido al “for” en java o en “C” con la diferencia de que no usas paréntesis y son requeridas las llaves {}

El for en Golang se compone de
-Inicialización de la variable
-La condición por la cual el loop continua su ejecución o no.
-Una instrucción que se ejecuta en cada iteración

package main

import "fmt"

func main() {

	// for
	for i := 0; i < 5; i++ {
		fmt.Println(i)
	}

	// for continued
	sum := 1
	for ; sum < 100; {
		sum += sum
	}
	fmt.Println("sum", sum)

	// for like while
	sumb := 1
	for sumb < 1000 {
		sumb += sumb
	}
	fmt.Println("sumb", sumb)

	// infinite for
	x := 0
	for {
		// do something in a loop and break manually
		x++
		if (x >= 10) {
			fmt.Println("infite loop break")
			break
		}
	}
}

$ go run for.go
0
1
2
3
4
sum 128
sumb 1024
infite loop break

Go Range

¿Qué es un Range en Golang?

En Golang “range” te permite iterar los elementos de distintas estructuras de datos. Por ejemplo puedes usar “range” para iterar un arreglo, un slice, un map, una cadena de texto.

package main

import "fmt"

func main() {

	// create array and range from this array
	var numbers = []int{2, 4, 6, 8}
	for i, v := range numbers {
		fmt.Println("index of array", i, "value or array", v)
	}

	// create slice and range from this slice
	nums := []int{2, 4, 6}
	sum := 0
	for i, v := range nums {
		sum += v
		fmt.Println("index of slice", i, "sum", sum)
	}

	// create a map and range from this map
	fruits := map[string]string{"o": "orange", "b": "banana"}
	for k, v := range fruits {
		fmt.Println("key of map", k, "value of map", v)
	}

	// create string and range from this string
	s := "golan hello word"
	for i, c := range s {
		fmt.Println("index of character", i, "character", string(c))
	}

}

$ go run range.go
index of array 0 value or array 2
index of array 1 value or array 4
index of array 2 value or array 6
index of array 3 value or array 8
index of slice 0 sum 2
index of slice 1 sum 6
index of slice 2 sum 12
key of map o value of map orange
key of map b value of map banana
index of character 0 character g
index of character 1 character o
index of character 2 character l
index of character 3 character a
index of character 4 character n
index of character 5 character  
index of character 6 character h
index of character 7 character e
index of character 8 character l
index of character 9 character l
index of character 10 character o
index of character 11 character  
index of character 12 character w
index of character 13 character o
index of character 14 character r
index of character 15 character d

Go If/Else

¿Cómo usar if/else en Golang

En Go para las declaraciones “if” no son requeridos los paréntesis (), pero si son obligatorias las llaves {}.

package main

import (
	"fmt"
)

func main() {

	// common if statement
	s := "hi"
	if s == "hi" {
		fmt.Println("Hello")
	} else if s == "bye" {
		fmt.Println("See you")
	} else {
		fmt.Println("How’s it going")
	}

	// if statement with operation
	x := 10
	n := 180
	if v := x + n; v <= 100 {
		fmt.Println("value is less or equal than 100 ", v)
	} else if v <= 200 {
		fmt.Println("value is less or equal than 200 ", v)
	} else {
		fmt.Println("the value is greater than 200", v)
	}

	// if without else
	i := 8
	j := 4
	if i % j == 0 {
		fmt.Println("It is divisible")
	}
}

$ go run ifelse.go
Hello
value is less or equal than 200  190
It is divisible

Go Switch

¿Cómo usar Switch en Golang?

Switch es otro control condicional que te permite controlar el flujo de tu código.

Observa que en Golang para el switch no es necesario agregar break como sucede por ejemplo en Java. El break esta implicito. Puedes agregar opcionalmente “default” para que en el caso de que ninguna condición se cumpla. Además, es posible comparar más de un valor en un “case”.

package main

import (
	"fmt"
	"time"
)

func main() {

	// basic switch - 'break' it is not necessary
	value := 4
	switch value {
	case 1:
		fmt.Println("value is one")
	case 2:
		fmt.Println("value is two")
	case 3:
		fmt.Println("value is three")
	case 4, 5:
		fmt.Println("value is four or five")
	default:
		fmt.Println("I do not recognize the value")
	}

	// switch without condition, like if
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon")
	default:
		fmt.Println("Go home")
	}
}

$ go run switch.go
value is four or five
Good afternoon

Go Maps

¿Cómo crear Mapas en Golang?

Al igual que en otros lenguajes un mapa relaciona un key con un valor.
Así creas un mapa en Golang con un key entero que represente un id, y un valor que represente un el nombre de un producto:

// map literal
var x = map[int]string{
      1: "coffee",
      2: "te",
      3: "chocolate",
}

// adding more elements
x[4] = "cappuccino"
fmt.Println("key value of map x", x)


// retrieve one element
product := x[2]
fmt.Println("Product of key 2", product)

// delete element
delete(x, 2)

// check if element exist
element, okExist := x[1]
if(okExist) {
      fmt.Println("Element with id 1 is",element)
}

key value of map x map[1:coffee 2:te 3:chocolate 4:cappuccino]
Product of key 2 te
Element with id 1 is coffee

Para crear un map vacío en Golang usas la función make

// create a map using make
m := make(map[string]string)
m["key1"] = "value1"
m["key2"] = "value2"
fmt.Println("key value of map m", m)
key value of map m map[key1:value1 key2:value2]

Go Funciones

¿Cómo crear funciones en Golang

En Golang las funciones pueden recibir cero o más argumentos y retornar cualquier cantidad de elementos acordes al tipo.

package main

import "fmt"

// function receive two arg and return one
func plus(a int, b int) int {
      return a + b
}

// function receive two arg and return two
func hello(h string, w string) (string, string) {
      return h, w
}

func main() {
      fmt.Println("plus", plus(2, 6))
      fmt.Println(hello("hello", "golang"))
}

La salida de estas funciones

$ go run functions.go
plus 8
hello golang

Go Structs (estructuras)

¿Qué son y cómo crear structs en Golang?

Un Struct en Golang define una estructura de fields / campos. Si vienes de Java lo puedes pensar como una clase que no permite herencia pero que sí permite composición. El acceso a los campos es directo desde el struct, es decir, no requieres getters y setters.

package main

import "fmt"

type User struct {
      Name     string
      LastName string
}

func main() {

      u := User{"Albert", "Einstein"}
      fmt.Println(u)

      fmt.Println(u.Name, "was a genius");
}
$ go run structs.go
{Albert Einstein}
Albert was a genius

Go Métodos

¿Cómo se escribe un método en Golang?

En Golang un método es un tipo de función con un receptor definido como argumento.
Observa que a diferencia de las funciones comunes este receptor va entre el keyword “func” y el nombre del método.

Por ejemplo, si tienes un Struct User que representa un usuario

type User struct {
	Name     string
	LastName string
}

puedes definir un método cuyo receptor sea ese User

func (u User) CheckGenius() bool {
      return u.Name == "Albert"
}

que luego debes usar de este modo

func main() {

	u := User{"Albert", "Einstein"}


	// using method throw User.CheckGenius
	fmt.Println("Method CheckGenius", u.Name, u.CheckGenius())


}


Así queda el código, en Golang escribes este método afuera del Struct.

package main

import "fmt"

type User struct {
      Name     string
      LastName string
}

// this is a method
func (u User) CheckGenius() bool {
      return u.Name == "Albert"
}

// this is a common function
func CheckGenius(u User) bool {
      return u.Name == "Albert"
}

func main() {

      u := User{"Albert", "Einstein"}

      // using method throw User.CheckGenius
      fmt.Println("Method CheckGenius", u.Name, u.CheckGenius())

      // using a common function
      fmt.Println("Function CheckGenius", CheckGenius(u))

}

Go Imports

¿Cómo importas en Go?

Para importar en Go puedes hacerlo agrupado o linea por linea

Import linea por linea

package main

import "fmt"
import "time"

Import agrupado (recomendado).

package main

import (
	"fmt"
	"time"
)

func main() {
	h, m, s := time.Now().Clock()
	fmt.Println("Hour: ", h)
	fmt.Println("Minute: ", m)
	fmt.Println("Second: ", s)
}
$ go run imports.go
Hour:  13
Minute:  20
Second:  48

Go Packages

¿Cómo organiza Go sus archivos?

Go organiza sus programas en paquetes y se inicia su ejecución en el package main

package main

func main() {
      print("Hola, Mundo!")
}
$ go run packages.go
Hola, Mundo!

Go, por convención, usa como nombre de package el último elemento de la ruta de los import.

Por ejemplo, si importamos el paquete “math / rand”, esto incluye todos los archivos que se encuentran en el paquete rand.

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	fmt.Println("Random number", rand.Intn(10))
}

Referencias:
Estos ejemplos están basados en la excelente Guia oficial de Go

Compartir esto:

Go Hola Mundo

¿Cómo escribir un hola mundo en Golang?

En Go escribes un ‘hola mundo’ de este modo:

package main

import "fmt"

func main() {
	fmt.Printf("hola mundo\n")
}

-Los paquetes se definen en la primer línea como “package”
-A continuación van los “import”
-El ‘main’ del programa se inicia con func main()

¿Como ejecutar hello-world.go?

$ go run hello-world.go
hola mundo

¿Cómo hacer un build de hello-world.go?

$ go build hello-world.go
$ ./hello-world
hola mundo

Compartir esto:

Como instalar Go en Mac OS

Para instalar Golang en Mac OS sigue estos pasos:

Descargar la versión de golang para Mac desde esta dirección:

https://golang.org/dl/

Al finalizar la descarga, ejecuta el archivo pkg descargado.

Sigue los pasos del instalador de go

 

Al finalizar la instalación go queda en la carpeta /usr/local/go

Crea tu espacio de trabajo

Go utiliza como convención la ruta /go dentro de tu home como espacio de trabajo para los programas.

mkdir $HOME/go

Agrega tu espacio de trabajo al path

Go busca usando la variable de entorno GOPATH la ubicación de tu workspace. Agrega esta variable de entorno en tu archivo bash_profile de así

 
vi ~/.bash_profile
export GOPATH=$HOME/go

Agrega Go al path

El instalador agrega automáticamente al PATH la carpeta /usr/local/go/bin .
Verifica que Go esté incluido en el PATH buscando la ruta /usr/local/go/bin

~ $ echo $PATH
< ...others paths... : > /usr/local/go/bin
~ $

Si no está entonces agrega go al path en tu archivo ~/.bash_profile

 
vi ~/.bash_profile
export PATH=$PATH:/usr/local/go/bin

Comprueba la instalación

Comprueba go escribiendo go version desde la consola.

~ $ go version
go version go1.9.1 darwin/amd64
~ $

También puedes ejecutar go env desde la consola para ver todas las variables de entorno establecidas

~ $ go env
...
..
GOPATH="/Users/gustavo/go"
GOROOT="/usr/local/go"
..
...
~ $

Referencia: golang

Compartir esto:

Strategy Pattern con Spring Boot

Strategy Pattern con Spring Boot

Un buen patrón para resolver la complejidad cuando debes implementar diferentes comportamientos según algún estado es el patrón strategy.

Un patrón strategy encapsula comportamiento que podemos intercambiar en ejecución. Es decir, cambiar el comportamiento o lógica de negocio acorde al estado del modelo o contexto en el que te se encuentra, evitando el uso extensivo de if o switch en tu código.

Es bastante simple. Necesitas una interfaz Strategy y diferentes implementaciones para la resolución de los diferentes comportamientos deseados.

Para este ejemplo suponemos que tenemos una entidad “User” que representa un usuario. Estos users son clasificados por tipo acorde a un enum “UserType”. El UserType determinará el valor del atributo limitCredit del User.

@Entity
public class User {

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private Long id;

   @NotBlank
   private String userName;

   @NotBlank
   private String lastName;

   private UserType type;

   private double limitCredit;


}

public enum UserType {
   NORMAL, FULL, GOLD;
}


Queremos que nuestro servicio se desentienda de esta decisión, por lo que crearemos diferentes estrategias para cada tipo de user.

Cómo crear el patrón Strategy

Defines la interfaz de la cual luego vas a implementar los diferentes comportamientos. En este ejemplo simple se trata de cambiar el limite bajo algún cálculo que suponemos complejo para los diferentes tipos de User.

public interface Strategy {
   void changeLimit(User user);
}

Las implementaciones de las estrategias para cada tipo UserType quedan así:

public class StrategyOperationNormal implements Strategy {
   @Override
   public void changeLimit(User user) {
       // a complex calculation.. 
       user.setType(UserType.NORMAL);
       user.setLimitCredit(1000D);
   }
}

public class StrategyOperationFull implements Strategy {
   @Override
   public void changeLimit(User user) {
       user.setType(UserType.FULL);
       user.setLimitCredit(5000D);
   }
}
public class StrategyOperationGold implements Strategy {

   @Override
   public void changeLimit(User user) {
       user.setType(UserType.GOLD);
       user.setLimitCredit(20000D);
   }

}

Usar un Factory que nos devuelva el Strategy

Para simplificar aún más el uso de Strategy en tu servicio creas una clase StrategyFactory. Este factory te devolverá la estrategia correspondiente según el UserType.

Un patrón factory te permite crear objetos bajo alguna condición sin necesidad de conocer o preocuparte acerca de la implementación real, en definitiva solo conoces la interfaz.

@Component
public class StrategyFactory {

   private Map<UserType, Strategy> strategies = new EnumMap<>(UserType.class);

   public StrategyFactory() {
       initStrategies();
   }

   public Strategy getStrategy(UserType userType) {
       if (userType == null || !strategies.containsKey(userType)) {
           throw new IllegalArgumentException("Invalid " + userType);
       }
       return strategies.get(userType);
   }

   private void initStrategies() {
       strategies.put(UserType.NORMAL, new StrategyOperationNormal());
       strategies.put(UserType.FULL, new StrategyOperationFull());
       strategies.put(UserType.GOLD, new StrategyOperationGold());
   }
  
}


En tu servicio haces uso del factory para obtener la estrategia y delegarle la lógica de cambio de tipo de UserType en el metodo “changeType” .

@Service
public class UserService {

   private final StrategyFactory strategyFactory;
   private final UserRepository repository;

   @Autowired
   public UserService(UserRepository repository, StrategyFactory strategyFactory) {
       this.repository = repository;
       this.strategyFactory = strategyFactory;
   }

   // other methods of service..

   public User changeType(long id, UserType type) {
       Strategy strategy = strategyFactory.getStrategy(type);
       User user = repository.findOne(id);
       strategy.changeLimit(user);
       return repository.save(user);
   }

}

Probando tu Strategy

Usando el Controller de la app, que tenemos ya definido, creamos un User. Por defecto el User se crea con un tipo “NORMAL”.

El controller de nuestra app

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/users", method = RequestMethod.GET)
    public ResponseEntity<User> list() {
        List<User> users = userService.list();
        return new ResponseEntity(users, HttpStatus.OK);
    }

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public ResponseEntity<User> userById(@RequestParam(value = "id") long id) {
        User user = userService.get(id);
        return new ResponseEntity(user, HttpStatus.OK);
    }

    @RequestMapping(value = "/create", method = RequestMethod.POST)
    public ResponseEntity<User> create(@Valid @RequestBody User user) {
        User userCreated = userService.create(user);
        return new ResponseEntity(userCreated, HttpStatus.CREATED);
    }

    @RequestMapping(value = "/change_type", method = RequestMethod.POST)
    public ResponseEntity<User> changeType(@RequestParam(value = "id") long id,
                                           @RequestParam(value = "type") UserType type) {
        User userCreated = userService.changeType(id, type);
        return new ResponseEntity(userCreated, HttpStatus.CREATED);
    }

}

El Service completo de nuestra app

@Service
public class UserService {

    private final StrategyFactory strategyFactory;
    private final UserRepository repository;

    @Autowired
    public UserService(UserRepository repository, StrategyFactory strategyFactory) {
        this.repository = repository;
        this.strategyFactory = strategyFactory;
    }

    public User get(long userId) {
        return repository.findOne(userId);
    }

    public List<User> list() {
        Iterable<User> users = repository.findAll();
        List<User> list = new ArrayList<User>();
        users.forEach(list::add);
        return list;
    }

    public User create(User user) {
        Strategy strategy = strategyFactory.getStrategy(UserType.NORMAL);
        strategy.changeLimit(user);
        return repository.save(user);
    }


    public User changeType(long id, UserType type) {
        Strategy strategy = strategyFactory.getStrategy(type);
        User user = repository.findOne(id);
        strategy.changeLimit(user);
        return repository.save(user);
    }

}

Ejecutas el rest api “/create” para crear un User que luego vamos a modificar. Enviamos en el body lo mínimo que necesita la entidad User.
El user se crea con el type “NORMAL” por defecto.

Luego ejecutas el rest api “/change_type?id=1&type=FULL” para probar tu Servicio que usa el Factory y el Strategy. Aquí intentas cambiar el type de NORMAL a FULL para el user previamente creado.

Observa que el user ha cambiado su límite acorde a la Strategy.

Descarga este código completo desde GitHub

Compartir esto:

Spring Boot – como capturar las excepciones y retornar un json estandar

Cómo capturar y manejar las excepciones con Spring Boot para una Rest Api

En una aplicacion Spring en la cual exponemos endpoints Rest para que los clientes la consuman contamos con un manejador de excepciones de forma global para toda tu app.
Esto nos da la flexibilidad de decidir que devolver al cliente para cada tipo de exception.

En este ejemplo te muestro como capturar una excepción cuando el cliente consume tu api y el body que te ha enviado no es válido.

En el caso de recibir un body inválido Spring crea una excepción MethodArgumentNotValidException y retorna un json que seguramente sobra para el cliente o información que te gustaría estandarizar para todos los errores de forma más acorde para tu api.

El modelo que representa el body que vas a recibir

public class User {

    @JsonProperty
    @NotBlank
    private String userName;

    @JsonProperty
    @NotBlank
    private String lastName;

    @JsonProperty
    @NotNull
    private Gender gender;

    @JsonProperty
    @Min(value = 18)
    @Max(value = 150)
    @NotNull
    private Integer age;

    @JsonProperty
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
    @Past
    @NotNull
    private Date dateOfBirth;

Resultado de una exception MethodArgumentNotValidException

Este es el resultado que recibes cuando Spring lanza una excepción al recibir un body inválido en un post. En este ejemplo se omite un tag requerido “dateOfBirth” en el body y por esta razón se lanza la excepción.

¿Cómo debes capturar esta excepción y devolver algo a tu gusto?

Creas la clase que representará la respuesta json

Defines tu clase con los json property que deseas mostrar como retorno del error. Suponemos que deseas devolver tres atributos simples, el mensaje, el status code, y la url.

import com.fasterxml.jackson.annotation.JsonProperty;

public class ErrorInfo {

   @JsonProperty("message")
   private String message;
   @JsonProperty("status_code")
   private int statusCode;
   @JsonProperty("uri")
   private String uriRequested;

   public ErrorInfo(ApiException exception, String uriRequested) {
       this.message = exception.getMessage();
       this.statusCode = exception.getStatusCode().value();
       this.uriRequested = uriRequested;
   }

   public ErrorInfo(int statusCode, String message, String uriRequested) {
       this.message = message;
       this.statusCode = statusCode;
       this.uriRequested = uriRequested;
   }

   public String getMessage() {
       return message;
   }

   public int getStatusCode() {
       return statusCode;
   }

   public String getUriRequested() {
       return uriRequested;
   }

}

Creas el manejador global de excepciones ControllerAdvice

Para capturar este error debes crear una clase anotada @ControllerAdvice allí definirás cada tipo de error que deseas manejar @ExceptionHandler. Este método recibe el request y la excepción en particular. Puedes manejar todas las excepciones que desees.

Para nuestro caso:

@ControllerAdvice
public class ErrorHandler {

   @ExceptionHandler(MethodArgumentNotValidException.class)
   public ResponseEntity<ErrorInfo> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
       ErrorInfo errorInfo = new ErrorInfo(HttpStatus.BAD_REQUEST.value(), e.getMessage(), request.getRequestURI());
       return new ResponseEntity<>(errorInfo, HttpStatus.BAD_REQUEST);
   }
}

Si pruebas el resultado verás esta respuesta, algo mejor, pero todavia el mensaje está poco claro.

Mejorar el mensaje

Para hacer más legible el mensaje puedes leer cada uno de los FieldError que son devueltos por MethodArgumentNotValidException.getBindingResult() para armar un string más legible.

@ControllerAdvice
public class ErrorHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorInfo> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {

        // get spring errors
        BindingResult result = e.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();

        // convert errors to standard string
        StringBuilder errorMessage = new StringBuilder();
        fieldErrors.forEach(f -> errorMessage.append(f.getField() + " " + f.getDefaultMessage() +  " "));

        // return error info object with standard json
        ErrorInfo errorInfo = new ErrorInfo(HttpStatus.BAD_REQUEST.value(), errorMessage.toString(), request.getRequestURI());
        return new ResponseEntity<>(errorInfo, HttpStatus.BAD_REQUEST);

    }

}

Observa el resultado nuevamente al enviar un post que no válida:

Conclusión

Puedes hacer uso de ControllerAdvice para capturar todas las excepciones de tu api y devolver un formato json estandarizado para toda tu aplicación.

Descarga este código completo desde GitHub

Compartir esto:

Spring Boot + MySql + HikariCP

Configurar Spring Boot con MySql usando HikariCP

Para configurar tu proyecto Spring con MySql utilizando HikariCP debes agregar las dependencias y configurar tu archivo properties con estos simples pasos.

Dependencias

Detalle de dependencias que necesitas para tu proyecto Spring con MySql y Hikari

<dependencies>

   <!-- Spring boot -->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
   </dependency>

   <!-- Hikari -->
   <dependency>
       <groupId>com.zaxxer</groupId>
       <artifactId>HikariCP</artifactId>
       <version>2.6.0</version>
   </dependency>

   <!-- MySQL Connector -->
   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
   </dependency>
</dependencies>

Definición del archivo properties

Debes especificar el dialect para MySql, y para el tipo de datasource indicar HikariDataSource.
Así queda tu archivo application.properties

spring.jpa.hibernate.ddl-auto: create-drop
spring.jpa.show-sql: true
spring.jpa.properties.hibernate.dialect: org.hibernate.dialect.MySQL5Dialect

spring.datasource.url: jdbc:mysql://localhost:3306/example_db
spring.datasource.username: root
spring.datasource.password:
spring.datasource.driverClassName: com.mysql.jdbc.Driver
spring.datasource.type: com.zaxxer.hikari.HikariDataSource

 ## HikariCP config - spring.datasource.hikari.*
spring.datasource.hikari.pool-name: pool-hikari-example
spring.datasource.hikari.maximum-pool-size: 10
spring.datasource.hikari.connection-timeout: 60000


Inicia tu aplicación con Spring Boot + MySlq + Hikari

Corres tu app mvn spring-boot:run

Observa en la consola esta salida en dónde puedes ver que ha levantado el datasource de Hikari con el pool que especificaste previamente en tu archivo de configuración.

    spring.datasource.hikari.pool-name: pool-hikari-example

Salida del log de Spring

INFO 59268 --- [           main] com.zaxxer.hikari.HikariDataSource       : pool-hikari-example - Start completed.

Con esto ya tienes tu configuración usando HikariCP.

Descarga este código completo

Compartir esto: