martes, 9 de febrero de 2016

Metodos Default en Java 8

Java 8 nos trae nuevos temas que se han introducido en el lenguaje para hacer las cosas más fáciles al programador. Entre ellas están los métodos default.

Si hasta ahora en una interfaz solo podíamos tener constantes y métodos abstractos (cabecera de método), ahora tendremos la posibilidad de declarar métodos con un comportamiento por defecto en una interfaz. Antes de Java 8 podíamos definir que toda clase que implementa a una interfaz debe construir, declarar, dar cuerpo a los distintos métodos que contenga la interfaz. En cambio, en Java 8, toda clase que implemente una interfaz debe declarar los distintos métodos que contenga la interfaz salvo aquellos que estén definidos como métodos por defecto.

Estos métodos se caracterizan porque son métodos que están declarados en la propia interfaz y pueden ser utilizados directamente en la clase si nos interesa su comportamiento por defecto.

public interface Color {
 int getColor();
 void setColor(int color);
 default void agregarColor() {
   setColor(getColor()+1);
 }
 default void eliminarColor() {
   int colorActual=getColor();
   if (colorActual>0) { 
     setColor(getColor()-1);
   } else {
     setColor(0);
   }   
 }
}

viernes, 5 de febrero de 2016

El nuevo Stream Api de Java 8

Seguiremos hablando sobre la versión 8 de Java, en conjunto con las expresiones Lambda, también se introduce el concepto de Streams, esta nueva característica ofrece comportamientos similares a los iteradores que todos conocemos pero con un enfoque distinto. Los Streams son objetos de clases que implementan la interfaz Stream (desde el paquete java.util.stream) o una de las interfaces especializadas Stream enfocadas en el procesamiento de colecciones de valores int, long o double.

Antes de proseguir recomiendo a todos los que no estén del todo familiarizados con las Expresiones Lambda que se detengan en este punto y lean primero el artículo donde hablamos sobre este tema

Tuberías Stream

Los Streams mueven los elementos a través de una secuencia de pasos de procesamiento llamados tuberías Stream o Stream Pipelines (como se les nombra en Inglés) y que comienzan con una fuente de datos que puede ser tanto un arreglo o una colección, ejecutando sobre estos operaciones intermedias, finalizando evidentemente con una operación final. Una tubería Stream se forma encadenando llamadas a métodos y a diferencia de las colecciones estas no cuentan con almacenamiento propio, por esto es que un Stream, una vez utilizada, no se puede volver a usar debido a que esta no mantiene una copia de los datos originales.

Operaciones Intermedias y Finales

Una operación intermedia aplica una tarea específica sobre el elemento actual del flujo de datos, dando como resultado un nuevo flujo ya modificado. Se dice que las operaciones intermedias son lazy (perezosas) ya que estas no comienzan su cadena de ejecución hasta que la operación final es invocada.

Por ejemplo si usted tiene una colección de 1,000,000 objetos Persona y deseamos encontrar el primero de estos cuyo primer apellido sea Bergery, el flujo de procesamiento puede terminar tan pronto como sea encontrada la primera coincidencia.

Una operación final comienza procesando la cadena de operaciones intermedias especificadas por nosotros finalizando con un resultado, que teniendo en cuenta el ejemplo anterior este sería un solo objeto del total inicial. Este comportamiento le da el nombre de eager (ansioso) que sería lo contrario de lazy (perezoso) en comparación a las operaciones intermedias, estos ejecutan las operaciones requeridas al momento de ser invocados.

Operaciones intermedias más com

filter, distinct, limit, map, sorted



Operaciones finales más comunes:

forEach, average, count, max, min, reduce, collect, toArray, findFirst, findAny, anyMatch, allMatch



Vamos a ver un ejemplo donde vemos en acción todas estas operaciones:

package com.wiseratel;
 
import java.util.stream.IntStream;
 
public class StreamAPI {
 
