Java 7 is out, quel est son lot de nouveautés ?
C’est Korben qui me passe l’info, provenant du blog de Mark Reinhold, via son Twitter; Java 7 release candidate est sortie.
After an initial round of testing we’ve declared build 147 to be the first Release Candidate of JDK 7.
Extrait de Mark Reinhold’blog
Avant toutes choses, de manière générale en Java, l’info ne se trouve plus chez Sun, puisque c’est Oracle qui a repris « l’affaire »… et (je trouve que) le site d’Oracle est beaucoup plus confus, l’info plus difficile à trouver. Ce sont donc java.net et openjdk.java.net (dépendant d’Oracle, que l’on se rassure) qui fournissent l’info Java.
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ébattues 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, littéraux entiers binaires, underscore _
dans les litté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 (nombre d’arguments variables).
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).
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 chiffres (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 là.
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 évolution est 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 le nombre d’arguments variables (varargs) permet un appel de méthode avec, comme son nom l’indique, 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);
Remarque : il s’agit bien de trois fois le caractère point .
et non du caractère points de suspensions …
.
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 génériques 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 très 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 ressources (une ressource est un objet nécessitant un close en fin de programme/bloc).
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 ressource. 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 readFirstLineFromFileWithFinallyBloc(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éressants (utilisant nio2).
Multi catch and rethrow
Java 7 permet de « catcher » (attraper) 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 maintenant é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 utiliseraPath
(pour mettre à jour du code existant, on peut utiliser la méthode statiqueFile.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 utilitairePaths
(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()
, etc.
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 ou AutoCloseable et il pourra être automatiquement 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 par exemple, ou si plusieurs applications sont susceptibles d’utiliser la même ressource.
On peut déplacer un fichier en écrivant (extrait de oracle):
Path source = Paths.get("/tmp/foo");
Path target = Paths.get("/tmp/bar");
Files.move(source, target,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.ATOMIC_MOVE);
NIO.2 ajoute la notion de glob qui est simplement 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 maniè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 bientôt vous ne pourrez plus vous en passer) et en utilisant le try-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 à exécuter sur chaque fichier rencontré.
Faire une rechercher dans un arbre 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](http://download.oracle.com/javase/tutorial/essential/io/notification.html.
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
.
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 constaté 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 concis. Pour les améliorations du langage, on pourra s’y faire très vite parfois (string in switch, binary literals, underscores in literals) et avec un peu plus de temps pour le reste.
Pour tester, il suffit de télécharger un JDK, ouvrir gVim… et ça roule 😉