L'habitude en Java est de demander l'avis des utilisateurs et de voter afin de proposer les prochaines évolutions du langage. Cela se fait par le biais des Java Specifications Request (JCP). Par exemple les petites évolutions du langage (JSR 334), débatues en 2009, sont implémentés dans Java 7.

Ces petites évolutions du langage ont été choisies dans le cadre du projet coin; chaînes de caractères dans le switch, litéraux entiers binaires, underscore dans les litéraux numériques, multi-catch, amélioration dans la création des types génériques via l'opérateur diamond, try with resources, simplification de l'appel de méthode avec les varargs.

The goal of Project Coin is to determine what set of small language changes should be added to JDK 7. That list is:
  • Strings in switch
  • Binary integral literals and underscores in numeric literals
  • Multi-catch and more precise rethrow
  • Improved type inference for generic instance creation (diamond)
  • try-with-resources statement
  • Simplified varargs method invocation

Extrait de openjdk, projet coin

À ce projet coin, viennent s'ajouter d'autres fonctionnalités; amélioration de la jvm et de son class loader, Unicode 6.0, nio2 (Tutorial Sun/Oracle), prise en charge du protocole SCTP, des algorithmes cryptographiques, Swing JLayer (Tutorial Sun/Oracle), concurrency and collections updates, ... (et plein de choses que je ne comprend pas).

Project coin

Binary Literals

Il est maintenant possible d'écrire des littéraux entiers en base 2 (binary literals). Avant seules les bases 10, 16 et 8 étaient autorisées.

Extrait de java.net

// An 8-bit 'byte' value:
byte aByte = (byte)0b00100001;

// A 16-bit 'short' value:
short aShort = (short)0b1010000101000101;

// Some 32-bit 'int' values:
int anInt1 = 0b10100001010001011010000101000101;
int anInt2 = 0b101;
int anInt3 = 0B101; // The B can be upper or lower case.

// A 64-bit 'long' value. Note the "L" suffix:
long aLong = 
  0b1010000101000101101000010100010110100001010001011010000101000101L;

Underscores in Numeric Literals

Il est possible d'ajouter des underscore (_) entre les chiifres (underscores literals) afin d'accroître la lisibilité (pour séparer les milliers par exemple).

Extrait de java.net

long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi = 	3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;

Strings in switch

Simplement permettre l'ajout de type String dans un switch (string in switch). La discussion, démarre .

On peut donc écrire;

String s = ... // initialize string
switch (s) {
  case "One":
    // manage case 1 
    break;
  case "Two"; case "Three":
    // manage 2 and 3
    break;
}

Comme ce document en parle on pourrait reprocher que cette évolutionest plus coûteuse mais l'utilisation de if-then-else imbriqués l'est sans doute tout autant.

Type Inference for Generic Instance Creation (diamond)

Lors de l'invocation d'un constructeur sur un type générique, il n'est plus utile de répéter les types entre les chevrons. Des chevrons vides suffisent (<>). On appelle cet opérateur, diamond.

You can replace the type arguments required to invoke the constructor of a generic class with an empty set of type parameters (<>) as long as the compiler can infer the type arguments from the context. This pair of angle brackets is informally called the diamond.

Extrait de java.net

// before
List<MyObject> myObjects = new ArrayList<MyObject>();
// after
List<MyObject> myObjects = new ArrayList<>();

//more "complex", before
Map<MyKey, MyValue> myKeyValues = new HasMap<MyKey, MyValue>();
//more "complex", after
Map<MyKey, MyValue> myKeyValues = new HasMap<>();

Simplified Varargs Method Invocation

Ce que je retiens de ce document (non reifiable varags) relativement complexe est (la note de fin) que dans le contexte des varargs c'est de la responsabilité du programmeur écrivant une méthode ayant des varargs en paramètres, d'éviter une pollution du tas (heap polution) alors qu'avant, c'était de la responsabilité du programmeur appelant la méthode.

Varargs

Pour rappel varargs (varargs) permet un appel de méthode avec un nombre variable d'arguments. À l'appel de la méthode, je passe un ou plusieurs arguments (voire un tableau) et la méthode les réceptionne dans un tableau.

public void myMethod(int... is) {
  // is is int[]
}

// call example
myMethod(1);
myMethod(1,2,3);
int[] is = {1,2,3,4};
myMethod(is);
Heap pollution

La plupart des types paramétrés (parameterized types, par exemple List<String>) sont des non-reifiable types, c'est-à-dire des types qui ne sont pas complètement disponible à l'exécution (runtime). Pour assurer une compatibilité avec des bibliothèques (libraries) java plus anciennes (avant la notion de generics) le compilateur supprime les informations relatives aux types paramétrés.

Une pollution du tas (heap pollution) survient si une variable d'un certain type paramétré référence une variable d'un autre type. L'exemple donné chez java.net me donne l'impression qu'une telle "pollution" survient dans des cas particuliers

List l = new ArrayList<Number>();
List<String> ls = l;       // unchecked warning
l.add(0, new Integer(42)); // another unchecked warning
String s = ls.get(0);      // ClassCastException is thrown