    public static void main(String[] args) {
 
        int[] values = {3, 10, 6, 1, 4, 8, 2, 5, 9, 7};
 
        // Mostrar los valores originales
 
        System.out.println("Valores originales: ");
 
        IntStream.of(values).forEach(value -> System.out.printf("%d ", value));
 
        System.out.println();
 
        // Ejecución de las funciones count, min, max, sum y average sobre los valores
 
        System.out.printf("%nCount: %d%n", IntStream.of(values).count());
 
        System.out.printf("Min: %d%n", IntStream.of(values).min().getAsInt());
 
        System.out.printf("Max: %d%n", IntStream.of(values).max().getAsInt());
 
        System.out.printf("Sum: %d%n", IntStream.of(values).sum());
 
        System.out.printf("Average: %.2f%n", IntStream.of(values).average().getAsDouble());
 
        // Suma de los valores con el método reduce
 
        System.out.printf("%nSuma usando el método reduce: %d%n", IntStream.of(values).reduce(0, (x, y) -> x + y));
 
        // Suma de los cuadrados de los valores usando el método reduce
 
        System.out.printf("Suma de los cuadrados usando el método reduce: %d%n", IntStream.of(values).reduce(0, (x, y) -> x + y * y));
 
        // Producto de los valores usando el método reduce
 
        System.out.printf("Productos usando el método reduce: %d%n", IntStream.of(values).reduce(1, (x, y) -> x * y));
 
        // Números pares mostrados de manera ordenada
 
        System.out.println();
 
        System.out.println("Números pares mostrados de manera ordenada: ");
 
        IntStream.of(values).filter(value -> value % 2 == 0).sorted().forEach(value -> System.out.printf("%d ", value));
 
        System.out.println();
 
        System.out.println("Números impares multiplicados por 10 y mostrados de manera ordenada: ");
 
        IntStream.of(values).filter(value -> value % 2 != 0).map(value -> value * 10).sorted().forEach(value -> System.out.printf("%d ", value));
 
        System.out.println();
 
        // Suma los valores del 1 al 10, exclusivo
 
        System.out.printf("%nSuma los valores del 1 al 9, exclusivo: %d%n", IntStream.range(1, 10).sum());
 
        // Suma los valores del 1 al 10, inclusivo
 
        System.out.printf("Suma los valores del 1 al 10, inclusivo: %d%n", IntStream.rangeClosed(1, 10).sum());
 
    } // main
 
} // StreamAPI



Esta seria la salida en pantalla:

3 10 6 1 4 8 2 5 9 7 
 
Count: 10
Min: 1
Max: 10
Sum: 55
Average: 5.50
 
Suma usando el método reduce: 55
Suma de los cuadrados usando el método reduce: 385
Productos usando el método reduce: 3628800
 
Números pares mostrados de manera ordenada: 
2 4 6 8 10 
Números impares multiplicados por 10 y mostrados de manera ordenada: 
10 30 50 70 90 
 
Suma los valores del 1 al 9, exclusivo: 45
Suma los valores del 1 al 10, inclusivo: 55

Lambda expresiones Java 8

Hola amigos anterior a la versión 8 de Java se contaba con el soporte para tres paradigmas de programación:


  • Programación Procedural
  • Programación Orientada a Objetos
  • Programación Genérica


En esta versión se añade uno más:


  • Programación Funcional


Todas las características nuevas, tanto a nivel de lenguaje como de librerías, que dan soporte a este nuevo (en el caso de Java) paradigma fueron añadidos a Java como parte del proyecto Lambda.

En este artículo intentaré explicar y definir lo que es la Programación Funcional, aprenderemos a escribir código mucho más rápido, más conciso y con menos errores que cuando usábamos técnicas anteriores.

La programación funcional tiene sus raíces en el cálculo lambda, en el caso específico de Java, este paradigma se apoya en las expresiones lambda, un recurso sintáctico que permite expresar funciones anónimas, que no pertenecen a ninguna clase y que implementan interfaces funcionales (un solo método abstracto) de forma muy compacta.

