Недавно у меня было интервью, и одним из вопросов был этот загадочный экземпляр: «В чем главное преимущество системы типов Kotlin перед JavaЭ» Честно говоря, определить, какое преимущество считать главным, оказалось для меня неразрешимой задачей: Ничего, отсутствие Wildcard и First-Class Functions вместо Java-костыля с Functional Interface (имеется в виду 8-я версия Java) не помогло.
не занять первое место в личном топе интервьюера, который мне предложили угадать.
Оказалось, что главное в Котлине — это возможность объявить Nullable Type и подход Null Safety (замечу, что по моему опыту собственные или библиотекиOptional или Maybe решают эту проблему и пишутся за 10 минут на Java 7. Там также являются аннотациями, допускающими значение Null, которые позволяют проверять поля во время компиляции.
Короче говоря, существует множество способов принудительно проверять значения Null в простой Java. Ну да ладно).
Но речь идет не о странных вопросах, связанных со вкусовыми предпочтениями интервьюеров относительно синтаксического сахара.
Дело в том, что Null Safety в Kotlin можно взломать, не выходя из его безопасного купола в суровый дикий мир Java и Null-References. Как? Короче говоря: ClassLoader ведет себя интересным образом при попытке загрузить статические поля классов, которые рекурсивно ссылаются на классы друг друга.
Ниже приведены примеры кода и подробное объяснение того, как он обманывает проверки Nullable. Искренне надеюсь, что для статьи не потребуются специфические знания Java/Kotlin — все детали я объясню по ходу дела и сократим рассмотрение до 10 минут. Давай начнем.
Я не буду тянуть кота за хвост. Вот код:
Ни одной строчки Java-кода, ни одного предупреждения или NPE в результате выполнения.class ClassToLoad1() { val classToLoad2 = ClassToLoad1.classToLoad2 //Creating single instance of object companion object { val classToLoad2 = ClassToLoad2() } } class ClassToLoad2() { val classToLoad1 = ClassToLoad2.classToLoad1 companion object { val classToLoad1 = ClassToLoad1() } } fun main() { val check = ClassToLoad1() val classToLoadRecurciveRef = check.classToLoad2.classToLoad1.classToLoad2 println(classToLoadRecurciveRef) //null classToLoadRecurciveRef.classToLoad1 //Throws NPE }
Почему это? Роковое стечение нескольких обстоятельств: Рекурсивная ссылка, статическая инициализация и не самый явный контракт поведения ClassLoader в JVM в таких случаях.
//Рекурсивные ссылки
Давайте отрежем из нашего примера все, кроме рекурсивного обращения классов к самим себе.Базовый пример — один класс, который хочет получить себя в качестве экземпляра.
Зачем это вообще делать? Самый распространенный случай, когда вы видели такую структуру, — это связанный список (который, кстати, тоже популярен на собеседованиях).
Любая другая ссылочная структура данных, например узел в дереве, также будет ссылаться на себя.
И если вам действительно нравится писать графики, вы можете напрямую столкнуться с таким кодом.
class SelfClassLoad(val s: SelfClassLoad) // warning: Constructor has non-null self reference parameter
Ой, у нас предупреждение! И это существует по очень понятной причине — вы не можете использовать этот класс: class SelfClassLoad(val s: SelfClassLoad) // warning: Constructor has non-null self reference parameter
fun main() {
SelfClassLoad(
SelfClassLoad(
SelfClassLoad(
SelfClassLoad(
SelfClassLoad(
//No value passed for parameter 's'
)
)
)
)
)
}
Также было бы интересно немного изменить пример: class SelfClassLoad(){
val s = SelfClassLoad()
}
fun main() {
SelfClassLoad() //Stackoverflow error
}
По понятным причинам мы просто получим StackOverflow — бесконечная инициализация себя ни к чему хорошему не приводит. Но получим ли мы предупреждение, если попытаемся создать рекурсивную комбинацию двух классов? class SelfClassLoad1(val s2: SelfClassLoad2)
class SelfClassLoad2(val s1: SelfClassLoad1) //No warnings
В этом случае больше не выдается предупреждение.
Вопрос на эту тему уже создавался: https://youtrack.jetbrains.com/issue/KTIJ-14263 И это первый шаг к нашему НП?.
//Статическая инициализация
Что происходит в Котлине, когда вы создаете объект-компаньон? Если вы пытались вызвать аналогичный код из Java, то знаете, что ваш класс будет содержать статическую ссылку на сопутствующий объект. Если вы попытаетесь перевести Kotlin на Java, вы получите что-то вроде этого: public final class ClassToLoad1 {
@NotNull
private static final ClassToLoad2 classToLoad2;
@NotNull
public static final Companion Companion; //Here, it's "static"
@NotNull
public final ClassToLoad2 getClassToLoad2() {
return this.classToLoad2$1;
}
static {
Companion = new Companion(null);
classToLoad2 = new ClassToLoad2();
}
public static final class Companion {
@NotNull
public final ClassToLoad2 getClassToLoad2() {
return classToLoad2;
}
private Companion() {
}
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
Опытные Java-разработчики уже поняли, к чему это идет (если, конечно, не поняли с самого начала), но для широкой аудитории оставлю пояснение: Ключевое слово статический , делает именно то, что говорит — объект становится статическим.
А в мире JVM это означает, что этот объект находится в особой области памяти — Permanent Generation Space. Это означает 2 вещи: 1. Статический объект будет жить до тех пор, пока жива его JVM. 2. Он будет инициализирован (помещен в PermGen) 1 раз.
(Да-да, бывают случаи, когда это не так, в настоящее время это выходит за рамки) Для тех, кто помнит про синглтоны: Возможно, вы сейчас думаете что-то вроде: «Зачем тогда нужен сложный синглтон с Syncronized, если статики достаточноЭ» Ответ: синхронизация и другие методы необходимы, когда вам нужно лениво инициализировать синглтон.
class Singleton1{
public final static Singleton1 singleton = new Singleton1()
} //Warranty to be single time initialized, but will init on class init.
class Singleton2{
private static Singleton2 singleton;
public static void getSingleton(){
Теги: #java #Ненормальное программирование #Kotlin #jvm #отладка #Swift #компиляторы #bug #compiler #recursive #classloader
-
Ноутбуки Становятся Зелеными
19 Oct, 24 -
Тест На Способности
19 Oct, 24 -
Сми Упали
19 Oct, 24 -
Идея Стартапа: Пикабу. Ru
19 Oct, 24 -
Тринадцатое Исследование Экономики Развития
19 Oct, 24 -
Ibm Приобретает Wind River?
19 Oct, 24