On n'en saura pas plus aujourd'hui ;-)

The try-with-resources Statement

L'instruction try avec ressources (try with resources) ou encore automatic resource managment est une instruction try permettant la déclaration de resources (une resource est un objet nécessitant un close en fin de programme/block). Cette instruction assure que les ressources déclarées seront fermées en fin d'instruction.

Seuls les objets implémentant l'interface java.lang.AutoCloseable ou java.io.Closeable peuvent être utilisés comme resource. Les classes java.io.InputStream, OutputStream, Reader, Writer, java.sql.Connection, Statement, et ResultSet ont été adaptées pour implémenter AutoCloseable.

Avant, pour lire une ligne dans un fichier, je pouvais écrire:

static String readFirstLineFromFileWithFinallyBlock(String path) 
    throws IOException {
  BufferedReader br = new BufferedReader(new FileReader(path));
  try {
    return br.readLine();
  } finally {
    if (br != null) br.close();
  }
}

... maintenant, je peux me contenter de (notez la présence des parenthèses après le try):

static String readFirstLineFromFile(String path) throws IOException {
  try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
  }
}

Extrait de java.net contenant d'autres exemples intéresssant (utilisant nio2).

Multi catch and rethrow

Java 7 permet de catcher plusieurs exceptions dans un même bloc (catch multiple). Ceci afin d'éviter de la redondance de code. Ce "catch multiples" se fait au moyen de l'opérateur |.

Ce code, extrait de java.net, est redondant:

catch (IOException ex) {
     logger.log(ex);
     throw ex;
catch (SQLException ex) {
     logger.log(ex);
     throw ex;
}

... avec le JDK7, on peut mainenant écrire:

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

Le compilateur permet également une analyse plus fine et précise lorsque l'on relance des exceptions. Lire, à ce sujet, l'exemple dans catch multiple and rethrow.

Java nio2

Java 6 avait vu apparaitre la classe Console, Java 7 parle de NIO.2 (tutorial oracle).

Introduction des classes (ou interfaces) Path, Paths, Files, FileSystems

Interface Path

L'interface Path permet de représenter un chemin dans le file system.

  • Là où l'on utilisait la classe File, on utilisera Path (pour mettre à jour du code existant, on peut utiliser la méthode statique File.toPath).
  • Un path n'est pas indépendant du file system (on ne compare pas un path MS Windows avec un path Linux (ou Solaris))
  • C'est la classe utilitaire Paths (notez le 's') qui permet d'instancier un path.

Par exemple (extraits de tutoriel oracle

Path p1 = Paths.get("/tmp/foo");
Path p2 = Paths.get(args[0]);
Path p3 = Paths.get(URI.create("file:///Users/joe/FileTest.java"));
// get method is shorthand for
Path p4 = FileSystems.getDefault().getPath("/users/sally");
Path p5 = Paths.get(System.getProperty("user.home"), "logs", "foo.log");

Un path offre tout une série de méthodes; path.toString()), path.getFileName()), getName(0)), getNameCount()), path.subpath(0,2)), path.getParent()), path.getRoot()), path.toURI(), path.toAbsolutePath(), ...

Classe utilitaire Files

Une classe utilitaire permettant de supprimer, copier, ... manipuler des fichiers et répertoires.

Au niveau des exceptions, il est conseillé d'utiliser le try with resources puisque la plupart des flux sont des ressources (pour rappel, un flux est une ressource si il implémente Closeable et il pourra être automatiquemant fermé).

De plus certaines IOException générées lors de la manipulation des flux héritent de FileSystemException (FileNotFoundException par exemple). Et cette exception propose des méthodes (getFile, getReason) sur le fichier source de l'exception. On pourra écrire:

try (...) {
    ...    
} catch (NoSuchFileException ex) {
    System.err.format("%s does not exist
", ex.getFile());
}

La classe Files permet, par exemple, de déplacer un fichier. De plus la méthode move de la classe Files peut recevoir des options sous forme de varargs. L'une de ces options permet de déplacer le fichier de manière atomique. Pratique dans une application multithreads ou si plusieurs applications sont susceptibles d'utiliser la même ressource.

On peut déplacer un fichier en écrivant (extrait de oracle):

import static java.nio.file.StandardCopyOption.*;

Path source = Paths.get("/tmp/foo");
Path target = Paths.get("/tmp/bar");
Files.move(source, target, REPLACE_EXISTING, ATOMIC_MOVE);

NIO.2 ajoute la notion de glob qui est simplement (du moins il me semble) la notion de wildcards du shell. Si je veux écrire un ls (lister le contenu d'un répertoire), je peux soit tout lister soit ajouter un glob, un wildcard pour ne sélectionner qu'une partie des fichiers/répertoires que je liste.

Par exemple (essayer avec et sans le paramètre *.java) :

import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.io.IOException;

public class Ls { 
  public static void main ( String[] args ) { 
    Path path = Paths.get(".");
    try (DirectoryStream<Path> stream 
        = Files.newDirectoryStream(path, "*.java")) {
      for (Path p : stream) {
        System.out.println("Path: " + p);
      }
    } catch (IOException ex) {
      System.err.println("Exception: " + ex.getMessage());
    }
  }
}

C'est également la classe Files qui permettra d'obtenir des métadonnées au sujet des fichiers ... toujours par l'intermédiaire d'un path. Par exemple; Files.size(path), Files.isHidden(path), ...

... ce qui n'est pas très efficace car il faudra accéder plusieurs fois au fichier. La classe Files propose deux méthodes permettant d'accéder en une fois à plusieurs métadonnées.

Dans l'exemple suivant, "*" veut dire toutes les métadonnées. (On peut se limiter à quelques unes comme présenté dans la javadoc)

import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.util.Map;
import java.io.IOException;

public class ReadAttributes {
  public static void main ( String[] args ) { 
    Path path = Paths.get(".");
    Map<String,Object> attributes;
      try {
        attributes = Files.readAttributes(path,"*");
        for (String key : attributes.keySet()) {
          System.out.println("Key: " + key);
          System.out.println("Value:" + attributes.get(key) + "
");
        }
      } catch (IOException ex) {
        System.err.println("Error: " + ex.getMessage());
      }
    }
}

NIO.2 propose différentes vues des attributs d'un fichier. En effet, en fonction du système de fichiers, différentes informations peuvent être obtenues; basiques, orientées DOS, POSIX, "ACL", ... À chaque vue correspond une classe et l'on peut passer une classe en paramètre de la méthode readAttributes au lieu d'une chaine de caractères.

Dans le cas des informations basiques sur un fichier, on peut écrire le code suivant (extrait de tutorial oracle):

Path file = ...;
BasicFileAttributes attr = 
  Files.readAttributes(file, BasicFileAttributes.class);

System.out.println("creationTime: " 
  + attr.creationTime());
System.out.println("lastAccessTime: " 
  + attr.lastAccessTime());
System.out.println("lastModifiedTime: " 
  + attr.lastModifiedTime());

System.out.println("isDirectory: " 
  + attr.isDirectory());
System.out.println("isOther: " 
  + attr.isOther());
System.out.println("isRegularFile: " 
  + attr.isRegularFile());
System.out.println("isSymbolicLink: " 
  + attr.isSymbolicLink());
System.out.println("size: " + attr.size());

D'autres changements interviennent également dans la mnière de lire/écrire dans un fichier. Si l'on veut maintenant utiliser un BufferedReader, on pourra l'instancier par le biais de la classe Files (toujours elle) et en utilisant le tru-with-resources cela peut donner (extrait de tutorial oracle):

Charset charset = Charset.forName("US-ASCII");
try (BufferedReader reader = 
  Files.newBufferedReader(file, charset)) {
    String line = null;
    while ((line = reader.readLine()) != null) {
	System.out.println(line);
    }
} catch (IOException x) {
	System.err.format("IOException: %s%n", x);
}

Une interface est proposée permettant de parcourir un sous-arbre du filesystem (tutorial oracle). Grace à la méthode Files.walkFileTree et à son argument de type FileVisitor il est facilement possible de parcourir un sous-arbre du filesystem et d'attribuer une action à éxécuter sur chaque fichier rencontré ...

Faire une rechercher dans un tree et surveiller un changement sur un fichier / répertoire (file change notification) sont deux tâches qui deviennent très simples avec NIO.2. Voir le tutorial d'Oracle; find et notification.

Pour terminer sur NIO.2, voici un tableau présentant l'évolution de IO vers NIO.2 (extrait de tutorial oracle où, là, les liens sont évidemment cliquables). On constate que la classe File est délaissée au profit de la classe Files ...

Mapping IO to NIO.2

Zip file system provider

Le zip file system provider permet de manipuler une archive zip ou jar comme un file system; je peux créer une instance de la classe FileSystem à partir d'un fichier jar ou zip par le biais de la classe FileSystems.

À partir de cette instance, je peux manipuler les fichiers que contient l'archive "montée".

D'autres petites choses

  • Ajout d'une méthode close à la classe URLClassLoader (javadoc)
  • Des mises à jours dans le package java.util.concurrent et dans le framework collections
  • Passage à Unicode 6.0 (mais je ne sais pas quelle version utilisait JDK6)
  • Mise à jour de la pile XML
  • Inclusion de la classe JXLayer à Java, qui devient javax.swing.JLayer
  • Une nouvelle classe Objects (noter le 's', parmi mille autres)
  • ... et sans doute d'autres petites choses ...

La javadoc

Vous l'aurez constatez si vous avez suivi les liens, la feuille de style de la javadoc a changé (et peut être d'autres choses aussi).

Conclusion

Pas mal de nouveautés !

Il va falloir revoir les entrées / sorties avec NIO.2 mais le code à écrire sera plus concit. Pour les améliorations du langage, on pourra s'y faire très vite parfois (string in switch, ""binary leterals, underscores in literals'') et avec un peu plus de temps pour le reste.

Pour tester, il suffit de télécharger un JDK (download), ouvrir gVim ... et ça roule ;-)