Skip to content

Instantly share code, notes, and snippets.

@JakeWharton
Last active May 28, 2022 16:14
Show Gist options
  • Save JakeWharton/ea4982e491262639884e to your computer and use it in GitHub Desktop.
Save JakeWharton/ea4982e491262639884e to your computer and use it in GitHub Desktop.
A comparison between non-capturing and capturing expressions across Java 6, Java 8, Java 8 with Retrolambda, Kotlin with native function expressions, and Kotlin with Java SAM expression.
Each respective use has a .class method cost of
Java 6 anonymous class: 2
Java 8 lambda: 1
Java 8 lambda with Retrolambda: 6
Java 8 method reference: 0
Java 8 method reference with Retrolambda: 5
Kotlin with Java Runnable expression: 3 (subtract one for non-capturing)
Kotlin with native function expression: 4 (subtract one for non-capturing)
All of them also require an additinal class to be generated (Java 8 defers this to happen at runtime).
(Remember, don't count the methods from `Capturing` / `NonCapturing` or its implicit constructor)
import java.util.Arrays;
class NonCapturing {
public static void main(String... args) {
run(new Runnable() {
@Override public void run() {
System.out.println("Hey!");
}
});
}
private static void run(Runnable run) {
run.run();
}
}
class Capturing {
public static void main(final String... args) {
run(new Runnable() {
@Override public void run() {
System.out.println("Hey! " + Arrays.toString(args));
}
});
}
private static void run(Runnable run) {
run.run();
}
}
$ javap -p NonCapturing*
Compiled from "Java6.java"
final class NonCapturing$1 implements java.lang.Runnable {
NonCapturing$1();
public void run();
}
Compiled from "Java6.java"
class NonCapturing {
NonCapturing();
public static void main(java.lang.String...);
private static void run(java.lang.Runnable);
}
$ javap -p Capturing*
Compiled from "Java6.java"
final class Capturing$1 implements java.lang.Runnable {
final java.lang.String[] val$args;
Capturing$1(java.lang.String[]);
public void run();
}
Compiled from "Java6.java"
class Capturing {
Capturing();
public static void main(java.lang.String...);
private static void run(java.lang.Runnable);
}
import java.util.Arrays;
class NonCapturing {
public static void main(String... args) {
run(() -> System.out.println("Hey!"));
}
private static void run(Runnable run) {
run.run();
}
}
class Capturing {
public static void main(String... args) {
run(() -> System.out.println("Hey! " + Arrays.toString(args)));
}
private static void run(Runnable run) {
run.run();
}
}
$ javap -p NonCapturing*
Compiled from "Java8Lambda.java"
class NonCapturing {
NonCapturing();
public static void main(java.lang.String...);
private static void run(java.lang.Runnable);
private static void lambda$main$0();
}
$ javap -p Capturing*
Compiled from "Java8Lambda.java"
class Capturing {
Capturing();
public static void main(java.lang.String...);
private static void run(java.lang.Runnable);
private static void lambda$main$1(java.lang.String[]);
}
$ javap -p NonCapturing*
final class NonCapturing$$Lambda$1 implements java.lang.Runnable {
private static final NonCapturing$$Lambda$1 instance;
private NonCapturing$$Lambda$1();
public void run();
static {};
public static java.lang.Runnable lambdaFactory$();
}
Compiled from "Java8Lambda.java"
class NonCapturing {
NonCapturing();
public static void main(java.lang.String...);
private static void run(java.lang.Runnable);
private static void lambda$main$0();
static void access$lambda$0();
}
$ javap -p Capturing*
final class Capturing$$Lambda$1 implements java.lang.Runnable {
private final java.lang.String[] arg$1;
private Capturing$$Lambda$1(java.lang.String[]);
private static java.lang.Runnable get$Lambda(java.lang.String[]);
public void run();
public static java.lang.Runnable lambdaFactory$(java.lang.String[]);
}
Compiled from "Java8Lambda.java"
class Capturing {
Capturing();
public static void main(java.lang.String...);
private static void run(java.lang.Runnable);
private static void lambda$main$1(java.lang.String[]);
static void access$lambda$0(java.lang.String[]);
}
class NonCapturing {
public static void main(String... args) {
run(NonCapturing::sayHi);
}
private static void run(Runnable run) {
run.run();
}
private static void sayHi() {
System.out.println("Hey!");
}
}
class Capturing {
public static void main(Capturing instance) {
run(instance::sayHi);
}
private static void run(Runnable run) {
run.run();
}
void sayHi() {
System.out.println("Hey!");
}
}
$ javap -p NonCapturing*
Compiled from "Java8MethodRef.java"
class NonCapturing {
NonCapturing();
public static void main(java.lang.String...);
private static void run(java.lang.Runnable);
private static void sayHi();
}
$ javap -p Capturing*
Compiled from "Java8MethodRef.java"
class Capturing {
Capturing();
public static void main(Capturing);
private static void run(java.lang.Runnable);
void sayHi();
}
$ javap -p NonCapturing*
final class NonCapturing$$Lambda$1 implements java.lang.Runnable {
private static final NonCapturing$$Lambda$1 instance;
private NonCapturing$$Lambda$1();
public void run();
static {};
public static java.lang.Runnable lambdaFactory$();
}
Compiled from "Java8MethodRef.java"
class NonCapturing {
NonCapturing();
public static void main(java.lang.String...);
private static void run(java.lang.Runnable);
static void sayHi();
static void access$lambda$0();
}
$ javap -p Capturing*
final class Capturing$$Lambda$1 implements java.lang.Runnable {
private final Capturing arg$1;
private Capturing$$Lambda$1(Capturing);
private static java.lang.Runnable get$Lambda(Capturing);
public void run();
public static java.lang.Runnable lambdaFactory$(Capturing);
}
Compiled from "Java8MethodRef.java"
class Capturing {
Capturing();
public static void main(Capturing);
private static void run(java.lang.Runnable);
void sayHi();
static void access$lambda$0(Capturing);
}
// 'run' is built-in method so we use 'run2' instead.
class NonCapturing {
fun main(vararg args: String) {
run2(Runnable { println("Hey!") })
}
private fun run2(func: Runnable) {
func.run()
}
}
class Capturing {
fun main(vararg args: String) {
run2(Runnable { println("Hey! $args") })
}
private fun run2(func: Runnable) {
func.run()
}
}
$ javap -p NonCapturing*
Compiled from "KotlinClass.kt"
final class NonCapturing$main$1 implements java.lang.Runnable {
public static final NonCapturing$main$1 INSTANCE;
public final void run();
NonCapturing$main$1();
static {};
}
Compiled from "KotlinClass.kt"
public final class NonCapturing {
public final void main(java.lang.String...);
private final void run2(java.lang.Runnable);
public NonCapturing();
}
$ javap -p Capturing*
Compiled from "KotlinClass.kt"
final class Capturing$main$1 implements java.lang.Runnable {
final java.lang.String[] $args;
public final void run();
Capturing$main$1(java.lang.String[]);
}
Compiled from "KotlinClass.kt"
public final class Capturing {
public final void main(java.lang.String...);
private final void run2(java.lang.Runnable);
public Capturing();
}
// 'run' is built-in method so we use 'run2' instead.
class NonCapturing {
fun main(vararg args: String) {
run2({ println("Hey!") })
}
private fun run2(func: () -> Unit) {
func()
}
}
class Capturing {
fun main(vararg args: String) {
run2({ println("Hey! $args") })
}
private fun run2(func: () -> Unit) {
func()
}
}
$ javap -p NonCapturing*
Compiled from "KotlinFunc.kt"
final class NonCapturing$main$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit> {
public static final Capturing$main$1 INSTANCE;
public java.lang.Object invoke();
public final void invoke();
NonCapturing$main$1();
static {};
}
Compiled from "KotlinFunc.kt"
public final class NonCapturing {
public final void main(java.lang.String...);
private final void run2(kotlin.jvm.functions.Function0<kotlin.Unit>);
public NonCapturing();
}
$ javap -p Capturing*
Compiled from "KotlinFunc.kt"
final class Capturing$main$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit> {
final java.lang.String[] $args;
public java.lang.Object invoke();
public final void invoke();
Capturing$main$1(java.lang.String[]);
}
Compiled from "KotlinFunc.kt"
public final class Capturing {
public final void main(java.lang.String...);
private final void run2(kotlin.jvm.functions.Function0<kotlin.Unit>);
public Capturing();
}
@wiibaa
Copy link

wiibaa commented Jan 8, 2016

Sorry for the silly question, but did you want to say "Java Seven with Retrolambda" ?

@JakeWharton
Copy link
Author

@wiibaa Not really. It's code compiled with Java 8 that is then run through the Retrolambda tool. I could have been more clear here, I suppose, but it's Java 8 code not Java 7 code even though the bytecode ends up not being Java 8 but Java 7 compatible.

@tomquist
Copy link

Great summary. It would be nice to have some up to date numbers for Retrolambda 2.3 (after merging your pull request luontola/retrolambda#81).

@mattijsf
Copy link

mattijsf commented Apr 4, 2017

Is there a difference between compiling against different minSdkVersion using the new java 8 toolchain e.g. on minSdkVersion < 24 and minSdkVersion >= 24. As on minSdkVersion 24 java 8 feature support seems much more complete, there might also be a difference in generated classes.

@piotrgwiazda
Copy link

A Scala example would be great plus Kotlin 1.1 with -jvm-target 1.8

@timemanx
Copy link

timemanx commented Jul 3, 2017

@JakeWharton In KotlinFunc_output.txt, on line 4, I guess it should be public static final NonCapturing$main$1 INSTANCE; instead of public static final Capturing$main$1 INSTANCE;.

@nhoxbypass
Copy link

Hi Jake, I have a question

Non-capturing lambda will generate a static instance (singleton) to re-use, so this instance never gets garbage collected? Because it might effect memory footprint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment