
ربما يكون عرض كود Kotlin bytecode المتحلل في Java هو أفضل طريقة لفهم كيف لا يزال يعمل وكيف تؤثر بعض تركيبات اللغة على الأداء. قام الكثيرون بذلك من تلقاء أنفسهم منذ فترة طويلة ، لذلك ستكون هذه المقالة ذات صلة خاصة بالمبتدئين وأولئك الذين أتقنوا جافا منذ فترة طويلة وقرروا استخدام Kotlin مؤخرًا.
سأفتقد عن عمد لحظات مبتذلة ومعروفة تمامًا لأنه ، على الأرجح ، ليس من المنطقي أن تكون لك مائة مرة للكتابة عن الجيل من المستوطنين / المستوطنين لمختلف وأشياء مماثلة. لذلك دعونا نبدأ.
كيفية عرض رمز البايت المترجم في Intellij Idea؟
بسيط للغاية - فقط افتح الملف المطلوب وحدد في القائمة أدوات -> Kotlin -> إظهار Kotlin Bytecode

بعد ذلك ، في النافذة التي تظهر ، ما عليك سوى النقر فوق Decompile

للعرض ، سيتم استخدام إصدار Kotlin 1.3-RC.
الآن ، أخيرًا ، دعنا ننتقل إلى الجزء الرئيسي.
الكائن
Kotlin
object Test
جافا مترجمة
public final class Test { public static final Test INSTANCE; static { Test var0 = new Test(); INSTANCE = var0; } }
أفترض أن كل من يتعامل مع Kotlin يعرف أن الكائن يخلق مفردًا. ومع ذلك ، فإنه ليس من الواضح للجميع بالضبط أي سينجلتون يتم إنشاؤه وما إذا كان خيط آمن.
يُظهر الكود الذي تم فك تجميعه أن المفرد المستلم مشابه للتنفيذ المتلهف للمفرد ، يتم إنشاؤه في الوقت الذي يقوم فيه محمل الفصل بتحميل الصف. من ناحية ، يتم تنفيذ كتلة ثابتة عندما يتم تحميلها بواسطة برنامج تشغيل فئة ، وهو في حد ذاته آمن للخيط. من ناحية أخرى ، إذا كان هناك أكثر من سائق واحد في الفصل الدراسي ، فلا يمكنك النزول بنسخة واحدة.
ملحقات
Kotlin
fun String.getEmpty(): String { return "" }
جافا مترجمة
public final class TestKt { @NotNull public static final String getEmpty(@NotNull String $receiver) { Intrinsics.checkParameterIsNotNull($receiver, "receiver$0"); return ""; } }
هنا ، بشكل عام ، كل شيء واضح - الامتدادات هي مجرد سكر نحوي ويتم تجميعها في طريقة ثابتة عادية.
إذا تم الخلط بين شخص ما بواسطة السطر مع Intrinsics.checkParameterIsNotNull ، فإن كل شيء يكون شفافًا هناك - في جميع الوظائف ذات الوسيطات غير القابلة للإلغاء ، يضيف Kotlin فحصًا فارغًا ويطرح استثناءًا إذا انزلق
خنزير فارغًا ، على الرغم من أنه في الوسيطات التي وعدت بعدم القيام بها. يبدو هذا:
public static void checkParameterIsNotNull(Object value, String paramName) { if (value == null) { throwParameterIsNullException(paramName); } }
ما هو النموذجي إذا لم تكتب دالة ولكن خاصية امتداد
val String.empty: String get() { return "" }
بعد ذلك ، ونتيجة لذلك ، نحصل على نفس الشيء تمامًا الذي حصلنا عليه لطريقة String.getEmpty ()
مضمنة
Kotlin
inline fun something() { println("hello") } class Test { fun test() { something() } }
جافا مترجمة
public final class Test { public final void test() { String var1 = "hello"; System.out.println(var1); } } public final class TestKt { public static final void something() { String var1 = "hello"; System.out.println(var1); } }
مع كل شيء مضمن ، كل شيء بسيط للغاية - يتم ببساطة إدراج وظيفة مميزة على أنها مضمنة تمامًا وبالكامل في المكان الذي تم استدعاؤها فيه. ومن المثير للاهتمام ، أنه يقوم أيضًا بتجميع نفسه في إحصاءات ، ربما للتوافق مع Java.
يتم الكشف عن كل قوة المضمنة في اللحظة التي تظهر فيها
لامدا في الحجج:
Kotlin
inline fun something(action: () -> Unit) { action() println("world") } class Test { fun test() { something { println("hello") } } }
جافا مترجمة
public final class Test { public final void test() { String var1 = "hello"; System.out.println(var1); var1 = "world"; System.out.println(var1); } } public final class TestKt { public static final void something(@NotNull Function0 action) { Intrinsics.checkParameterIsNotNull(action, "action"); action.invoke(); String var2 = "world"; System.out.println(var2); } }
في الجزء السفلي ، تظهر الإحصائيات مرة أخرى ، وفي الجزء العلوي من الواضح أن لامدا في الوسيطة الدالة مبطنة أيضًا ، ولا تنشئ فئة إضافية مجهولة الهوية ، كما هو الحال مع لامدا المعتادة في كوتلين.
حول هذا ، ينتهي العديد من معرفة Kotlin المضمنة ، ولكن هناك نقطتان أكثر إثارة للاهتمام ، وهما noinline و crossinline. هذه هي الكلمات الرئيسية التي يمكن تعيينها لامدا وهي وسيطة في دالة مضمنة.
Kotlin
inline fun something(noinline action: () -> Unit) { action() println("world") } class Test { fun test() { something { println("hello") } } }
جافا مترجمة
public final class Test { public final void test() { Function0 action$iv = (Function0)null.INSTANCE; action$iv.invoke(); String var2 = "world"; System.out.println(var2); } } public final class TestKt { public static final void something(@NotNull Function0 action) { Intrinsics.checkParameterIsNotNull(action, "action"); action.invoke(); String var2 = "world"; System.out.println(var2); } }
بمثل هذا السجل ، يبدأ IDE في الإشارة إلى أن مثل هذا المضمّن عديم الفائدة أقل قليلاً من تمامًا. وهي تجمع بالضبط نفس Java - تنشئ Function0. لماذا فك مع (غريبة 0) null.INSTANCE؛ - ليس لدي أي فكرة ، على الأرجح أن هذا خطأ في جهاز فك الشفرة.
بدورها ، يقوم crossinline بدوره بنفس الشيء تمامًا مثل السطر العادي (أي ، إذا لم يتم كتابة أي شيء قبل lambda في الوسيطة) ، مع استثناءات قليلة ، لا يمكن كتابة العودة في lambda ، وهو أمر ضروري لمنع القدرة على إنهاء الوظيفة التي تستدعيها بشكل مفاجئ. بالمعنى ، يمكنك كتابة شيء ما ، ولكن أولاً ، سيقسم IDE ، وثانيًا ، عند الترجمة ، نحصل على
"العودة" غير مسموح بها هنا
ومع ذلك ، لا يختلف الرمز البيني للخط المتقاطع عن السطر الافتراضي - يتم استخدام الكلمة الرئيسية من قبل المترجم فقط.
انفيكس
Kotlin
infix fun Int.plus(value: Int): Int { return this+value } class Test { fun test() { val result = 5 plus 3 } }
جافا مترجمة
public final class Test { public final void test() { int result = TestKt.plus(5, 3); } } public final class TestKt { public static final int plus(int $receiver, int value) { return $receiver + value; } }
يتم تجميع وظائف Infix مثل ملحقات الإحصائيات العادية
الذيل
Kotlin
tailrec fun factorial(step:Int, value: Int = 1):Int { val newValue = step*value return if (step == 1) newValue else factorial(step - 1,newValue) }
جافا مترجمة
public final class TestKt { public static final int factorial(int step, int value) { while(true) { int newValue = step * value; if (step == 1) { return newValue; } int var10000 = step - 1; value = newValue; step = var10000; } }
tailrec هو شيء مسل. كما ترى من الكود ، فإن العودية تذهب ببساطة إلى دورة أقل قابلية للقراءة ، ولكن المطور يمكن أن ينام بسلام ، حيث لا شيء سيطير من Stackoverflow في أكثر اللحظات غير السارة. شيء آخر في الحياة الواقعية نادرا ما يجد الذيل.
مكرر
Kotlin
inline fun <reified T>something(value: Class<T>) { println(value.simpleName) }
جافا مترجمة
public final class TestKt { private static final void something(Class value) { String var2 = value.getSimpleName(); System.out.println(var2); } }
بشكل عام ، حول مفهوم reified نفسه وسبب ضرورته ، يمكنك كتابة مقال كامل. باختصار ، الوصول إلى النوع نفسه في Java غير ممكن في وقت الترجمة ، لأنه قبل تجميع جافا ، لا أحد يعرف ما سيكون هناك على الإطلاق. Kotlin مسألة أخرى. يمكن استخدام الكلمة الأساسية المضبوطة فقط في الوظائف المضمنة ، والتي ، كما سبق ذكره ، يتم ببساطة نسخها ولصقها في الأماكن الصحيحة ، وبالتالي بالفعل أثناء "استدعاء" الوظيفة ، يكون المترجم على دراية بنوعها ويمكنه تعديل الرمز الثانوي.
يجب الانتباه إلى حقيقة أن وظيفة ثابتة ذات مستوى وصول
خاص يتم تجميعها في رمز البايت ، مما يعني أن هذا لن يعمل خارج Java. بالمناسبة ، بسبب إعلان Kotlin
"100٪ قابل للتشغيل المتبادل مع Java و Android" ، يتم الحصول على عدم الدقة على الأقل.

ربما بعد كل 99٪؟
الحرف الأول
Kotlin
class Test { constructor() constructor(value: String) init { println("hello") } }
جافا مترجمة
public final class Test { public Test() { String var1 = "hello"; System.out.println(var1); } public Test(@NotNull String value) { Intrinsics.checkParameterIsNotNull(value, "value"); super(); String var2 = "hello"; System.out.println(var2); } }
بشكل عام ، مع init ، كل شيء بسيط - هذه وظيفة عادية مضمنة ، والتي تعمل
قبل استدعاء رمز المُنشئ نفسه.
فئة البيانات
Kotlin
data class Test(val argumentValue: String, val argumentValue2: String) { var innerValue: Int = 0 }
جافا مترجمة
public final class Test { private int innerValue; @NotNull private final String argumentValue; @NotNull private final String argumentValue2; public final int getInnerValue() { return this.innerValue; } public final void setInnerValue(int var1) { this.innerValue = var1; } @NotNull public final String getArgumentValue() { return this.argumentValue; } @NotNull public final String getArgumentValue2() { return this.argumentValue2; } public Test(@NotNull String argumentValue, @NotNull String argumentValue2) { Intrinsics.checkParameterIsNotNull(argumentValue, "argumentValue"); Intrinsics.checkParameterIsNotNull(argumentValue2, "argumentValue2"); super(); this.argumentValue = argumentValue; this.argumentValue2 = argumentValue2; } @NotNull public final String component1() { return this.argumentValue; } @NotNull public final String component2() { return this.argumentValue2; } @NotNull public final Test copy(@NotNull String argumentValue, @NotNull String argumentValue2) { Intrinsics.checkParameterIsNotNull(argumentValue, "argumentValue"); Intrinsics.checkParameterIsNotNull(argumentValue2, "argumentValue2"); return new Test(argumentValue, argumentValue2); }
بصراحة ، لم أكن أرغب في ذكر فصول التاريخ ، التي قيل عنها الكثير بالفعل ، ولكن مع ذلك هناك بضع نقاط تستحق الاهتمام. بادئ ذي بدء ، تجدر الإشارة إلى أن المتغيرات التي تم تمريرها إلى المنشئ فقط تدخل في يساوي / hashCode / copy / toString. بالنسبة للسؤال عن سبب ذلك ، أجاب أندريه بريسلاف أن أخذ الحقول التي لم يتم نقلها في المنشئ أمر صعب وحاد أيضًا. بالمناسبة ، من المستحيل أن ترث من تاريخ الفصل ، والحقيقة هي فقط لأنه
أثناء الإرث لن يكون الرمز الذي تم إنشاؤه صحيحًا . ثانيًا ، يجدر الإشارة إلى طريقة element1 () للحصول على قيمة الحقل. يتم إنشاء العديد من طرق ComponN () نظرًا لوجود وسائط في المُنشئ. يبدو عديم الفائدة ، ولكنك حقًا تحتاجه
لإعلان مدمر .
إعلان مدمر
على سبيل المثال ، سنستخدم فئة التاريخ من المثال السابق ونضيف الكود التالي:
Kotlin
class DestructuringDeclaration { fun test() { val (one, two) = Test("hello", "world") } }
جافا مترجمة
public final class DestructuringDeclaration { public final void test() { Test var3 = new Test("hello", "world"); String var1 = var3.component1(); String two = var3.component2(); } }
عادة ما تقوم هذه الميزة بجمع الغبار على الرف ، ولكن في بعض الأحيان يمكن أن تكون مفيدة ، على سبيل المثال ، عند العمل مع محتويات الخريطة.
عامل
Kotlin
class Something(var likes: Int = 0) { operator fun inc() = Something(likes+1) } class Test() { fun test() { var something = Something() something++ } }
جافا مترجمة
public final class Something { private int likes; @NotNull public final Something inc() { return new Something(this.likes + 1); } public final int getLikes() { return this.likes; } public final void setLikes(int var1) { this.likes = var1; } public Something(int likes) { this.likes = likes; }
هناك حاجة إلى الكلمة الأساسية عامل التشغيل لتجاوز بعض عامل اللغة لفئة معينة. بصراحة ، لم أر قط أي شخص يستخدم هذا ، ولكن مع ذلك هناك مثل هذه الفرصة ، ولكن لا يوجد سحر في الداخل. في الواقع ، يقوم المترجم ببساطة باستبدال عامل التشغيل بالوظيفة المرغوبة ، مثلما يتم استبدال أنماط الكتابة بنوع معين.
ونعم ، إذا فكرت الآن في ما سيحدث إذا قمت بإعادة تعريف عامل الهوية (=== الذي) ، فأسرع إلى إزعاجك ، فهذا عامل لا يمكن إعادة تعريفه.
فئة مضمنة
Kotlin
inline class User(internal val name: String) { fun upperCase(): String { return name.toUpperCase() } } class Test { fun test() { val user = User("Some1") println(user.upperCase()) } }
جافا مترجمة
public final class Test { public final void test() { String user = User.constructor-impl("Some1"); String var2 = User.upperCase-impl(user); System.out.println(var2); } } public final class User { @NotNull private final String name;
من القيود - يمكنك استخدام وسيطة واحدة فقط في المُنشئ ، ومع ذلك ، فمن المفهوم ، بالنظر إلى أن الفئة المضمنة تكون بشكل عام مجمعة على أي متغير واحد. قد يحتوي الفصل المضمن على طرق ، لكنها ثابتة فقط. من الواضح أيضًا أنه تم إضافة جميع الطرق الضرورية لدعم تشغيل Java.
الملخص
لا تنس أولاً أنه لن يتم دائمًا فك الشفرة بشكل صحيح ، وثانيًا ، لا يمكن فك شفرة كل كود. ومع ذلك ، فإن القدرة على مشاهدة كود Kotlin المتحلل في حد ذاته أمر مثير للاهتمام للغاية ويمكن أن يوضح الكثير.