Java 8. Com simular closures amb classes internes i expressions lambda

El passat 18 de març es va publicar la versió 8 de Java SE/ME. El JDK 8. Es tracta, doncs,  d’una versió que encara manté l’escalforeta del forn.

La nova versió de Java inclou característiques que, segons Oracle converteixen aquesta release en “revolucionària”: “Java 8 is a revolutionary release of the world’s #1 development platform. It includes a huge upgrade to the Java programming model and a coordinated evolution of the JVM, Java language, and libraries. Java 8 includes features for productivity, ease of use, improved polyglot programming, security and improved performance.”

En aquest article exploren els trets més característics de la release Java 8. En resum, serien aquests:

–  Expressions lambda.  Es tracta d’una estructura que prové de la programació funcional. Es tracta d’una reclamació antiga de la comunitat de programadors Java que va patir una gran decepció quan  es va saber que Java 7 no incorporava elements de la programació funcional. Els llenguatges funcionals ofereixen una estructura, les clausures (closures) molt potent. Les  closures fa temps que “estan de moda”. De fet, molts dels llenguatges per a la JVM -com Clojure, Scala i Groovy- fa temps que la suporten.

Doncs bé, Java 8 NO incorpora closures. Però la funcionalitat de les closures es pot aproximar amb inner classes i expressions lambda. El motiu de fons és que al Java els objectes són els elements de primer ordre, mentre que a un llenguatge funcional, ho són les funcions. Java treballa amb noms, i els llenguatges funcionals amb verbs.

Si el paràgraf anterior sembla críptic és perquè, efectivament, ho és.

Nashorn. Nou engine Javascript nadiu de la JVM . Nashorn reemplaça al veterà Rhino i proporciona accés a la immensitat de les llibreries de Java des d’un llenguatge més lleuger i interpretat com Javascript. Nashorn permetrà una major integració entre Java i Javascript.

Java ME i Compact Profiles. La plataforma Java es presenta en tres edicions: Standard, Enterprise i Micro . L’actual release 8 avança les edicions estàndard i micro.

L’edició micro és la que es fa servir a hardware divers com set-top boxes, electrònica de consum, telèfons mòbils… Aquesta edició sembla que està cridada a augmentar la seva importància amb les smart cities i la “Internet of things”.

Java ME defineix perfils de hardware (memòria disponible, sensors, connectivitat…) i la versió Java 8 ME afegeix nous perfils als ja existents.

– Nova API per a Temps i Dates.

– Integració de Java FX. On Java FX és la resposta Java a Flash (Flex) d’Adobe o SilverLight de Microsoft.

– Major participació de la comunitat Java:  major pes dels JUGs (Java Users Groups) en la definició del llenguatge i programa “Adopt a JSR (Java Specification Request)”; Open JDK;  Millora del JCP (Java Community Process).

– Millores a la JVM que afecten de forma destacable al GC (Garbage collector). Millora general del rendiment.

Aquests són els grans trets.

 

Closures amb inner class i expressions lambda

Com de costum, el millor sempre és provar amb les pròpies mans.

Una bona idea és descarregar-se el JDK 8. Es pot fer des de la pàgina de descàrreges de la web d’Oracle. Jo m’he descarregat la versió empaquetada amb NetBeans 8.0.

Punt de partida: Feia temps que volia tocar les closures. Tenia preparat aquest exemple amb Javascript:

<!DOCTYPE html>
<html>
    <head>
    <script>
    f = function(x) {
        var z = x;
 
        g = function(x) {
            z++;
            return x + z;
        };
 
        return g;
 }
 </script> 
 </head>
 <body>
     <script language>
     f2 = f(1);
     f3 = f(2);
     f4 = f(3);
     document.writeln("Value f2(3): " + f2(3) + "</br>");
     document.writeln("Value f3(3): " + f3(3) + "</br>");
     document.writeln("Value f4(3): " + f4(3) + "</br>");
     document.writeln("Value f2(3): " + f2(3) + "</br>");
     document.writeln("Value f3(3): " + f3(3) + "</br>");
     document.writeln("Value f4(3): " + f4(3) + "</br>");
     document.writeln("Value f2(3): " + f2(3) + "</br>");
     document.writeln("Value f3(3): " + f3(3) + "</br>");
     document.writeln("Value f4(3): " + f4(3) + "</br>");
 </script>
 </body>
</html>
 
Presenta:
 
Value f2(3): 5
Value f3(3): 6
Value f4(3): 7
Value f2(3): 6
Value f3(3): 7
Value f4(3): 8
Value f2(3): 7
Value f3(3): 8
Value f4(3): 9

Jo veig les clausures com funcions amb estat. Estem acostumats a veure factories d’objectes amb java, doncs bé, el codi anterior no deixa de ser una factoria de funcions.

 

Següent pas: Expressions lambda a Java 8.

Per a definir una expressió lambda amb Java 8 cal seguir els següents passos:

1. Cal definir la interface funcional de l’expressió lambda. Una interface amb un únic mètode (la funció):

interface InterfaceFuncional(Tipus1 var1, Tipus2 var2,..) {
  TipusRetorn UnUnicMètode(Tipus1 var1, Tipus2 var2,..);
}

2. A continuació, l’expressió lambda pròpiament dita.

InterfaceFuncional varFuncional = (Tipus var1, Tipus var2,...) -> {
  instruccions;
  instruccions;
  ...
}

3. Finalment, la invocació de l’expressió lambda:

varFuncional.UnUnicMetode(var1, var3, ...);

 

Això no va

Com podria fer amb Java 8 i expressions lambda el mateix que a l’exemple amb JavaScript?

Doncs bé, realment no es pot fer de forma directa. El següent codi dona un error de compilació:

package cat.apuntsdetecnologia;

/**
 *
 * @author albert
 */
public class ProvaClosures {
  /**
  * @param args the command line arguments
  */
   
  public static void main(String[] args) {
    new ProvaClosures();
  }
 
  interface g {
    int operation(int x);
  } 
 
  interface f {
    g init(int x); 
  }

  public ProvaClosures() {
    f f1 = (x) -> {
      int z_f = x; 
      
      g g1 = (y) -> {
        z_f++;
        return y + z_f;
      };
    
      return g1;
    };

    g g1 = f1.init(1);
    g g2 = f1.init(2);
    g g3 = f1.init(3);
 
    System.out.println("Value g1.operation(3): " + g1.operation(3));
    System.out.println("Value g2.operation(3): " + g2.operation(3));
    System.out.println("Value g3.operation(3): " + g3.operation(3));
    System.out.println("---------------------------------------");
    System.out.println("Value g1.operation(3): " + g1.operation(3));
    System.out.println("Value g2.operation(3): " + g2.operation(3));
    System.out.println("Value g3.operation(3): " + g3.operation(3));
    System.out.println("---------------------------------------");
    System.out.println("Value g1.operation(3): " + g1.operation(3));
    System.out.println("Value g2.operation(3): " + g2.operation(3));
    System.out.println("Value g3.operation(3): " + g3.operation(3)); 
  }
}

Què és el que falla? Que dins l’expressió lambda la variable z_f ha de ser final. Però un mètode java NO pot tenir un estat intern. L’estat el manté la instància de la classe. En particular, una expressió lambda de java tampoc pot mantenir un estat intern. Si vull simular la closure hauré de fer servir classes.

 

Però això sí

El següent codi SÍ que compila. Com simular una closure amb una classe interna i una expressió lambda amb Java 8?

 

package cat.apuntsdetecnologia;

/**
 *
 * @author albert
 */
public class ProvaClosures {
  /**
   * @param args the command line arguments
   */
  public static void main(String[] args) {
    new ProvaClosures();
  }

  interface g {
    int operation(int x);
  } 

  public ProvaClosures() {
    class f {
      int z_f;
      
      g init(int x) {
        z_f = x;
        
        g g1 = (y) -> {
          this.z_f++;
          return y + this.z_f;
        };
 
        return g1;
      }
    }

    g g1 = (new f()).init(1);
    g g2 = (new f()).init(2);
    g g3 = (new f()).init(3);

    System.out.println("Value g1.operation(3): " + g1.operation(3));
    System.out.println("Value g2.operation(3): " + g2.operation(3));
    System.out.println("Value g3.operation(3): " + g3.operation(3));
    System.out.println("---------------------------------------");

    System.out.println("Value g1.operation(3): " + g1.operation(3));
    System.out.println("Value g2.operation(3): " + g2.operation(3));
    System.out.println("Value g3.operation(3): " + g3.operation(3));
    System.out.println("---------------------------------------");

    System.out.println("Value g1.operation(3): " + g1.operation(3));
    System.out.println("Value g2.operation(3): " + g2.operation(3));
    System.out.println("Value g3.operation(3): " + g3.operation(3)); 
 }
}

Què he fet?

– He transformat la closure que era  f en una classe interna f;

– L’expressió lambda associada a f en el mètode init de la classe interna;

– La variable z_f ha esdevingut una propietat d’f. D’aquesta forma  l’expressió lambda g ha pogut referir-se a la variable z_f que quedava definida implícitament com final.

– La invocació a f.init(var) es transforma en (new f()).init(var). La resta, tot igual.

 

El resultat és l’esperat:

run:
Value g1.operation(3): 5
Value g2.operation(3): 6
Value g3.operation(3): 7
---------------------------------------
Value g1.operation(3): 6
Value g2.operation(3): 7
Value g3.operation(3): 8
---------------------------------------
Value g1.operation(3): 7
Value g2.operation(3): 8
Value g3.operation(3): 9
BUILD SUCCESSFUL (total time: 0 seconds)

En el post d’avui he comentat algunes de les caraterístiques principals de Java 8 i he parat atenció a les expressions lambda. Amb les expressions lambda Java 8 fa un pas per apropar-se a les estructures de la programació funcional. A l’exemple he presentat una tècnica per simular closures amb classes internes i expressions lambda.

 

Podeu descarregar la classe del repositori GitHub. https://github.com/abaranguer/java8lambdaexp