Los lenguajes de programación funcionales, especialmente los que son puramente funcionales, son aquellos donde las variables que se manejan no tienen estado — no hay cambios en éstas a lo largo del tiempo — y son inmutables — no pueden cambiarse los valores a lo largo de la ejecución, además los programas se estructuran componiendo expresiones que se evalúan como funciones, han sido enfatizados en el ambiente académico principalmente y no tanto en el desarrollo de software comercial, aunque lenguajes como R (estadística), Mathematica (matemáticas simbólicas), Haskell y F# en Microsoft.NET son solamente algunos ejemplos de usos en la industria a través de lenguajes de dominio específico.

La programación funcional también puede ser desarrollada en lenguajes que no fueron diseñados específicamente con este enfoque en mente, como es el caso de Java en su versión 8, donde como ya hemos explicado se ha incorporando la programación funcional y el uso de las expresiones lambda, brindando una oportunidad de desarrollo que hasta el momento el lenguaje no tenía, actualizándolo a los tiempos que vivimos.

Veamos a continuación la sintaxis de una expresión Lambda:

(Parámetros) -> { cuerpo de la expresión }



Su sintaxis es muy sencilla, se comienza con unos paréntesis () donde van los argumentos del método seguidos de -> para indicar el cuerpo que debe ser denotado por corchetes {} a menos que sea una sola línea. Los aspectos a tener en cuenta en cada declaración serían los siguientes:


  1. El operador lambda (->) es mandatorio.
  2. Si la expresión posee una sola línea, pueden omitirse los paréntesis y/o llaves.
  3. Si y solo si se esta enviando un único parámetro es posible quitar los paréntesis.
  4. Si no se envía ningún parámetro se deben de colocar los paréntesis vacíos.


Ejemplo:

(int x) -> x * x
 
(int x, int y) -> { return x + y; }
 
(int value) -> System.out.println("%d", value)
 
String s -> textfield.setText(s.toUpperCase())


La inferencia de tipos nos ayuda a simplificar aun más nuestras expresiones:

x -> x * x
 
x, y -> x + y
 
s -> textfield.setText(s.toUpperCase())


Seguidamente veremos algunos ejemplos de uso:

import java.util.function.*;
 
public class Main {
 
    public static void main(String[] args) {
 
        Consumer imprimir = (texto) -> System.out.println(texto);
 
        imprimir.accept("\nFuck Yehaaa!!!");
 
        Predicate verifyAge = (age) -> age > 10;
 
        System.out.println("\nLa edad es mayor a 10: " + verifyAge.test(12));
 
        BinaryOperator sum = (x, y) -> { return x + y; };
 
        System.out.println("\nLa suma de 23.7 + 65.2 es igual a: " + sum.apply(23.7, 65.2));
 
    } // main
 
} // Main


La salida en pantalla sería:

La edad es mayor a 10: true
 
La suma de 23.7 + 65.2 es igual a: 88.9


En este ejemplo hemos usado varias interfaces funcionales, aquellas incluidas en el paquete java.util.function, en las cuales nos apoyamos para ejemplificar el uso de las expresiones lambda. En la línea 9 hacemos uso de la interfaz funcional Consumer la cual contiene un método abstracto llamado “accept” el cual toma un parámetro T y no devuelve nada. Al igualar la instancia de esta interfaz a la expresión lambda estamos asignando una funcionalidad al método “accept”, estaríamos implementandolo al vuelo, en otras palabras; en este caso imprimimos el objeto String que pasamos como parámetro aunque como podemos observar no lo especificamos en la expresión lambda ya que el compilador infiere o toma esta información al momento de crear nuestra instancia del objeto Consumer donde especificamos de que tipo será el parámetro T. En la línea 11 ejecutamos el método “accept” pasándole un texto cualquiera, acción que se ejecuta de manera satisfactoria generando la primera línea en la salida en pantalla.

Así sucede con las líneas siguientes donde hacemos uso de las interfaces funcionales Predicate y BinaryOperator, a las que asignamos su respectiva expresión lambda obteniendo el resultado esperado como podemos constatar en la salida en pantalla.