Suppose I do this in 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?
In a normal program I could write l.forEach(this::printIsEven)
in a non-static context or l.forEach(MyClass::printIsEven)
in the static context of a class named MyClass
.
Using this::printIsEven
in jshell doesn't work because jshell executes statements in a static context, but you can't use a static method reference because there's no class name to prefix ::printIsEven
, and trying l.forEach(::printIsEven)
is just a syntax error.
Types of Method ReferencesStatic Method Reference. Instance Method Reference of a particular object. Instance Method Reference of an arbitrary object of a particular type. Constructor Reference.
Method references are a special type of lambda expressions. They're often used to create simple lambda expressions by referencing existing methods. There are four kinds of method references: Static methods. Instance methods of particular objects.
Java provides a new feature called method reference in Java 8. Method reference is used to refer method of functional interface. It is compact and easy form of lambda expression. Each time when you are using lambda expression to just referring a method, you can replace your lambda expression with method reference.
That's all about what is method reference is in Java 8 and how you can use it to write clean code in Java 8. The biggest benefit of the method reference or constructor reference is that they make the code even shorter by eliminating lambda expression, which makes the code more readable.
You can create a new class for that:
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
Technically it is no longer a top-level function, but it achieves the desired effect.
Now, if you knew that and still want to reference top-level methods...
As far as I can tell, the "top-level class" that holds "state" for the shell is jdk.jshell.JShell
, but jdk.jshell.JShell::printIsEven
results in Error: invalid method reference
. And you already mentioned it's not possible to make top-level methods static (Modifier 'static' not permitted in top-level declarations, ignored
).
After a quick look at the JEP, it seems intentional. And it actually mentions the "define-static-method-in-new-class" approach from above.
I'm guessing the top-level "class" needs special magic to be able to redefine methods & other top-level declarations, and the limitations might derive from the JVM's own limitations in its ability to redefine classes/methods at runtime. The source is interesting but I'm not able to derive a meaningful answer from that.
Edit: So, I kinda got carried away. This is your fault.
I still think it's not possible to obtain a method reference to a top-level method in jshell, but... my previous guess about the reasons why is probably wrong.
The following shows that in jshell, when you "redefine" a class, the old class is still there: the evaluation context just shifts some mappings to resolve further references to the new class definition.
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
So this little demo suggests it has nothing to do with JVM internals for class redefinition, because no such thing happens here.
Then I wanted to see the list of all loaded classes:
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, yes, Java 9 modules... dammit :)
Oh, well, that'll be all for today.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With