Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to use method references for top-level functions in jshell?

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.

like image 293
adashrod Avatar asked May 29 '17 04:05

adashrod


People also ask

What type of methods can be pointed by method references?

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.

WHAT ARE method references and how are they useful?

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.

Which code uses method reference correctly?

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.

What is the advantage of using method reference in Java 8?

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.


1 Answers

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.

like image 161
Hugues M. Avatar answered Oct 12 '22 19:10

Hugues M.