TechEmpower, une entreprise de développement d'applications personnalisées basée à El Segundo, Californie, a posté une entrée de blog intitulée "Tout sur Java 8". Le billet de blog est un résumé complet des changements impactant les développeurs qui viennent avec Java 8. Voici une rapide vue d'ensemble du billet. Veuillez vous rendre sur le blog de TechEmpower pour les détails complets.
Améliorations sur les Interfaces
Les interfaces peuvent désormais définir des méthodes statiques. Par exemple, java.util.Comparator a désormais une méthode statique naturalOrder
.
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
Les interfaces peuvent désormais fournir des méthodes par défaut. Cela permet aux développeurs d'ajouter de nouvelles méthodes sans casser le code existant implémentant l'interface. Par exemple, java.lang.Iterable
a maintenant une méthode forEach
par défaut.
public default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
Notez qu'une interface ne peut pas fournir d'implémentation par défaut pour toute les méthodes de la classe Object.
Interfaces Fonctionnelles
Une interface fonctionnelle est une interface qui défini exactement une méthode abstraite. L'annotation FunctionalInterface a été introduite pour indiquer qu'une interface est conçue comme interface fonctionnelle. Par exemple, java.lang.Runnable
est une interface fonctionnelle.
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Notez que le compilateur Java considérera toute interface répondant à la définition comme interface fonctionnelle, que l'annotation FunctionalInterface soit présente ou non.
Lambdas
La propriété importante des interfaces fonctionnelles est qu'elles peuvent être instanciées en utilisant des lambdas. Les expression lambda vous permettent de traiter une fonctionnalité comme argument de méthode, ou du code en tant que donnée. Voici quelques exemples de lambdas. Les entrées sont sur la gauche et le code est sur la droite. Les types d'entrée peuvent être inférés et sont optionnels.
(int x, int y) -> { return x + y; }
(x, y) -> x + y
x -> x * x
() -> x
x -> { System.out.println(x); }
Voici un exemple d'instanciation de l'interface fonctionnelle Runnable.
Runnable r = () -> { System.out.println("Running!"); }
Références de Méthodes
Les références de méthodes sont des expressions lambda compactes pour des méthodes ayant déjà un nom. Voici quelques exemples de références de méthodes, avec l'expression lambda équivalente à droite.
String::valueOf x -> String.valueOf(x)
Object::toString x -> x.toString()
x::toString () -> x.toString()
ArrayList::new () -> new ArrayList<>()
Lambdas capturantes vs non-capturantes
Les lambdas sont dites "capturantes" si elles accèdent à une variable ou un objet non-statique qui a été défini hors du corps de la lambda. Par exemple, cette lambda accède à la variable x :
int x = 5;
return y -> x + y;
Une expression lambda peut seulement accéder aux variables locales et aux paramètres du bloc conteneur qui sont final ou finales dans les faits (NdT: une variable sans le mot clé final, mais instanciée une seule fois).
java.util.function
Un grand nombre de nouvelles interfaces fonctionnelles ont été ajoutées au package java.util.function. Voici quelques exemples:
- Function - prend un T en entrée, retourne un R en sortie
- Predicate - prend un T en entrée, retourne un booléen en sortie
- Consumer - prend un T en entrée, ne retourne rien
- Supplier - ne prend rien en entrée, retourne un T en sortie
- BinaryOperator - prend deux T en entrée, retourne un unique T en sortie
java.util.stream
Le nouveau package java.util.stream fourni des classes pour supporter des opérations de style fonctionnel sur des flux de valeurs. Une manière commune d'obtenir un stream sera depuis une collection :
Stream<T> stream = collection.stream();
Voici un exemple provenant de la Javadocs du package.
int sumOfWeights = blocks.stream().filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
Ici nous utilisons une Collection de blocs comme source pour un stream, et nous réalisons une opération dite de filter-map-reduce (filtrer-transformer-réduire) pour obtenir la somme des poids des blocs rouges. Les Streams peuvent être infinis et avec état. Ils peuvent être séquentiels ou parallèles. Quand vous travaillez avec des streams, vous obtenez tout d'abord un stream depuis une source, réalisez une ou plusieurs opérations intermédiaires puis une unique opération finale de terminaison. Les opérations intermédiaires incluent filter
, map
, flatMap
, peel
, distinct
, sorted
, limit
, et substream
. Les opérations de terminaison incluent forEach
, toArray
, reduce
, collect
, min
, max
, count
, anyMatch
, allMatch
, noneMatch
, findFirst
, et findAny
. Une classe utilitaire très pratique est java.util.stream.Collectors
. Celle-ci implémente des opérations de réduction variées, telles que convertir des streams en collections, ou l'agrégation d'éléments.
Amélioration de l'inférence des types génériques
Ceci améliore la capacité du compilateur Java à inférer les types génériques et réduit le nombre d'arguments de typage dans les appels de méthodes génériques. En Java 7, le code ressemble à quelque-chose comme çà :
foo(Utility.<Type>bar());
Utility.<Type>foo().bar();
En Java 8, l'inférence améliorée dans les arguments et appels chaînés vous permet d'écrire quelque-chose comme çà:
foo(Utility.bar());
Utility.foo().bar();
java.time
La nouvelle API date/heure est contenue dans le package java.time. Toutes les classes sont immuables et thread-safe. Les types date et heure incluent Instant
, LocalDate
, LocalTime
, LocalDateTime
et ZonedDateTime
. Au delà des dates et des heures, il y a aussi les types Duration
(durée) et Period
(périodicité). D'autres types de valeurs incluent Month
, DayOfWeek
, Year
, Month
, YearMonth
, MonthDay
, OffsetTime
et OffsetDateTime
. La plupart de ces nouvelles classes de date/heure sont supportées par JDBC.
Ajouts à l'API Collections
La capacité des interfaces à avoir des méthodes par défaut a permis à Java 8 d'ajouter un grand nombre de nouvelles méthodes à l'API Collections. Les implémentations par défaut ont été fournies sur toutes les interfaces et des implémentations plus efficaces ont été ajoutées aux classes concrètes quand cela était possible. Voici une liste des nouvelles méthodes :
- Iterable.forEach(Consumer)
- Iterator.forEachRemaining(Consumer)
- Collection.removeIf(Predicate)
- Collection.spliterator()
- Collection.stream()
- Collection.parallelStream()
- List.sort(Comparator)
- List.replaceAll(UnaryOperator)
- Map.forEach(BiConsumer)
- Map.replaceAll(BiFunction)
- Map.putIfAbsent(K, V)
- Map.remove(Object, Object)
- Map.replace(K, V, V)
- Map.replace(K, V)
- Map.computeIfAbsent(K, Function)
- Map.computeIfPresent(K, BiFunction)
- Map.compute(K, BiFunction)
- Map.merge(K, V, BiFunction)
- Map.getOrDefault(Object, V)
Ajouts à l'API Concurrence
Il y a eu quelques ajouts à l'API de Concurrence, dont certains vont être brièvement discutés ici. ForkJoinPool.commonPool()
est la structure qui gère toutes les opérations parallélisées sur les streams. Le pool commun est utilisé par toute ForkJoinTask
qui n'est pas explicitement soumise à un pool spécifique. ConcurrentHashMap
a été complètement réécrite. Le StampedLock
est une nouvelle implémentation de lock qui peut être utilisée comme alternative à ReentrantReadWriteLock
. CompletableFuture
est une implémentation de l'interface Future qui fourni des méthodes pour réaliser et chaîner des tâches asynchrones.
Ajouts aux API IO/NIO
Il y a de nouvelles méthodes d'IO/NIO, qui sont utilisées pour obtenir des java.util.stream.Stream
à partir de fichiers et des input streams.
- BufferedReader.lines()
- Files.list(Path)
- Files.walk(Path, int, FileVisitOption...)
- Files.walk(Path, FileVisitOption...)
- Files.find(Path, int, BiPredicate, FileVisitOption...)
- Files.lines(Path, Charset)
- DirectoryStream.stream()
Il y a aussi la nouvelle UncheckedIOException
, une IOException
qui étend RuntimeException
. Il y a aussi CloseableStream
, un flux qui peut être, et devrait être fermé.
Changements sur la Réflexion et les annotations
Avec les annotations de type, les annotations peuvent être écrites à plus d'endroits, tels que les arguments de typage génériques comme List<@Nullable String>
. Cela améliore la détection d'erreurs par les outils d'analyse statique, ce qui renforcera et raffinera le système de type intégré à Java.
Nashorn JavaScript Engine
Nashorn est la nouvelle implémentation légère haute-performance de Javascript intégrée au JDK. Nashorn est le successeur de Rhino, qui améliore la performance et l'usage mémoire. Il supportera l'API javax.script, mais n'incluera pas le support du DOM/CSS ni d'API de plugin de navigateur.
Autres ajouts divers à java.lang, java.util et ailleurs
Il y a beaucoup d'autres ajouts à d'autres packages que nous n'avons pas encore mentionné. En voici quelque uns parmi les plus notables. ThreadLocal.withInitial(Supplier)
permet une déclaration d'une variable thread-local plus compacte. Les trop longtemps attendus StringJoiner
et String.join(...)
font maintenant partie de Java 8. Les Comparator fournissent de nouvelles méthodes pour réaliser des comparaisons chaînées ou basées sur les attributs. La taille de la map pour le pool de String est plus grande, autour de 25-50K. Référez-vous au billet de blog "Everything about Java 8" pour les détails complets. Le billet a été mis à jour pour la dernière fois le 29 Mai 2013.