لامبدا و کلاس ناشناس داخلی چگونه کار میکنند؟

در نگاه اول لامبدا یک نسخه کوتاه شده کلاس ناشناس داخلی یا همان Anonymous Inner class به نظر میرسند اما تفاوتهایی بین این دو وجود دارد.
نکات کلیدی:
- لامبدا یک واسط کاربری تابعی (functional interface) پیادهسازی میکند.
- کلاسهای ناشناس داخلی میتوانند یک کلاس را extend کرده یا یک واسط کاربری را با هر تعداد تابع پیادهسازی کنند.
- لامبدا تنها به متغیرهای final یا عملا final دسترسی دارد.
- کلاس ناشناس داخلی میتواند از متغیرهای نمونه استفاده کند به همین دلیل وضعیت دارد اما لامبدا چنین نیست.
- لامبدا نمیتواند متغیری با نامی مشابه با یک متغیر در همان محدوده را تعریف کند.
- کلاس ناشناس به یک کلاس کامپایل میشود، در حالیکه لامبدا ساختار invokedynamic دارد.
چگونه کار میکنند:
کلاس ناشناس داخلی
- کامپایلر یک فایل کلاس به ازای هر کلاس ناشناس داخلی تولید میکند.
برای مثال AnonymouseInnerClass$1.class
- مشابه هر کلاس دیگر لازم است بارگذاری و اعتبارسنجی آن در شروع اجرا انجام گیرد.
لامبدا
نکته کلیدی پیادهسازی لامبدا ساختار InvokeDynamic است که در جاوا ۷ معرفی گردید. این قابلیت این امکان را میدهد تا زبانهای پویا در زمان اجرا به Symbolها متصل شوند.
یک لامبدا اینچنین کار میکند:
- محل فراخوانی invokedynamic را تولید کرده و از یک lambdafactory برای بازگرداندن پیادهسازی تابعی استفاده می کند.
- لامبدا به یک متد تبدیل شده تا توسط invokedynamic فراخوانی شود.
- متد در کلاس به عنوان تابع private static ذخیره میشود.
- دو نوع لامبدا وجود دارد. non-capturing لامبداها تنها از فیلدهای درون بدنه خودشان استفاده میکند در حالیکه capturing لامبداها به فیلدهای خارج از بدنه خود هم دسترسی دارند.
non-capturing لامبدا
public class NonCapturingLambda { public static void main(String[] args) { Runnable nonCapturingLambda = () -> System.out.println("NonCapturingLambda"); nonCapturingLambda.run(); } }
اگر فایل کلاس را با استفاده از CFR decompiler دیکود کنیم موارد زیر را خواهیم دید:
- لامبدا metafactory
- لامبدا یک تابع static void در کلاس است.
java -jar cfr_0_119.jar NonCapturingLambda --decodelambdas false /* * Decompiled with CFR 0_119. */ import java.io.PrintStream; import java.lang.invoke.LambdaMetafactory; public class NonCapturingLambda { public static void main(String[] args) { Runnable nonCapturingLambda = (Runnable)LambdaMetafactory.metafactory(null, null, null, ()V, lambda$0(), ()V)(); nonCapturingLambda.run(); } private static /* synthetic */ void lambda$0() { System.out.println("NonCapturingLambda"); } }
capturing لامبدا
به متغیرهای فاینال یا عملا فاینال خارج از بدنه خود دسترسی دارد.
public class CapturingLambda { public static void main(String[] args) { String effectivelyFinal = "effectivelyFinal"; Runnable capturingLambda = () -> System.out.println("capturingLambda " + effectivelyFinal); capturingLambda.run(); } }
که به شکل زیر decompile میشود.
java -jar cfr_0_119.jar CapturingLambda --decodelambdas false /* * Decompiled with CFR 0_119. */ import java.io.PrintStream; import java.lang.invoke.LambdaMetafactory; public class CapturingLambda { public static void main(String[] args) { String effectivelyFinal = "effectivelyFinal"; Runnable capturingLambda = (Runnable)LambdaMetafactory.metafactory(null, null, null, ()V, lambda$0(java.lang.String ), ()V)((String)effectivelyFinal); capturingLambda.run(); } private static /* synthetic */ void lambda$0(String string) { System.out.println("capturingLambda " + string); } }
نکته جالب این است که امضای تابع lambda$0 از بدون پارامتر به گرفتن یک پارامتر رشتهای تغییر کرده است.
منبع: