⚠️ Lisez cette lettre dans votre navigateur pour profiter des corrections de dernière minute
Les liens marqués avec 🔒 sont accessibles seulement aux membres de M33
Savez-vous qu’avant la découverte de l’Amérique on sculptait des navets à la place des citrouilles ? Les citrouilles ont été préférées car elles sont plus tendres et plus grosses.
Mais je trouve quand même que les navets sont plus flippants :
Pôle de formation Craft
Je parle ici de mes activités transverses au sein de Sipios
Gemba
Nous avons ralenti (euphémisme) nos activités de Gemba ces derniers temps car nous avons dû faire face à de nouvelles priorités. Cependant les EM et moi avons à cœur de reprendre la pratique. Nous mettons au point le Heijunka et reviendront bientôt auprès des tech leads pour organiser les rotations.
Veille
Quelques pépites que j’ai envie de partager avec vous
Une chaîne bourrée de bons conseils pour les devs juniors et les seniors qui veulent réviser les fondamentaux :
Peut-être connaissez-vous Emily Bache, connue notamment pour avoir popularisé le kata Gilded Rose inventé par Terry Hughes ? Elle a récemment lancé sa chaîne et c’est déjà une mine de diamants que je vous invite à déguster à votre rythme :
Puzzler Investigation
Ici un petit challenge pour apprendre en s’amusant !
En partant de ce code :
import java.util.function.UnaryOperator;
public class Duck {
private static class Renamer implements UnaryOperator<String> {
@Override
public String apply(String aName) {
return aName + " of Northumbria";
}
}
private String name;
Duck(String name) {
this.name = name;
}
void rename(UnaryOperator<String> renamer) {
name = renamer.apply(name);
}
public static void main(String[] args) {
Duck duck = new Duck("Smith");
duck.rename(new Renamer()); // #1
duck.rename("John "::concat); // #2
duck.rename(aName -> "M. " + aName); // #3
duck.rename(String::toUpperCase); // #4
System.out.println(duck.name);
}
}
on souhaite comprendre comment le compilateur s’y prend pour comprendre que
#1 Une instance de Renamer
est assignable à un UnaryOperator<String>
#2 Une référence de méthode de signature
String concat(String)
est assignable à un UnaryOperator<String>
#3 Une lambda expression de signature
String λ(String)
est assignable à un UnaryOperator<String>
#4 Une référence de méthode de signature
String toUpperCase()
est assignable à un UnaryOperator<String>
javap
Pour commencer notre enquête nous allons utiliser les outils fournis avec la JVM.
javap pour “Java Disassembler” (Hein ?) est un outil qui nous permet d’inspecter le bytecode généré par le compilateur
En l’occurrence la commande javap -v Duck
nous affiche ceci :
Classfile ...blabla des détails sur le fichier...
public class Duck
...blabla des détails sur la classe...
Constant pool:
...une liste interminable de constantes...
{
Duck(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: (0x0000)
Code:
...le bytecode du constructeur...
void rename(java.util.function.UnaryOperator<java.lang.String>);
descriptor: (Ljava/util/function/UnaryOperator;)V
flags: (0x0000)
Code:
...le bytecode de rename...
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
...le bytecode de main...
}
SourceFile: "Duck.java"
NestMembers: ...les membres du nest...
BootstrapMethods:
...Tiens ? C'est quoi des BootstrapMethods ?...
InnerClasses:
...Pourquoi on parle de java/lang/invoke/MethodHandles$Lookup ici ?...
J’ai éludé les détails car beaucoup ne sont pas très intéressants. Parmi ceux qui sont intéressants, certains ne seront pas abordés ici.
Par exemple si vous voulez des détails sur les Nests, je vous renvoie à ce brillant article : Java — Modificateurs d’accès
Si vous voulez des détails sur tout, la meilleure source est la spec de la JVM
Ici on va se concentrer sur le bytecode de la méthode main
et la section BootstrapMethods
invokedynamic
Cette instruction, introduite avec Java 7, est issue de la JSR 292, elle-même issue du projet Da Vinci Machine. Avouons que rien que le nom est déjà super cool 😎
Vous l’avez peut-être déjà entendu : en Java, tout est objet, même le compilateur. Et même le runtime d’ailleurs.
Qu’est-ce que ça veut dire ? Ça veut dire qu’il est possible de fournir du code à la JVM pour influencer son comportement au runtime.
Nous avons là 3 instructions invokedynamic
(aux offsets 24, 33 et 42) :
#0:apply:(Ljava/lang/String;)Ljava/util/function/UnaryOperator;
#1:apply:()Ljava/util/function/UnaryOperator;
#2:apply:()Ljava/util/function/UnaryOperator;
Le premier champ (un nombre) est une référence dans la section BootstrapMethods
(cf ci-dessous ⬇️).
Le deuxième, “apply”, est la méthode de l’interface fonctionnelle que nous voulons invoquer.
Le troisième est une signature de méthode. La première prend un argument de type String
, et toutes retournent un UnaryOperator
.
BootstrapMethods
“Bootstrap” ça fait penser à du code qui tourne une seule fois pour démarrer un autre mécanisme.
Une BootstrapMethods
est une méthode Java exécutée au runtime la première fois que le flot d’exécution passe par une instruction invokedynamic
s. Elle retourne une instance de CallSite
qui, à son tour, est invoquée et sera ensuite directement invoquée à chaque fois que la JVM exécutera la même instruction1.
Ici la méthode bootstrap est la même dans les 3 cas qui nous intéressent. Il s’agit de la méthode LambdaMetafactory.metafactory du JDK :
REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#86 (Ljava/lang/Object;)Ljava/lang/Object;
...implementation...
#91 (Ljava/lang/String;)Ljava/lang/String;
Seul le second argument change :
REF_invokeVirtual java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String;
REF_invokeStatic Duck.lambda$main$0:(Ljava/lang/String;)Ljava/lang/String;
REF_invokeVirtual java/lang/String.toUpperCase:()Ljava/lang/String;
Curieusement la taille de la liste des arguments (3) ne correspond pas au nombre de paramètres de la méthode (6). Lisons la javadoc pour comprendre :
Parameters:
caller
- Represents a lookup context with the accessibility privileges of the caller. Specifically, the lookup context must have full privilege access. When used withinvokedynamic
, this is stacked automatically by the VM.
interfaceMethodName
- The name of the method to implement. When used withinvokedynamic
, this is provided by theNameAndType
of theInvokeDynamic
structure and is stacked automatically by the VM.
factoryType
- The expected signature of theCallSite
. The parameter types represent the types of capture variables; the return type is the interface to implement. When used withinvokedynamic
, this is provided by theNameAndType
of theInvokeDynamic
structure and is stacked automatically by the VM.
Ok, la VM fournit les 3 premiers arguments. Les arguments listés dans le bytecode sont donc les 3 derniers. C’est à dire :
interfaceMethodType
- Signature and return type of method to be implemented by the function object.
implementation
- A direct method handle describing the implementation method which should be called (with suitable adaptation of argument types and return types, and with captured arguments prepended to the invocation arguments) at invocation time.
dynamicMethodType
- The signature and return type that should be enforced dynamically at invocation time. In simple use cases this is the same asinterfaceMethodType
.
Observons le code source de cette metafactory :
On wrap une instance de InnerClassLambdaMetafactory
autour des arguments, on les valide, puis on construit un CallSite. Straightforward 🙂
Avant de conclure, faisons un petit détour du côté du projet Leyden
Projet Leyden
Le projet Leyden a pour but d’accélérer le démarrage de la JVM. Le principe est de décaler certaines opérations du runtime au compile-time.
On pourrait être tenté de décaler l’appel des méthodes bootstrap au compile-time mais ce n’est pas si simple 🙂
Pour conclure
Je trouve ça passionnant toute l’activité autour de Java depuis qu’ils ont changé leur modèle de release. Cela renforce ma conviction que plus vos itérations sont courtes, plus votre Takt est régulier et plus vous gagnez en productivité
La prochaine fois nous examineront d’un peu plus près la classe CallSite
, j’ai hâte de découvrir et de partager avec vous ce sujet passionnant !
En attendant, je cite Kurgan en vous souhaitant une heureuse Toussaint !
Le mécanisme est très bien décrit ici : https://www.baeldung.com/java-invoke-dynamic