2017-05-29 1 views
10

Supposons que je fais dans jshell:Existe-t-il un moyen d'utiliser les références de méthode pour les fonctions de premier niveau dans jshell?

jshell> void printIsEven(int i) { 
    ...>  System.out.println(i % 2 == 0); 
    ...> } 
| created method printIsEven(int) 

jshell> List<Integer> l = Arrays.asList(7,5,4,8,5,9); 
l ==> [7, 5, 4, 8, 5, 9] 

jshell> l.forEach(/* ??? */); // is it possible to use a method reference here? 

Dans un programme normal que je pourrais écrire l.forEach(this::printIsEven) dans un contexte non statique ou l.forEach(MyClass::printIsEven) dans le contexte statique d'une classe nommée MyClass.

L'utilisation this::printIsEven dans jshell ne fonctionne pas parce que jshell exécute des instructions dans un contexte statique, mais vous ne pouvez pas utiliser une référence de méthode statique parce qu'il n'y a pas de nom de classe préfixe ::printIsEven, et d'essayer l.forEach(::printIsEven) est juste une erreur de syntaxe.

+0

Je n'ai pas utilisé JShell, mais ne pouvez-vous pas rendre la méthode statique? –

+1

@ChandlerBing Non, cela rend 'Modifier 'static' non autorisé dans les déclarations de niveau supérieur, ignoré' –

Répondre

13

Vous pouvez créer une nouvelle classe pour que:

jshell> class Foo { static void printIsEven(int i) { 
    ...>  System.out.println(i % 2 == 0); 
    ...> }} 
| created class Foo 

jshell> Arrays.asList(1,2,3).forEach(Foo::printIsEven) 
false 
true 
false 

Techniquement, il n'y a plus une fonction de haut niveau, mais l'effet souhaité.

Maintenant, si vous saviez que et que vous voulez toujours faire référence à des méthodes de haut niveau ...

Pour autant que je peux dire, la « classe de niveau supérieur » qui tient « Etat » pour la coquille est jdk.jshell.JShell , mais jdk.jshell.JShell::printIsEven dans Error: invalid method reference. Et vous avez déjà mentionné qu'il n'est pas possible de rendre les méthodes de niveau supérieur statiques (Modifier 'static' not permitted in top-level declarations, ignored). Après un coup d'oeil rapide à la JEP, il semble intentionnel. Et il mentionne en fait l'approche "définir-static-method-in-new-class" par le dessus.

Je suis deviner la « classe » de haut niveau a besoin de magie pour pouvoir redéfinir méthodes & autres déclarations de haut niveau, et les limitations pourraient découler de ses propres limites de la machine virtuelle Java dans sa capacité à redéfinir les classes/méthodes au moment de l'exécution. The source est intéressant mais je ne suis pas en mesure d'en tirer une réponse significative.


Edit: Donc, je me suis un peu emporté. C'est de ta faute.
Je pense toujours qu'il n'est pas possible d'obtenir une référence de méthode à une méthode de niveau supérieur dans jshell, mais ... ma précédente estimation des raisons pour lesquelles est probablement erronée. Le tableau suivant montre que dans jshell, lorsque vous "redéfinissez" une classe, l'ancienne classe est toujours présente: le contexte d'évaluation décale simplement certains mappages pour résoudre d'autres références à la nouvelle définition de classe.

jshell> class A { static int v=1; void m() { System.out.println(getClass() + " v=" + v); } } 
| created class A 

jshell> new A().m() 
class REPL.$JShell$11$A v=1 

// Changing static value of "v" 
jshell> class A { static int v=2; void m() { System.out.println(getClass() + " v=" + v); } } 
| modified class A 

// Actually not modified, this is still the same class (and as a result the static init of v has not been reexecuted, so, still 1) 
jshell> new A().m() 
class REPL.$JShell$11$A v=1 

// Let's add a boolean field to change the structure 
jshell> class A { static int v=3; boolean x=false; void m() { System.out.println(getClass() + " v=" + v); } } 
| replaced class A 

// Notice new class name: 
jshell> new A().m() 
class REPL.$JShell$11B$A v=3 

// But old version is still there, only hidden a bit by evaluation context: 
jshell> Class.forName("REPL.$JShell$11$A").getDeclaredField("v").getInt(null) 
$7 ==> 1 

jshell> Class.forName("REPL.$JShell$11B$A").getDeclaredField("v").getInt(null) 
$8 ==> 3 

donc cette petite démo suggère qu'il n'a rien à voir avec internals JVM pour la redéfinition des classes, car pas une telle chose se passe ici.

Alors je voulais voir la liste de toutes les classes chargées:

jshell> Class c = Thread.currentThread().getContextClassLoader().getClass() 
c ==> class jdk.jshell.execution.DefaultLoaderDelegate$RemoteClassLoader 

jshell> while (c != java.lang.ClassLoader.class) { c = c.getSuperclass(); System.out.println(c); } 
class java.net.URLClassLoader 
class java.security.SecureClassLoader 
class java.lang.ClassLoader 

jshell> c.getDeclaredField("classes").setAccessible(true) 
| java.lang.reflect.InaccessibleObjectException thrown: Unable to make field private final java.util.Vector java.lang.ClassLoader.classes accessible: module java.base does not "opens java.lang" to unnamed module @7494e528 
|  at AccessibleObject.checkCanSetAccessible (AccessibleObject.java:337) 
|  at AccessibleObject.checkCanSetAccessible (AccessibleObject.java:281) 
|  at Field.checkCanSetAccessible (Field.java:175) 
|  at Field.setAccessible (Field.java:169) 
|  at (#26:1) 

Ah, oui, Java 9 modules ... :) Bon sang

Oh, eh bien, ce sera tout pour aujourd'hui.

+0

Merci pour le suivi en profondeur. J'ai essayé 'jshell -J-add-opens = java.base/java.lang = ALL-UNNAMED' et même' jshell -J-permit-illegal-access' et j'ai quand même obtenu le 'InaccessibleObjectException' dans les deux cas. Tant pis. – adashrod