We are in 2015 and Java 8 will slowly but surely become the new standard. Our world will be filled with lambdas. Generics, for themselves, have appeared in 2004. They brought benefits and complexity. Enough to blow up the complexity quota of Java according to Josh Bloch.
But lambdas, whatever are sexy they are, are going to add even more complexity. Mix with generics your now playing with nitroglycerin.
We will travel together through the bottom of the generics, through inference and through lambdas type resolution. To explain why. To provide solutions. To ease Java 8 adoption.
Boost Fertility New Invention Ups Success Rates.pdf
Generics and Lambdas cocktail explained - Montreal JUG
1. 27 au 29 mars 2013
Les lambda sont là!
Mais êtes-vous sûr d'avoir
compris les génériques?
Henri Tremblay
Architecte Senior
Pages Jaunes
@henri_tremblay
16. Dreaded warnings
16
Type safety: The expression of type List needs
unchecked conversion to conform to List<String>
Type safety: Unchecked cast from
List<capture#1-of ?> to List<String>
21. Arrays
Arrays are covariant:
Number n = Integer.MAX_VALUE;
Number[] list = new Integer[0];
Generics are not:
List<Number> l =
new ArrayList<Integer>(); // Illegal
21
22. Why not?
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));
int i = li.get(0); // ClassCastException
22
Would work if covariant And allow to break rule #1
26. Because
If it was allowed
List<String>[] lsa = new List<String>[10]; // illegal
Object[] oa = lsa; // OK (covariant)
oa[0] = new ArrayList<Integer>(); // OK
oa[0].add(42);
String s = lsa[0].get(0); // bad
26
28. Capture usually is
Type
List<?> bar();
<T> IExpectationSetters<T> expect(T value);
void andReturn(T value); // Method of IExpectationSetters
expect(bar()).andReturn(new ArrayList<String>());
And you get
The method andReturn(List<capture#6-of ?>) in the type
IExpectationSetters<List<capture#6-of ?>> is not applicable for the arguments
(ArrayList<String>)
28
30. Only solution
We need to cast
expect((List<String>) bar()).andReturn(new
ArrayList<String>());
But still a warning
Type safety: Unchecked cast from List<capture#6-of ?> to
List<String>
Framework coder tip:
Try to never return a wildcard unless necessary
30
Tell to expect we want a
List<String>
39. Trick #2
So the only solution is
foo(EasyMock.<List<String>> anyObject());
… which sadly doesn’t support static imports
foo(.<List<String>> anyObject()); // Illegal
39
42. How did it do it?
Tweet.TWEETS.stream()
List<Tweet> list = Tweet.TWEETS;
Stream<Tweet> stream = list.stream();
43. How did it do it?
Stream<Tweet> stream = list.stream();
R result = stream.collect(Collector<? super T, ?, R> collector);
R result = stream.collect(Collector<? super Tweet, ?, R> collector);
44. How did it do it?
stream.collect(Collector<? super Tweet, ?, R> collector);
Collector<T, ?, Map<Boolean, List<T>>> collector =
Collectors.partitioningBy(Predicate<? super T> predicate);
Collector<Tweet, ?, Map<Boolean, List<Tweet>>> collector =
Collectors.partitioningBy(Predicate<? super Tweet> predicate);
45. How did it do it?
Predicate<? super Tweet> lambda = t -> t.containsHashTag("#lambda");
We now know that
So the best t can be is a Tweet
Predicate<? super Tweet> lambda = (Tweet t) -> t.containsHashTag("#lambda");
46. Trick #3: Lambda inference
Object o = (Runnable) () -> {
System.out.println("hi");
};
Collections.sort(strings,
(String a, String b) -> a.compareTo(b));
48. Erasure…
public void foo() {
List<String> l = new ArrayList<String>();
for (String s : l) {
System.out.println(s);
}
}
No type
public void foo() {
List l = new ArrayList();
for (String s : l) {
System.out.println(s);
}
}
Compilation
49. … or not erasure
public class A extends ArrayList<String> {}
public static void main(final String[] args) {
ParameterizedType type = (ParameterizedType)
A.class.getGenericSuperclass();
System.out.println(
type.getActualTypeArguments()[0]);
}
prints class java.lang.String
49
50. Type class
java.lang.reflect.Type
• GenericArrayType
• ParameterizedType
• TypeVariable
• WildcardType
• Implemented by Class
java.lang.reflect.GenericDeclaration
Implemented by Class, Method, Constructor
50
New powers unleashed!
52. Useful!
@SuppressWarnings("unchecked")
public T load(final long id) {
ParameterizedType type =
(ParameterizedType) getClass()
.getGenericSuperclass();
Type actualType = type.getActualTypeArguments()[0];
return em.find((Class<T>) actualType, (Long) id);
}
ADao
A
BaseDao<A>
Unsafe cast
54. Everything seems normal…
class A<T> {
abstract void set(T value);
}
class B extends A<String> {
String value;
@Override
void set(final String value) {
this.value = value;
}
}
55. But is not
class B extends A {
void set(String value) {
this.value = value;
}
volatile void set(Object o){
set((String)o);
}
}
56. Example
A a = new B();
a.set(new Object());
But at runtime:
java.lang.ClassCastException
Raw type warning Perfectly compiling
57. The actual problem being
B.class.getDeclaredMethods()
volatile void set(java.lang.Object)
void B.set(java.lang.String)
This
Returns that
And gives you no way to find out
which method is bridged
58. What about lambdas?
public class A {
public static void main(String[] args) {
Method[] methods = A.class.getDeclaredMethods();
Arrays.stream(methods).forEach(m ->
System.out.println(m + " "
+ m.isBridge() + " " + m.isSynthetic()));
}
}
Prints this
public static void A.main(java.lang.String[]) false false
private static void A.lambda$0(java.lang.reflect.Method) false true
60. Useful links
Nice lambda tutorial (in French):
http://lambda.ninjackaton.ninja-squad.com/
Description of inference types on lambda:
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
Everything on generics:
http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html
Hopefully everything on lambdas
http://www.lambdafaq.org/
Hors le fait que ça nous donne des magnifiques oneliners, on y croise une quantité particulièrement épique de génériques
Malheureusement, quand on le sépare en petits morceaux, nous voyons apparaître des tonnes de génériques.
La bonne nouvelle, c’est que ça s’est amélioré. La dernière fois que j’ai donné cette conférence, ça ressemblait plutôt à ça
L’API était nettement plus complexe et il restait certains problèmes d’inférence
L’inférence, c’est le fait de deviner le type. Comme dans le Collectors ici. On en avait un peu depuis l’arrivé des génériques et il y en a maintenant beaucoup plus avec les lambda
Et vous terminez, pas trop fier de vous et pas trop sûr d’avoir compris le problème avec un aborable SuppressWarning.
Cette session a pour but de vous faire comprendre
1- Pourquoi vous avez ces erreurs
2- Pourquoi c’est fait comme ça
3- Comment c’est implémenté
Et vous terminez, pas trop fier de vous et pas trop sûr d’avoir compris le problème avec un aborable SuppressWarning.
Cette session a pour but de vous faire comprendre
1- Pourquoi vous avez ces erreurs
2- Pourquoi c’est fait comme ça
3- Comment c’est implémenté
Et vous terminez, pas trop fier de vous et pas trop sûr d’avoir compris le problème avec un aborable SuppressWarning.
Cette session a pour but de vous faire comprendre
1- Pourquoi vous avez ces erreurs
2- Pourquoi c’est fait comme ça
3- Comment c’est implémenté
Et vous terminez, pas trop fier de vous et pas trop sûr d’avoir compris le problème avec un aborable SuppressWarning.
Cette session a pour but de vous faire comprendre
1- Pourquoi vous avez ces erreurs
2- Pourquoi c’est fait comme ça
3- Comment c’est implémenté
Et vous terminez, pas trop fier de vous et pas trop sûr d’avoir compris le problème avec un aborable SuppressWarning.
Cette session a pour but de vous faire comprendre
1- Pourquoi vous avez ces erreurs
2- Pourquoi c’est fait comme ça
3- Comment c’est implémenté
Et vous terminez, pas trop fier de vous et pas trop sûr d’avoir compris le problème avec un aborable SuppressWarning.
Cette session a pour but de vous faire comprendre
1- Pourquoi vous avez ces erreurs
2- Pourquoi c’est fait comme ça
3- Comment c’est implémenté
Emplit de désespoir vous ne savez pas trop comment vous en débarasser parce que toutes vos tentatives ne compilent pas.
Emplit de désespoir vous ne savez pas trop comment vous en débarasser parce que toutes vos tentatives ne compilent pas.
Et vous terminez, pas trop fier de vous et pas trop sûr d’avoir compris le problème avec un aborable SuppressWarning.
Cette session a pour but de vous faire comprendre
1- Pourquoi vous avez ces erreurs
2- Pourquoi c’est fait comme ça
3- Comment c’est implémenté
Par exemple, les arrays
C’est cette règle qui a forcé plusieurs décisions de design et qui a par conséquent rendu notre ami le @SuppressWarning si fréquent.
On en vient à ce demander comment ils auraient fait si les annotations n’étaient pas apparu au même moment.
Par exemple, les arrays
Les arrays sont covariants. Si on peut assigner un object d’un type donné à un autre type, alors on peut assigner un array d’objet à un array de l’autre type
Par contre pour les génériques on ne peut pas. Il est illégal de faire la ligne du bas.
Car, un code qui compile sans warning ne doit jamais jamais causé un ClassCastException
Un exemple que vous avez sûrement déjà vu pour expliquer pourquoi c’est interdit.
Comme vous voyez, si la covariance était permisse nous briserions la règle #1
Et pour un array
Parce que pour un tableau, on a envie que ça ça passe
On ne peut pas faire de tableau de génériques et c’est bien dommage.
Une seule exception, un tableau de wildcard. Le wildcard étant un type quelconque, nous n’avons pas de soucis.
Dans tous les autres cas c’est interdit pour la raison suivante
On ne peut pas faire de tableau de génériques et c’est bien dommage.
Une seule exception, un tableau de wildcard. Le wildcard étant un type quelconque, nous n’avons pas de soucis.
Dans tous les autres cas c’est interdit pour la raison suivante
Nous avons le cas suivant.
Grâce une asticieuse utilisation de la covariance des arrays, si les arrays génériques étaient permis, nous briserions la règle numéro 1.
En résumé, si les designers des génériques avaient laissé tomber la règle pour ce cas et laisser le bénéfice du doute au développeur, nous aurions échappé à plusieurs complexité.
Je vous laisse juge de leur décision.
Par exemple, les arrays
Une méthode retourne un type en wildcard
Pour garder la cohérence, le compilateur va fixer le type et l’appeler capture#6 pour s’assurer de la cohérence du type donné à T
Pour les développeurs de framework, ne jamais retourner de wildcard si possible
Une méthode retourne un type en wildcard
Pour garder la cohérence, le compilateur va fixer le type et l’appeler capture#6 pour s’assurer de la cohérence du type donné à T
Pour les développeurs de framework, ne jamais retourner de wildcard si possible
Warning mandatory pour ne pas enfreindre la règle #1
Par exemple, les arrays
Lorsqu’il y a un paramètre, le compilateur prend le type du paramètre pour déterminer le type de retour.
Cette pour cette raison que le syntaxe que vous voyez est apparu en Java 5. Le paramètre ne sert à rien sauf à fixer le type.
Mais ça vous les savez sûrement déjà.
Ensuite s’il n’y a pas de paramètre, le compilateur déduit le type à partir de la variable à laquelle elle est assignée.
Mais ça ne marche pas à tous les coups.
Dans les cas d’overloading par exemple. Impossible de deviner quelle méthode doit être appelé
Un truc classique est d’ajouter un paramètre juste pour la bonne cause. C’est un contournement classique.
Dans les cas d’overloading par exemple. Impossible de deviner quelle méthode doit être appelé
Pour les lambda, il est très fort probable d’en avoir besoin.
Vous avez ici l’exemple du début où je l’utilisais. Fort heureusement, ce n’est pas nécessaire dans la nouvelle API.
Par contre, j’ai un peu de difficulté à vous promettre que ce sera toujours le cas. On touche du bois
Par exemple, les arrays
Everybody knows what is erasure?
List<String> and ArrayList<String> got erasure
The variable in the loop kept is type
Boring, nous on va parler des cas où il n’y a pas d’erasure.
Oh dear, no erasure
On récupère la classe parent qui s’avère être générique et implémente donc l’interface ParameterizedType.
Il y a eu un ajout de plusieurs interfaces.
Elles sont passées presque complètement inaperçu mais peuvent être utile.
Par exemple, l’exemple précédent peut servir à faire ça
Par polymorphisme, le getClass() retourne ADao. Donc la superclass est BaseDao, et le type du paramètre générique est A.
And yes, the dumb part if the SuppressWarnings. It comes come the (Class<T>) cast. Since the compiler can’t be sure that actualType is a Class, we get the warning. On ne peut pas s’en débarasser.
Par exemple, les arrays
Vous avez une classe générique étendue par un autre qui fixe le type à String. Rien de plus classique.
Magie, à la décompilation, une nouvelle méthode est apparue. C’est le bridge. Elle ne sert à rien sauf à l’assurer c’est bien une String qui est passé en paramètre.
Le volatile c’est parce que la modificateur de champs et de méthodes ont des codes communs. Donc en fait, le décompilateur (jad) a un petit bogue.
Attention, Java Decompiler ne vous montrera pas le bridge, il le fait disparaitre. On ne le voit qu’avec Jad qui est moins futé.
La règle #1 n’est pas enfreinte car nous avons un warning.
Par contre, Java ajoute dans ce cas particulier une sur-protection.
Impossible de s’en prémunir à la compilation. Tout est valide. Il fallait donc combler la faille pour empêcher un Object d’être assigné à une String et amener le chaos et l’anarchie dans la JVM. D’où, le bridge.
Vous avez deux méthodes. L’une déléguant directement à l’autre.
Si vous avez un framework faisant des proxies dynamiques ou de l’AOP, vous vous retrouvez avec deux méthodes instrumentées. Et c’est assez rare que c’est ce que vous aviez prévus.
Résultat, plus rien ne marche. Par exemple, Spring qui utilise beaucoup de proxies, a dû implémenter une class nommée BridgeMethodResolver qui permet de trouver la méthode concrête appelée par le bridge. Et malheureusement, le JDK n’a pas prévu le coup donc il faut la déduire.
C’est ce que j’avais de plus compliqué à vous montrer aujourd’hui. Passons donc à la conclusion.
J’ai pas mal pataugé sur ces sujets car ils sont assez peu documentés. C’est pas simple mais on finit par s’y retrouver.
La question qui me taraude maintenant c’est “Est-ce que vous connaissiez tout ça?” Pour m’assurez que je ne raconte pas que des banalités.
Est-ce que chaque personne pourrait lever la main et me dire si vous avez appris au moins une chose aujourd’hui?