I have a function that uses http request to retrieve remote page. That page may has one or zero next page's link. If I want to generate a chain of all pages , generateSequence is an ideal solution. This is what I have done:
First , there are two utility functions :
fun getBlockingDocument(url: String): Document? , as the name suggests, it is a blocking function. The implementation is just sending HTTP request and parsing to a JSoup document.
fun getNextIndexPage(doc: Document, url: String): String? , it is also a blocking function , but it's not related to network , it just parses to get the next page, so blocking is OK here.
OK , here is the sequence code :
val initUrl = // initial url
generateSequence(tools.getBlockingDocument(initUrl).let { initUrl to it }) { (url, doc) ->
doc?.let {
parser.getNextIndexPage(doc, url)
}?.let { nextUrl ->
nextIndexUrl to tools.getBlockingDocument(nextUrl)
}
}.forEachIndexed { index, urlAndDoc ->
val url = urlAndDoc.first
logger.info("[{}] : {}", index, url)
}
It works well , and correctly chains all pages.
But what if I change the network call to a suspend function ? This is what I created :
suspend fun getSuspendingDocument(url: String): Document?
I found no similar generateSequence builder samples as flow , So I implement like this :
#ExperimentalCoroutinesApi
#Test
fun testGetAllPagesByFlow() {
val flow = flow<Pair<String, Document?>> {
suspend fun generate(url: String) {
tools.getSuspendingDocument(url)?.let { url to it }?.also { (url, doc) ->
emit(url to doc)
parser.getNextIndexPage(doc, url)?.also { nextUrl ->
generate(nextUrl) // recursive
}
}
}
generate("http://...initial url here")
} // flow
runBlocking {
flow.collectIndexed { index, urlAndDoc ->
val url = urlAndDoc.first
logger.info("[{}] : {}", index, url)
}
}
}
I uses a recursive call (fun generate()) to emit the next url found in each page. I am not sure if it is the idiomatic way to create a Flow but I found no similar codes. If you have better/idiomatic way , please tell me , thanks a lot!
Anyway , I think it should work , but my IDE (IntelliJ) complains wrong bytecode generated , which I've never seen it before.
Error:Kotlin: [Internal Error] org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: wrong bytecode generated
#Lorg/jetbrains/annotations/Nullable;() // invisible
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
#Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
L1
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.p$ : Lkotlinx/coroutines/flow/FlowCollector;
ASTORE 2
L2
L3
LINENUMBER 44 L3
NEW destiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1
DUP
ALOAD 2
ALOAD 3
ACONST_NULL
INVOKESPECIAL destiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1.<init> (Lkotlinx/coroutines/flow/FlowCollector;Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1;Lkotlin/coroutines/Continuation;)V
ASTORE 3
L4
L5
LINENUMBER 56 L5
ALOAD 3
CHECKCAST destiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.$initUrl : Ljava/lang/String;
ALOAD 0
ALOAD 0
ALOAD 2
PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.L$0 : Ljava/lang/Object;
ALOAD 0
ALOAD 3
PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.L$1 : Ljava/lang/Object;
ALOAD 0
ICONST_1
PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.label : I
INVOKEVIRTUAL destiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1.invoke (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
L6
DUP
ALOAD 4
IF_ACMPNE L7
L8
LINENUMBER 42 L8
ALOAD 4
ARETURN
L9
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.L$1 : Ljava/lang/Object;
CHECKCAST destiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1
ASTORE 3
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow$1.L$0 : Ljava/lang/Object;
CHECKCAST kotlinx/coroutines/flow/FlowCollector
ASTORE 2
L10
ALOAD 1
INVOKESTATIC kotlin/ResultKt.throwOnFailure (Ljava/lang/Object;)V
ALOAD 1
L7
LINENUMBER 58 L7
POP
L11
L12
GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
ARETURN
L13
L14
L15
NEW java/lang/IllegalStateException
DUP
LDC "call to 'resume' before 'invoke' with coroutine"
INVOKESPECIAL java/lang/IllegalStateException.<init> (Ljava/lang/String;)V
ATHROW
RETURN
L16
LOCALVARIABLE $this$flow Lkotlinx/coroutines/flow/FlowCollector; L2 L14 2
LOCALVARIABLE $fun$generate$1 Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow$1$1; L4 L11 3
LOCALVARIABLE this Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow$1; L0 L13 0
LOCALVARIABLE $result Ljava/lang/Object; L0 L13 1
MAXSTACK = 5
MAXLOCALS = 4
File being compiled at position: (42,46) in /destiny/data/core/src/test/java/destiny/data/FlowTest.kt
The root cause org.jetbrains.kotlin.codegen.CompilationException was thrown at: org.jetbrains.kotlin.codegen.TransformationMethodVisitor.visitEnd(TransformationMethodVisitor.kt:92)
at org.jetbrains.kotlin.codegen.FunctionCodegen.endVisit(FunctionCodegen.java:990)
at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethodBody(FunctionCodegen.java:487)
at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:260)
at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:176)
...
It's very long , the remainings are omitted.
What's wrong with this code ?
And if the recursive way is not ideal , is there any better solution ? (like generateSequence , it is beautiful) . Thanks.
Environments :
<kotlin.version>1.3.50</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.3.2</version>
</dependency>
IntelliJ 2018.3.6
$ java -version
java version "11.0.3" 2019-04-16 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.3+12-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.3+12-LTS, mixed mode)
Why use recursion at all? The following code would do the same
val f: Flow<Pair<String, Document?>> = flow {
var nextUrl: String? = url
while (nextUrl != null) {
val doc = tools.getSuspendingDocument(nextUrl)
emit(url to doc)
if (doc == null) break;
nextUrl = parser.getNextIndexPage(doc, url)
}
}
Related
From what I understand, the big advantage of ?.let{} over != null is it guarantees that a mutable value is not changed inside the block.
However, in case of an immutable variable is there a performance difference?
For example, I have a simple method
private fun test() {
val x: String? = ""
if (x != null) {
print("test")
}
x?.let {
print("test")
}
}
When I see the resulting Kotlin Bytecode it seems that for let it has much more code.
For the x != null case:
LINENUMBER 8 L1
L2
LINENUMBER 9 L2
LDC "test"
ASTORE 2
L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 2
INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/Object;)V
L4
L5
For x?.let { } case it is:
LINENUMBER 12 L5
ALOAD 1
ASTORE 2
L6
L7
ALOAD 2
ASTORE 3
L8
ICONST_0
ISTORE 4
L9
LINENUMBER 13 L9
LDC "test"
ASTORE 5
L10
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 5
INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/Object;)V
L11
L12
LINENUMBER 14 L12
L13
NOP
L14
LINENUMBER 12 L14
L15
NOP
L16
If I decompile to java then the resulting code seems similar with one more variable being assigned for let (curiously an int variable is set to false)
For x != null:
String var2 = "test";
System.out.print(var2);
For x?.let { }
int var4 = false;
String var5 = "test";
System.out.print(var5);
In the end, my question is: Is there a performance different between let and != for immutable variables?
The two statements are not really equivalent. The outcome is obviously the same in your two examples, but let is a scope function and thus is doing a bit more lifting than simple flow control.
From the linked documentation:
The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When you call such a function on an object with a lambda expression provided, it forms a temporary scope.
For example even if you're not using the it in your example, this context with the it-variable is still created and that bears some overhead with it.
However, as others have pointed out I think this is a case where it's better to optimize the code for readability over speed in this case. In cases where a let allows for more readable code (which IMO is usually the case), that's a bigger win than the miniscule performance gain you may get from using an if instead.
In Scala, you can write
val x = {
... do some complex computations ..
42
}
to hide stuff inside of the code block.
The closest I came in Kotlin is:
val x = {
... do some complex computations ..
42
}()
Is there a better way?
EDIT:
isn’t run {} in the above example essentially the same
is calling run costly?
ANSWER:
using run {} inlines, whereas {}() does NOT (see my own answer below)
Use the run function. It takes a function as a parameter, runs it and returns the result.
val x = run {
... do some complex computations ..
42
}
The run function is inlined, so it will have no performance overhead.
Yes there is the run function.
val x = run {
...
42
}
And also you can use these methods too:
val a=1.also{
// your code
}
val b=2.apply{
// your code}
}
val c=3.let{
// your code
}
val d=4.runCatching{
// your code
}
To verify inlining when using run, I created a small example:
fun main() {
{
Math.random()
}()
run {
Math.random()
}
}
and viewed the produced bytecode:
public final static main()V
L0
LINENUMBER 2 L0
GETSTATIC TestKt$main$1.INSTANCE : LTestKt$main$1;
CHECKCAST kotlin/jvm/functions/Function0
INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object; (itf)
POP
L1
LINENUMBER 6 L1
L2
L3
L4
ICONST_0
ISTORE 0
L5
LINENUMBER 7 L5
INVOKESTATIC java/lang/Math.random ()D
L6
L7
LINENUMBER 6 L7
L8
POP2
L9
LINENUMBER 9 L9
RETURN
L10
LOCALVARIABLE $i$a$-run-TestKt$main$2 I L5 L7 0
MAXSTACK = 2
MAXLOCALS = 1
So, indeed:
Calling via () calls a generated Function0-derived class that encapsulates the block as a lambda (label L0)
Calling via run fully inlines the block (label L5)
I think I found a strange fringe case:
What I had:
class Day constructor(cal: Calendar, refCal: Calendar) {
val cal: Calendar
val isBefore: Boolean
val isAfter: Boolean
init {
this.cal = cal.clone() as Calendar
isBefore = 0 < cal.compareTo(refCal)
isAfter = 0 > cal.compareTo(refCal)
}
}
I would instantiate this with two dates, one being a reference, and determine if the date is before or after a reference date. However, what I found on execution is that isBefore and isAfter remained false in some cases, independently of what they should be worth, unless I went in step-by-step with the debugger. So, apparently init was called not right after the constructor, just delayed enough for my values not to be set?
What I did to resolve it was simply calculate isBefore and isAfter in getters:
class Day constructor(cal: Calendar, refCal: Calendar) {
val cal: Calendar
init {
this.cal = cal.clone() as Calendar
this.refCal = refCal.clone() as Calendar
}
val isBefore: Boolean
get() { return 0 < cal.compareTo(refCal) }
val isAfter: Boolean
get() { return 0 > cal.compareTo(refCal) }
}
I'd like to know if my assumption is correct, if there's a way a value can be called before it is initialized with init and if so, if there is a way to mitigate this.
Call example:
fun setDates(refDate: Calendar) {
val cal = Calendar.getInstance()
cal.set(refDate.get(Calendar.YEAR), refDate.get(Calendar.MONTH), refDate.get(Calendar.DAY_OF_MONTH), 0, 0, 0)
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
val monday = Day(cal, refDate)
//...
As far as I know the init block will be executed within the primary constructor, so your original class Day should be fine. You could just remove the init block and assign the values directly with the property's declaration though.
I see just 2 problems, one is:
However, what I found on execution is that isBefore and isAfter
remained false in some cases
That wouldn't be a surprise at all if cal.compareTo(refCal) returned 0. Or if the value of refCal changed between the execution of the 2 lines
isBefore = 0 < cal.compareTo(refCal)
isAfter = 0 > cal.compareTo(refCal)
Second problem is your second Day implementation, when you declare a getter for the property
val isBefore: Boolean
get() { return 0 < cal.compareTo(refCal) }
you're comparing the value each time you read that property, this means that isBefore's value could very easily change within the same Dayinstance, if the value of refCal property changes. If you want just to read the value of refCal and assign isBefore accordingly you should do that when initializing the property, within init block or within declaration (you could also omit the type and use > operator directly):
class Day constructor(cal: Calendar, refCal: Calendar) {
private val cal = cal.clone() as Calendar
val isBefore = cal > refCal
val isAfter = cal < refCal
}
And maybe add property like val isEqual = !isBefore && !isAfter because those 2 are not mutually exclusive.
Update
I just checked with Kotlin 1.2.30, Intellij's Kotlin plugin allows you to see the bytecode of the compile Kotlin class (the action is called "Show Kotlin Bytecode"). The code you put in the init block is indeed executed within the primary constructor. For instance this class:
class Day(cal: Calendar) {
val cal: Calendar
init {
this.cal = cal.clone() as Calendar
}
}
Compiles this constructor:
// access flags 0x1
public <init>(Ljava/util/Calendar;)V
#Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 1
LDC "cal"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 3 L1
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L2
LINENUMBER 8 L2
ALOAD 0
ALOAD 1
INVOKEVIRTUAL java/util/Calendar.clone ()Ljava/lang/Object;
DUP
IFNONNULL L3
NEW kotlin/TypeCastException
DUP
LDC "null cannot be cast to non-null type java.util.Calendar"
INVOKESPECIAL kotlin/TypeCastException.<init> (Ljava/lang/String;)V
ATHROW
L3
CHECKCAST java/util/Calendar
PUTFIELD Day.cal : Ljava/util/Calendar;
L4
RETURN
L5
LOCALVARIABLE this LDay; L0 L5 0
LOCALVARIABLE cal Ljava/util/Calendar; L0 L5 1
MAXSTACK = 5
MAXLOCALS = 2
As you can see the Calendar.clone is called there, so you can treat the init the same way as you would with a constructor in Java.
If you don't have a primary constructor, e.g.:
class Day {
constructor(cal: Calendar)
constructor()
val cal: Calendar
init {
this.cal = Calendar.getInstance()
}
}
2 constructors are created and the code of the init block is executed in each one of them.
// access flags 0x1
public <init>(Ljava/util/Calendar;)V
#Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 1
LDC "cal"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 4 L1
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L2
LINENUMBER 10 L2
ALOAD 0
INVOKESTATIC java/util/Calendar.getInstance ()Ljava/util/Calendar;
DUP
LDC "Calendar.getInstance()"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
PUTFIELD Day.cal : Ljava/util/Calendar;
L3
RETURN
L4
LOCALVARIABLE this LDay; L0 L4 0
LOCALVARIABLE cal Ljava/util/Calendar; L0 L4 1
MAXSTACK = 4
MAXLOCALS = 2
// access flags 0x1
public <init>()V
L0
LINENUMBER 5 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 10 L1
ALOAD 0
INVOKESTATIC java/util/Calendar.getInstance ()Ljava/util/Calendar;
DUP
LDC "Calendar.getInstance()"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
PUTFIELD Day.cal : Ljava/util/Calendar;
L2
RETURN
L3
LOCALVARIABLE this LDay; L0 L3 0
MAXSTACK = 4
MAXLOCALS = 1
I know inline keyword means to avoid the call overhead calling a funtion. But I can't figure out what inline a extension property work for?
Let say we have two extension property named foo and another with is inlined named bar
val Any.foo : Long
get() = Date().time
inline val Any.bar : Long
get() = Date().time
Executing any of them, we gent the expected output, the current time.
The bytecode for this file is this below:
public final class InlinedExtensionPropertyKt {
public final static getFoo(Ljava/lang/Object;)J
#Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "$receiver"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 9 L1
NEW java/util/Date
DUP
INVOKESPECIAL java/util/Date.<init> ()V
INVOKEVIRTUAL java/util/Date.getTime ()J
LRETURN
L2
LOCALVARIABLE $receiver Ljava/lang/Object; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
public final static getBar(Ljava/lang/Object;)J
#Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "$receiver"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 12 L1
NEW java/util/Date
DUP
INVOKESPECIAL java/util/Date.<init> ()V
INVOKEVIRTUAL java/util/Date.getTime ()J
LRETURN
L2
LOCALVARIABLE $receiver Ljava/lang/Object; L0 L2 0
LOCALVARIABLE $i$f$getBar I L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
#Lkotlin/Metadata;(mv={1, 1, 7}, bv={1, 0, 2}, k=2, d1={"\u0000\u000e\n\u0000\n\u0002\u0010\u0009\n\u0002\u0010\u0000\n\u0002\u0008\u0005\"\u0016\u0010\u0000\u001a\u00020\u0001*\u00020\u00028\u00c6\u0002\u00a2\u0006\u0006\u001a\u0004\u0008\u0003\u0010\u0004\"\u0015\u0010\u0005\u001a\u00020\u0001*\u00020\u00028F\u00a2\u0006\u0006\u001a\u0004\u0008\u0006\u0010\u0004\u00a8\u0006\u0007"}, d2={"bar", "", "", "getBar", "(Ljava/lang/Object;)J", "foo", "getFoo", "test sources for module app"})
// compiled from: InlinedExtensionPropertyKt.kt
}
We can see both are similar but differents only on these lines:
foo extract:
LOCALVARIABLE $receiver Ljava/lang/Object; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
bar extract:
LOCALVARIABLE $receiver Ljava/lang/Object; L0 L2 0
LOCALVARIABLE $i$f$getBar I L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
I really don't understand what is happennig here.
Can someone point me to see what is the behaviour, or the equivalent in java, or some use for this?
Edit
Given the compiler will replace the content of inlined property, it may convenient to inline every extension property having not heavy operations ?
Thank you
From Kotlin's doc,
Note that, since extensions do not actually insert members into classes, there's no efficient way for an extension property to have a backing field.
and also,
The inline modifier can be used on accessors of properties that don't have a backing field.
As mentioned above, an inline extension property does not have a backing field. You may treat an extension property as a pair of static getter/setter, like this:
//In Kotlin
var Any.foo : Long
get() = Date().time
set(value) {
//Cannot access field here since extension property cannot have backing field
//Do something with `obj`
}
//In Java
public static long getFoo(Object obj) {
return new Date().getTime();
}
public static void setFoo(Object obj) {
//Do something with `obj`
}
So, inline property means that the code of the getter/setter function will be inlined into the call site when accessing the property (same as regular inline functions).
//In Kotlin
val x = "".foo
val y = "".bar
//Generated code
val x = InlinedExtensionPropertyKt.getFoo("")
val y = Date().time
For the bytecode that you post in the question, sorry that I am not able explain what is happening. But you may try to take a look at the bytecode of the following code:
fun get() {
val x = "".foo
val y = "".bar
}
, where "".foo will invoke the getter function but "".bar will not.
I am still learning Kotlin and trying to understand its core principles. What I do not get is this:
fun x() : Int { return 10 }
val y : () -> Int = ::x
val z : () -> Int = { 10 }
fun main(args: Array<String>) {
println(::x)
println(y)
println(z)
}
we get the following output:
fun x(): kotlin.Int
fun x(): kotlin.Int
() -> kotlin.Int
My question is why the output is not the same (I believed these functions should be interchangeable, equivalent)? I think the type of all the functions should be () -> Int. Why do we keep the original name with the function signature (fun x) even though it is assigned to a different name (y)? Is there any language design principle which would require such difference in function signatures?
And a bonus question - why do we need to use the operator ::. It does not compile without it. But why is this required by the language design? Would not val y = x work just fine and be much simpler?
fun x() is an ordinary named function from procedural programming. val y is a property which holds a reference to x. val z is an anonymous functional from functional programming.
:: is a 'function reference', a kind of bridge between procedural and functional programming.
By default, your functions should be fun. Lambda expressions (anonymous functions), on the other side, are intended to be passed to other functions as callbacks.
IF you think from my point of view, I think you can understand it immediately.
x just is an identifier not a variable, so you can't reference it directly.
fun x() is a class which derived from Function0.
the expression ::x is the instance of the type of fun x(), this is called function reference expression in kotlin.
Kotlin makes functions and properties first-class citizens in the language, and introspecting them.
sometimes function has it own class in kotlin like as java-8 lambda expression. but function reference expression can't used with the diff receiver.
when calling an inline function followed with a lambda, both the lambda & the function will be inlined at call-sites. but call with a non-inline function reference expression, only the inline function will be inlined at call-sites.
I think for better understanding look at bytecode
private final static Lkotlin/jvm/functions/Function0; y
#Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x1A
// signature Lkotlin/jvm/functions/Function0<Ljava/lang/Integer;>;
// declaration: kotlin.jvm.functions.Function0<java.lang.Integer>
private final static Lkotlin/jvm/functions/Function0; z
#Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x19
public final static main([Ljava/lang/String;)V
#Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "args"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 6 L1
GETSTATIC MainKt$main$1.INSTANCE : LMainKt$main$1;
ASTORE 1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L2
LINENUMBER 7 L2
GETSTATIC MainKt.y : Lkotlin/jvm/functions/Function0;
ASTORE 1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L3
LINENUMBER 8 L3
GETSTATIC MainKt.z : Lkotlin/jvm/functions/Function0;
ASTORE 1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L4
LINENUMBER 9 L4
RETURN
L5
LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x19
public final static x()I
L0
LINENUMBER 11 L0
BIPUSH 10
IRETURN
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x19
// signature ()Lkotlin/jvm/functions/Function0<Ljava/lang/Integer;>;
// declaration: kotlin.jvm.functions.Function0<java.lang.Integer> getY()
public final static getY()Lkotlin/jvm/functions/Function0;
#Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 12 L0
GETSTATIC MainKt.y : Lkotlin/jvm/functions/Function0;
ARETURN
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x19
// signature ()Lkotlin/jvm/functions/Function0<Ljava/lang/Integer;>;
// declaration: kotlin.jvm.functions.Function0<java.lang.Integer> getZ()
public final static getZ()Lkotlin/jvm/functions/Function0;
#Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 13 L0
GETSTATIC MainKt.z : Lkotlin/jvm/functions/Function0;
ARETURN
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x8
static <clinit>()V
L0
LINENUMBER 12 L0
GETSTATIC MainKt$y$1.INSTANCE : LMainKt$y$1;
CHECKCAST kotlin/jvm/functions/Function0
PUTSTATIC MainKt.y : Lkotlin/jvm/functions/Function0;
L1
LINENUMBER 13 L1
GETSTATIC MainKt$z$1.INSTANCE : LMainKt$z$1;
CHECKCAST kotlin/jvm/functions/Function0
PUTSTATIC MainKt.z : Lkotlin/jvm/functions/Function0;
RETURN
MAXSTACK = 1
MAXLOCALS = 0
}
y and z value have type Function0
static <clinit>()V // here we have static initialization block where y and z are initialized
x have type MainKt$main$1
In each case(println) we just show method declaration(visibility modifiers, return type, signature) without invocation.
y and z are high order function that in bytecode are presented by Function0 class, for x value it's just static function.
And when you invoke println(::x) println(y) println(z) you just print functions declarations. This is ok, that declaration of x and y functions are different that in z function. Because y has reference to x function and z are still high order function that return 10. In simple words y == x, because you assign to y function declaration from x.
Just for Note. In Java I can show method declaration in this way:
public static void main(String[] args) {
try {
System.out.println(Main.class.getMethod("A", null)); // prints public static void Main.A()
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public static void A() {
System.out.println(15);
}
Conclusion:
In your code you just print function return type, visibility modifier and signature.