Java 8 – JSR-335 – 3/3 – Impacts sur l’API Collection et utilisation de la JDK 8 Early Access
Note importante : La JSR n’étant pas encore à l’état « final », il est possible qu’il y ait des différences entre les fonctionnalités décrites dans cet article et les fonctionnalités proposées dans la version finale.
De plus, un grand nombre de packages, classes et méthodes mentionnés dans cet article ont été modifiés récemment et continueront à changer au cours des prochains mois.
Par conséquent, ce qui nous intéresse pour l’instant, est d’analyser les nouveautés et leurs apports. Les exemples suivants ne compileront probablement plus avec le prochain build de la JDK.
Pour cet article, la JDK 8 build b64 avec support des Lambdas a été utilisée. Le dernier build peut être téléchargé depuis java.net.
L’intégralité des sources est disponible via Github.
Impacts sur l’API Collection
Sorting
Actuellement (avec Java 7), pour trier une liste nous avons deux possibilités : soit implémenter l’interface Comparable et définir la méthode compareTo() (ce qui permet de trier une liste d’objets d’un certain type d’une, et une seule, manière), soit créer une classe anonyme et implémenter la méthode compare(). Nous nous intéresserons à la deuxième possibilité, dont l’écriture est grandement facilitée grâce aux expressions Lambda et par extension aux références de méthodes.
Avec les versions de Java antérieures à la JSR-335, nous pouvons implémenter la méthode compare() de l’interface Comparator, directement dans la méthode Collections.sort() :
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
// ...
}
});
Ou si nous souhaitons réutiliser ce comparateur, créer une constante de type Comparator :
public final static Comparator<Person> NATURAL_SORT = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
// ...
}
};
Avec la JSR-335, de nouvelles possibilités s’offrent à nous :
/**
* org.isk.collections.sorting.Person.java
*/
public class Person {
private final String firstName;
private final String lastName;
public Person() {
this.firstName = "";
this.lastName = "";
}
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public static int staticNaturalSort(final Person p1, final Person p2) {
final String lastName1 = p1.getLastName();
final String lastName2 = p2.getLastName();
if (lastName1.equals(lastName2)) {
return p1.getFirstName().compareTo(p2.getFirstName());
} else {
return lastName1.compareTo(lastName2);
}
}
public int sortByLastName(final Person p2) {
return this.getLastName().compareTo(p2.getLastName());
}
public int sortByFirstName(final Person p1, final Person p2) {
return p1.getFirstName().compareTo(p2.getFirstName());
}
}
/**
* org.isk.collections.PersonSorterTest.java
*/
public class PersonSorterTest {
private List<Person> persons = new ArrayList<>();
@Before
public void setUp() {
this.persons.add(new Person("Doug", "Lea"));
this.persons.add(new Person("Joshua", "Bloch"));
this.persons.add(new Person("Brian", "Goetz"));
this.persons.add(new Person("Max", "Bloch"));
}
@After
public void tearDown() {
this.persons.clear();
}
}
Expression Lambda
Commençons par utiliser une expression Lambda :
/**
* org.isk.collections.sorting.PersonSorter.java
*/
public static void naturalSortUsingLambda(final List<Person> list) {
Collections.sort(list, (p1, p2) -> {
final String lastName1 = p1.getLastName();
final String lastName2 = p2.getLastName();
if (lastName1.equals(lastName2)) {
return p1.getFirstName().compareTo(p2.getFirstName());
} else {
return lastName1.compareTo(lastName2);
}
});
}
/**
* org.isk.collections.PersonSorterTest.java
*/
@Test
public void naturalSortUsingLambda() {
PersonSorter.naturalSortUsingLambda(this.persons);
Assert.assertEquals(new Person("Joshua", "Bloch"), this.persons.get(0));
Assert.assertEquals(new Person("Max", "Bloch"), this.persons.get(1));
Assert.assertEquals(new Person("Brian", "Goetz"), this.persons.get(2));
Assert.assertEquals(new Person("Doug", "Lea"), this.persons.get(3));
}
Méthode statique
Nous pouvons aussi utiliser une référence de méthode statique :
/**
* org.isk.collections.sorting.PersonSorter.java
*/
public static void naturalSortUsingStatic(final List<Person> list) {
Collections.sort(list, Person::staticNaturalSort);
}
/**
* org.isk.collections.PersonSorterTest.java
*/
@Test
public void naturalSortUsingStatic() {
PersonSorter.naturalSortUsingStatic(this.persons);
Assert.assertEquals(new Person("Joshua", "Bloch"), this.persons.get(0));
Assert.assertEquals(new Person("Max", "Bloch"), this.persons.get(1));
Assert.assertEquals(new Person("Brian", "Goetz"), this.persons.get(2));
Assert.assertEquals(new Person("Doug", "Lea"), this.persons.get(3));
}
Méthode d’instance d’un objet en particulier
Ou encore utiliser une méthode d’instance d’un objet en particulier :
/**
* org.isk.collections.sorting.PersonSorter.java
*/
public static void sortByFirstNameUsingInstance(final List<Person> list) {
Collections.sort(list, new Person()::sortByFirstName);
}
/**
* org.isk.collections.PersonSorterTest.java
*/
@Test
public void sortByFirstNameUsingInstance() {
PersonSorter.sortByFirstNameUsingInstance(this.persons);
Assert.assertEquals(new Person("Brian", "Goetz"), this.persons.get(0));
Assert.assertEquals(new Person("Doug", "Lea"), this.persons.get(1));
Assert.assertEquals(new Person("Joshua", "Bloch"), this.persons.get(2));
Assert.assertEquals(new Person("Max", "Bloch"), this.persons.get(3));
}
Méthode d’instance d’aucun objet en particulier
Et pour finir, voici l’utilisation d’une méthode d’instance d’aucun objet en particulier :
/**
* org.isk.collections.sorting.PersonSorter.java
*/
public static void sortByLastNameUsingNoneStatic(final List<Person> list) {
Collections.sort(list, Person::sortByLastName);
}
/**
* org.isk.collections.PersonSorterTest.java
*/
@Test
public void sortByLastNameUsingNoneStatic() {
PersonSorter.sortByLastNameUsingNoneStatic(this.persons);
Assert.assertEquals("Bloch", this.persons.get(0).getLastName());
Assert.assertEquals("Bloch", this.persons.get(1).getLastName());
Assert.assertEquals(new Person("Brian", "Goetz"), this.persons.get(2));
Assert.assertEquals(new Person("Doug", "Lea"), this.persons.get(3));
}
Utilisation de la méthode comparing()
La solution suivante nous simplifie la vie :
/**
* org.isk.collections.sorting.PersonSorter.java
*/
public static void sortByFirstNameUsingComparingAndLambda(final List<Person> list) {
Collections.sort(list, PersonSorter.comparing((Person p) -> p.getFirstName()));
}
public static <T, R extends Comparable<? super R>> Comparator<T> comparing(Mapper< R, T> mapper) {
return (x, y) -> mapper.map(x).compareTo(mapper.map(y));
}
/ **
* org.isk.collections.PersonSorterTest.java
*/
@Test
public void sortByFirstNameUsingComparingAndLambda() {
PersonSorter. sortByFirstNameUsingComparingAndLambda(this.persons);
Assert.assertEquals(new Person("Brian", "Goetz"), this.persons.get(0));
Assert.assertEquals(new Person("Doug", "Lea"), this.persons.get(1));
Assert.assertEquals(new Person("Joshua", "Bloch"), this.persons.get(2));
Assert.assertEquals(new Person("Max", "Bloch"), this.persons.get(3));
}
Cet exemple semble un peu plus complexe, mais ce n’est pas le cas. A terme, la méthode comparing() devrait faire partie de la classe Collections si l’on en croit l’article « State of the Lambda » [1]. Néanmoins pour l’instant, elle n’existe pas, tout comme son implémentation (celle présentée ci-dessus me semble la plus logique).
Un Mapper est comme l’interface GenericLambda de l’article 1. Il s’agit d’une interface fonctionnelle, ayant une méthode abstraite map(), prenant en paramètre un objet et en retournant un autre. Comme par exemple, sans expression Lambda :
new Mapper<String, Person>() {
@Override
public String map(Person person) {
return person.getFirstName();
}
}
La méthode comparing(), quant à elle, prend un Mapper dont le type de l’objet retourné implémente Comparable, nécessaire pour l’utilisation de compareTo(). Pour la simplifier, il suffit de remplacer les T par Person et les R par String.
public static Comparator<Person> comparing(Mapper<String, Person> mapper) {
return new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return mapper.map(o1).compareTo(mapper.map(o2));
}
};
}
Si nous n’avions pas à écrire la méthode comparing(), il nous aurait suffit de fournir le champ sur lequel doit être fait le tri, avec cette fois le type du paramètre de l’expression Lambda.
Forme simplifiée
Il est possible de remplacer l’expression Lambda de l’exemple précédent par une référence de méthode :
Collections.sort(list, PersonSorter.comparing((Mapper<String, Person>)Person::getFirstName));
Malheureusement, en raison de la déduction du type (Type inference) propre au compilateur, il est nécessaire de préciser les deux types attendus par le Mapper :
(Mapper<String, Person>)Person::getFirstName
Inutilité de la classe Collections
Il est aussi possible de se passer de la classe Collections pour réaliser un tri :
/**
* org.isk.collections.sorting.PersonSorter.java
*/
public static void sortByFirstNameUsingListSort(final List<Person> list) {
list.sort(PersonSorter.comparing((Mapper<String, Person>)Person::getFirstName));
}
/**
* org.isk.collections.PersonSorterTest.java
*/
@Test
public void sortByFirstNameUsingListSort() {
PersonSorter.sortByFirstNameUsingListSort(this.persons);
Assert.assertEquals(new Person("Brian", "Goetz"), this.persons.get(0));
Assert.assertEquals(new Person("Doug", "Lea"), this.persons.get(1));
Assert.assertEquals(new Person("Joshua", "Bloch"), this.persons.get(2));
Assert.assertEquals(new Person("Max", "Bloch"), this.persons.get(3));
}
D’après mon expérience, je pense que l’on peut approfondir pour avoir quelque chose de plus propre :
/**
* org.isk.collections.sorting.PersonSorter.java
*/
public final static Mapper<String, Person> MAPPER_BY_FIRSTNAME;
static {
MAPPER_BY_FIRSTNAME = Person::getFirstName;
}
/**
* org.isk.collections.PersonSorterTest.java
*/
@Test
public void sortByFirstNameUsingListSortWithConstant() {
this.persons.sort(PersonSorter.comparing(PersonSorter.MAPPER_BY_FIRSTNAME));
Assert.assertEquals(new Person("Brian", "Goetz"), this.persons.get(0));
Assert.assertEquals(new Person("Doug", "Lea"), this.persons.get(1));
Assert.assertEquals(new Person("Joshua", "Bloch"), this.persons.get(2));
Assert.assertEquals(new Person("Max", "Bloch"), this.persons.get(3));
}
On définit un Mapper statique, qui peut ensuite être utilisé lorsque l’on en a besoin en combinaison avec comparing(). De plus, ceci facilite le tri inversé, comme nous allons le voir dans l’exemple du paragraphe suivant.
Tri inversé
Avec Java 7 et les versions antérieures, effectuer un tri inversé est un véritable calvaire. Avec Java 8, notre vie est simplifiée :
/**
* org.isk.collections.PersonSorterTest.java
*/
@Test
public void sortByFirstNameUsingReverseSortWithConstant() {
this.persons.sort(PersonSorter.comparing(PersonSorter.MAPPER_BY_FIRSTNAME).reverse());
Assert.assertEquals(new Person("Brian", "Goetz"), this.persons.get(3));
Assert.assertEquals(new Person("Doug", "Lea"), this.persons.get(2));
Assert.assertEquals(new Person("Joshua", "Bloch"), this.persons.get(1));
Assert.assertEquals(new Person("Max", "Bloch"), this.persons.get(0));
}
Il suffit de réutiliser notre Mapper défini précédemment et d’appeler la méthode reverse() sur le comparing().
Looping
Dans la partie suivante, nous utiliserons des formes (Shape) :
/**
* org.isk.collections.stream.Shape.java
*/
public class Shape {
private Color color;
private ShapeNameEnum shapeName;
private int weight;
public Shape(Color color, ShapeNameEnum shapeName, int weight) {
this.color = color;
this.shapeName = shapeName;
this.weight = weight;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public ShapeNameEnum getShapeName() {
return shapeName;
}
public void setShapeName(ShapeNameEnum shapeName) {
this.shapeName = shapeName;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public enum ShapeNameEnum {
CIRCLE, SQUARE, RECTANGLE, TRIANGLE;
}
}
/**
* org.isk.collections.stream.LoopingTest.java
*/
public class LoopingTest {
private List<Shape> shapes = new ArrayList<>();
@Before
public void setUp() {
this.shapes.add(new Shape(Color.BLACK, Shape.ShapeNameEnum.CIRCLE, 1));
this.shapes.add(new Shape(Color.RED, Shape.ShapeNameEnum.TRIANGLE, 2));
this.shapes.add(new Shape(Color.BLUE, Shape.ShapeNameEnum.SQUARE, 3));
this.shapes.add(new Shape(Color.YELLOW, Shape.ShapeNameEnum.TRIANGLE, 4));
}
@After
public void tearDown() {
this.shapes.clear();
}
}
Pour parcourir une liste nous pouvons utiliser un « foreach » :
for (Shape s : shapes) {
s.setColor(RED);
}
Malheureusement, cette forme d’itération externalisée (non contrôlée par l’API) a deux problèmes :
- Elle est intrinsèquement sérielle et impose de traiter les éléments dans l’ordre spécifié par la collection.
- Elle prive l’API de la possibilité de gérer la séquence des instructions à l’intérieur de la boucle, ce qui permettrait la réorganisation des données, le parallélisme, le court-circuit, ou une forme de « lazyness » pour améliorer les performances.
L’alternative est une itération internalisée :
/**
* org.isk.collections.StreamTest.java
*/
@Test
public void looping() {
this.shapes.forEach(e -> e.setColor(Color.GREEN));
this.shapes.forEach(e -> Assert.assertEquals(Color.GREEN, e.getColor()));
}
Cette approche permet d’augmenter le degré d’abstraction en déplaçant la gestion de l’exécution des instructions du code client (utilisateur) vers des libraries, pour réorganiser les données, gérer le parallélisme, les court-cicuits ou le “lazyness”.
Que la méthode forEach(), ou toute autre méthode fonctionnant sur le même principe, profite de telles fonctionnalités ou non, dépendra de son implémentation dans les librairies en question.
Bien évidemment, dans certains cas les boucles externalisées sont nécessaires. L’objectif n’étant pas de les supprimer complètement.
A noter que la notion de parallélisme ne sera pas introduite de façon transparente dans l’évolution de l’API Collection. Il sera à la charge de l’utilisateur d’appeler les méthodes adaptées. Comme par exemple :
this.shapes.parallel() //...
Filtering
Semblable à une requête SQL (proche des criteria d’Hibernate), il est possible de sélectionner les éléments d’une liste répondant à certains critères.
Par exemple, si nous souhaitons que les formes de couleur bleue, et uniquement celles-ci, deviennent vertes en utilisant un simple for, nous le ferions de la manière suivante :
for (Shape shape : this.shapes) {
if (shape.getColor() == Color.BLUE) {
shape.setColor(Color.GREEN);
}
}
Et avec la méthode forEach() :
/**
* org.isk.collections.StreamTest.java
*/
@Test
public void setColorsBlueToGreen() {
this.shapes.stream()
.filter(e -> e.getColor() == Color.BLUE)
.forEach(e -> e.setColor(Color.GREEN));
Assert.assertEquals(Color.BLACK, this.shapes.get(0).getColor());
Assert.assertEquals(Color.RED, this.shapes.get(1).getColor());
Assert.assertEquals(Color.GREEN, this.shapes.get(2).getColor());
Assert.assertEquals(Color.YELLOW, this.shapes.get(3).getColor());
}
La méthode filter(), du nouvel objet Stream, attend un objet de type Predicate. L’interface fonctionnelle Predicate a une méthode abstraite test(), qui prend en paramètre un objet et retourne un booléen. Dans le cas présent, sans expression Lambda nous aurions :
Predicate predicate = new Predicate() {
@Override
public boolean test(Object o) {
return ((Shape)o).getColor() == Color.BLUE;
}
};
Nous avons aussi d’autres méthodes à notre disposition pouvant être utilisées en association avec filter(), comme nous le verrons par la suite.
Multi-Filtering
Si nous souhaitons filtrer sur plusieurs champs, il est possible de chaîner les filter() :
/**
* org.isk.collections.StreamTest.java
*/
@Test
public void multiFiltering() {
this.shapes.stream()
.filter(e -> e.getShapeName() == Shape.ShapeNameEnum.TRIANGLE)
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> e.setColor(Color.GREEN));
Assert.assertEquals(Color.BLACK, this.shapes.get(0).getColor());
Assert.assertEquals(Color.GREEN, this.shapes.get(1).getColor());
Assert.assertEquals(Color.BLUE, this.shapes.get(2).getColor());
Assert.assertEquals(Color.YELLOW, this.shapes.get(3).getColor());
}
Tous les triangles rouges deviendront des triangles verts.
Sous-Collection
Nous pouvons aussi créer une sous-collection très facilement. Par exemple, si nous souhaitons avoir une liste de triangles :
/**
* org.isk.collections.StreamTest.java
*/
@Test
public void sublist() {
final List<Shape> triangles =
this.shapes.stream()
.filter(e -> e.getShapeName() == Shape.ShapeNameEnum.TRIANGLE)
.into(new ArrayList<Shape>());
Assert.assertEquals(2, triangles.size());
triangles.forEach(e ->
Assert.assertEquals(Shape.ShapeNameEnum.TRIANGLE, e.getShapeName()));
}
Mapping
En utilisant un Mapper, il est possible d’avoir une liste triée des noms des formes :
/**
* org.isk.collections.StreamTest.java
*/
@Test
public void mappingSorted() {
final Set<Shape.ShapeNameEnum> shapeNames =
this.shapes.stream()
.map(e -> e.getShapeName())
.sorted(Shape.ShapeNameEnum::compareTo)
.into(new TreeSet<Shape.ShapeNameEnum>());
Assert.assertEquals(3, shapeNames.size());
final Iterator<Shape.ShapeNameEnum> iterator = shapeNames.iterator();
Assert.assertEquals(Shape.ShapeNameEnum.CIRCLE, iterator.next());
Assert.assertEquals(Shape.ShapeNameEnum.SQUARE, iterator.next());
Assert.assertEquals(Shape.ShapeNameEnum.TRIANGLE, iterator.next());
}
Accumulation
La méthode reduce() prend une valeur de base (au cas où la liste est vide ce sera la valeur retournée) et un opérateur (ici, une addition), et calcule l’expression suivante :
0 + list[0] + list[1] + list[2] + ...
Avec un exemple (calcul de la somme du poids des formes) :
/**
* org.isk.collections.StreamTest.java
*/
@Test
public void accumulator() {
final int sum = this.shapes.stream()
.map(e -> e.getWeight())
.reduce(0, (x, y) -> x + y);
Assert.assertEquals(10, sum);
}
Conclusion
Au cours de ces trois articles, nous avons vu que la JSR-335 introduit une nouvelle façon de penser, mais aussi permettra de faire évoluer de manière significative les APIs standards, et en particulier l’API Collection.
Bien évidemment, tout n’est pas parfait, et avec ou sans, il faut rester rigoureux lors de la phase de conception et de développement.
Utilisation de la JDK 8 Early Access
Pour éviter de modifier votre version courante de la JDK, je vous conseille de créer une variable d’environnement temporaire, limitée au shell. Mais aussi d’utiliser Maven pour exécuter une suite de tests unitaires, ou exécuter une application en mode fork (grâce au plugin exec) pour utiliser le launcher de la JDK.
- Télécharger le binaire correspondant à votre plateforme (https://jdk8.java.net/lambda/). Attention seule cette JDK inclue les expressions Lambda.
- Extraire le contenu de l’archive.
- Modifier la variable d’environnement JAVA_HOME (cf. Appendice A)
- Créer un projet Maven (cf. Appendices B et C)
Pour ceux qui souhaitent utiliser un IDE, je vous invite à consulter l’appendice D.
Appendices
Appendice A – Créer une variable d’environnement temporaire
Unix
Pour sh , ksh , bash (où <path> est le chemin d’accès à la JDK 8) :
$ export VARNAME="<path>/jdk1.8.0"
Pour csh et tcsh (où <path> est le chemin d’accès à la JDK 8) :
$ setenv VARNAME "<path>/jdk1.8.0"
Windows
C:\>set JAVA_HOME=<path>\jdk1.8.0
Où <path> est le chemin d’accès à la JDK 8.
Vérification (Windows XP 64-bit)
C:\>mvn -v
Apache Maven 3.0.4 (r1232337; 2012-01-17 09:44:56+0100)
Maven home: F:\softs\apache-maven-3.0.4
Java version: 1.8.0-ea, vendor: Oracle Corporation
Java home: K:\lambda-8-b64-windows-x64-05_nov_2012\jdk1.8.0\jre
Default locale: en_US, platform encoding: Cp1252
OS name: "windows xp", version: "5.2", arch: "amd64", family: "windows"
Appendice B – Exemple de pom Maven
Vous trouverez ci-dessous un exemple de pom.xml configuré pour Java 8.
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.soat</groupId>
<artifactId>lambda-expressions</artifactId>
<version>0.1</version>
<packaging>jar</packaging>
<name>lambda-expressions</name>
<url>https://blog.soat.fr</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jdk.version>1.8</jdk.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- https://maven.apache.org/plugins/maven-compiler-plugin/ -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
<plugin>
<!-- https://maven.apache.org/plugins/maven-jar-plugin/ -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<mainClass>org.isk.LambdaExpressions</mainClass>
<packageName>org.isk</packageName>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<!-- https://mojo.codehaus.org/exec-maven-plugin/ -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
<configuration>
<executable>java</executable>
<arguments>
<argument>-jar</argument>
<argument>target/${project.name}-${project.version}.jar</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</project>
Appendice C – Commandes maven
Suppression du répertoire target, compilation et exécution des tests unitaires (l’option -q n’affiche pas les messages maven) :
$ mvn clean install test
Exécution d’un jar (la classe portant la méthode main doit être indiquée dans le pom – maven-jar-plugin) :
$ mvn exec:exec
Appendice E – JDK 8 EA et support des IDEs
Actuellement Eclipse ne supporte pas la syntaxe introduite par la JSR-335.
Netbeans 8 Nightly Builds [2] et IDEA 12 EAP [3], quant à eux, supportent cette nouvelle syntaxe. Néanmoins, il est nécessaire de garder à l’esprit qu’il s’agit de versions expérimentales.
Netbeans a tendance à crasher assez souvent lorsqu’il y a des références de méthodes dans la classe en cours d’édition, ce qui rend son utilisation laborieuse. Je n’ai donc pas poussé très loin l’analyse.
Quant à IDEA, il a des difficultés avec les références de méthodes et de constructeur lorsqu’il doit distinguer les bonnes et mauvaises formes. On obtient donc souvent des erreurs qui n’en sont pas. De plus, l’autocomplétion pose quelques problèmes avec les paramètres des expressions Lambda, puisqu’il veut absolument que l’on utilise une variable ou une méthode qu’il voit !
Resources
[1] https://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html
[2] https://bertram2.netbeans.org:8080/job/jdk8lambda/lastSuccessfulBuild/artifact/nbbuild/
[3] https://confluence.jetbrains.net/display/IDEADEV/IDEA+12+EAP
[-] https://cr.openjdk.java.net/~briangoetz/lambda/sotc3.html