Change from java to android

Как Java 8 поддерживается в Android

Привет, Хабр! Предлагаю вашему вниманию перевод замечательной статьи из цикла статей небезызвестного Джейка Вортона о том, как происходит поддержка Андроидом Java 8.

Оригинал статьи лежит тут

Несколько лет я работал из дома, и мне часто приходилось слышать, как мои коллеги жалуются на поддержку Андроидом разных версий Java.

Это довольно сложная тема. Для начала нужно определиться, что мы вообще подразумеваем под «поддержкой Java в Android», ведь в одной версии языка может быть много всего: фичи (лямбды, например), байткод, тулзы, APIs, JVM и так далее.

Когда говорят о поддержке Java 8 в Android, обычно подразумевают поддержку фичей языка. Итак, начнем с них.

Лямбды

Одним из главных нововведений Java 8 были лямбды.
Код стал более лаконичным и простым, лямбды избавили нас от необходимости писать громоздкие анонимные классы, используя интерфейс с единственным методом внутри.

После компиляции этого, используя javac и легаси dx tool , мы получим следующую ошибку:

Эта ошибка происходит из-за того, что лямбды используют новую инструкцию в байткоде — invokedynamic , которая была добавлена в Java 7. Из текста ошибки можно увидеть, что Android поддерживает ее только начиная с 26 API (Android 8).

Звучит не очень, ведь вряд ли кто-то будет выпускать приложение с 26 minApi. Чтобы это обойти, используется так называемый процесс десахаризации (desugaring), который делает возможным поддержку лямбд на всех версиях API.

История десахаризации

Она довольно красочна в мире Android. Цель десахаризации всегда одна и та же — позволить новым языковым фичам работать на всех устройствах.

Изначально, например, для поддержки лямбд в Android разработчики подключали плагин Retrolambda. Он использовал тот же встроенный механизм, что и JVM, конвертируя лямбды в классы, но делал это в рантайме, а не во время компиляции. Сгенерированные классы были очень дорогими с точки зрения количества методов, но со временем, после доработок и улучшений, этот показатель снизился до чего-то более-менее разумного.

Затем команда Android анонсировала новый компилятор, который поддерживал все фичи Java 8 и был более производительным. Он был построен поверх Eclipse Java компилятора, но вместо генерации Java-байткода генерировал Dalvik-байткод. Однако его производительность все равно оставляла желать лучшего.

Когда новый компилятор (к счастью) забросили, трансформатор Java байткода в Java байткод, который и выполнял дешугаринг, был интегрирован в Android Gradle Plugin из Bazel — системы сборки Google. И его производительность все равно была невелика, поэтому параллельно продолжался поиск более хорошего решения.

И вот нам представили новый dexer — D8, который должен был заменить dx tool . Десахаризация теперь выполнялась во время конвертации скомпилированных JAR-файлов в .dex (dexing). D8 сильно выигрывает в производительности по сравнению с dx , и, начиная с Android Gradle Plugin 3.1 он стал dexer’ом по умолчанию.

Теперь, используя D8, у нас получится скомпилировать приведенный выше код.

Чтобы посмотреть, как D8 преобразовал лямбду, можно использовать dexdump tool , который входит в Android SDK. Она выведет довольно много всего, но мы заострим внимание только на этом:

Если вы до этого еще не читали байткод, не волнуйтесь: многое из того, что здесь написано, можно понять интуитивно.

В первом блоке наш main метод с индексом 0000 получает ссылку от поля INSTANCE на класс Java8$1 . Этот класс был сгенерирован во время десахаризации . Байткод метода main тоже нигде не содержит упоминаний о теле нашей лямбды, поэтому, скорее всего, она связана с классом Java8$1 . Индекс 0002 затем вызывает static-метод sayHi , используя ссылку на INSTANCE . Методу sayHi требуется Java8$Logger , поэтому, похоже, Java8$1 имплементирует этот интерфейс. Мы можем убедиться в этом тут:

Флаг SYNTHETIC означает, что класс Java8$1 был сгенерирован и список интерфейсов, которые он включает, содержит Java8$Logger .
Этот класс и представляет собой нашу лямбду. Если вы посмотрите на реализацию метода log , то не увидите тело лямбды.

Вместо этого внутри вызывается static метод класса Java8 — lambda$main$0 . Повторюсь, этот метод представлен только в байткоде.

Флаг SYNTHETIC снова говорит нам, что этот метод был сгенерирован, и его байткод как раз содержит тело лямбды: вызов System.out.println . Причина, по которой тело лямбды находится внутри Java8.class, простая — ей может понадобиться доступ к private членам класса, к которым сгенерированный класс иметь доступа не будет.

Все, что нужно для понимания того, как работает десахаризация, описано выше. Однако, взглянув на это в байткоде Dalvik, можно увидеть, что там все намного более сложно и пугающе.

Преобразование исходников — Source Transformation

Чтобы лучше понимать, как происходит десахаризация, давайте попробуем шаг за шагом преобразовывать наш класс в то, что будет работать на всех версиях API.

Возьмем за основу тот же класс с лямбдой:

Сначала тело лямбды перемещается в package private метод.

Затем генерируется класс, имплементирующий интерфейс Logger , внутри которого выполняется блок кода из тела лямбды.

Далее создается синглтон инстанс Java8$1 , который хранится в static переменной INSTANCE .

Вот итоговый задешугаренный класс, который может использоваться на всех версиях API:

Если вы посмотрите на сгенерированный класс в байткоде Dalvik, то не найдете имен по типу Java8$1 — там будет что-то вроде -$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY . Причина, по которой для класса генерируется такой нейминг, и в чем его плюсы, тянет на отдельную статью.

Нативная поддержка лямбд

Когда мы использовали dx tool , чтобы скомпилировать класс, содержащий лямбды, сообщение об ошибке говорило, что это будет работать только с 26 API.

Поэтому кажется логичным, что если мы попробуем скомпилировать это с флагом —min-api 26 , то десахаризации происходить не будет.

Однако если мы сдампим .dex файл, то в нем все равно можно будет обнаружить -$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY . Почему так? Это баг D8?

Чтобы ответить на этот вопрос, а также почему десахаризация происходит всегда, нам нужно заглянуть внутрь Java-байткода класса Java8 .

Внутри метода main мы снова видим invokedynamic по индексу 0 . Второй аргумент в вызове — 0 — индекс ассоциируемого с ним bootstrap метода.

Вот список bootstrap методов:

Здесь bootstrap метод назван metafactory в классе java.lang.invoke.LambdaMetafactory . Он живет в JDK и занимается созданием анонимных классов налету (on-the-fly) в рантайме для лямбд так же, как и D8 генерит их в компайлтайме.

Если взглянуть на документацию Android к java.lang.invoke
или на исходники AOSP к java.lang.invoke , увидим, что в рантайме этого класса нет. Вот поэтому дешугаринг всегда происходит во время компиляции, независимо от того, какой у вас minApi. VM поддерживает байткод инструкцию, похожую на invokedynamic , но встроенный в JDK LambdaMetafactory недоступен для использования.

Читайте также:  Обновление gt n8000 до андроид 6

Method References

Вместе с лямбдами в Java 8 добавили ссылки на методы — это эффективный способ создать лямбду, тело которой ссылается на уже существующий метод.

Наш интерфейс Logger как раз является таким примером. Тело лямбды ссылалось на System.out.println . Давайте превратим лямбду в метод референc:

Когда мы это скомпилируем и взглянем на байткод, то увидим одно различие с предыдущей версией:

Вместо вызова сгенерированного Java8.lambda$main$0 , который содержит вызов System.out.println , теперь System.out.println вызывается напрямую.

Класс с лямбдой больше не static синглтон, а по индексу 0000 в байткоде видно, что мы получаем ссылку на PrintStream — System.out , который затем используется для того, чтобы вызвать на нем println .

В итоге наш класс превратился в это:

Default и static методы в интерфейсах

Еще одним важным и серьезным изменением, которое принесла Java 8, стала возможность объявлять default и static методы в интерфейсах.

Все это тоже поддерживается D8. Используя те же инструменты, что и ранее, несложно увидеть задешугаренную версию Logger’a с default и static методами. Одно из различий с лямбдами и method references в том, что дефолтные и статик методы реализованы в Android VM и, начиная с 24 API, D8 не будет дешугарить их.

Может, просто использовать Kotlin?

Читая статью, большинство из вас, наверное, подумали о Kotlin. Да, он поддерживает все фичи Java 8, но реализованы они kotlinc точно так же, как и D8, за исключением некоторых деталей.

Поэтому поддержка Андроидом новых версий Java до сих пор очень важна, даже если ваш проект на 100% написан на Kotlin.

Не исключено, что в будущем Kotlin перестанет поддерживать байткод Java 6 и Java 7. IntelliJ IDEA, Gradle 5.0 перешли на Java 8. Количество платформ, работающих на более старых JVM, сокращается.

Desugaring APIs

Все это время я рассказывал про фичи Java 8, но ничего не говорил о новых API — стримы, CompletableFuture , date/time и так далее.

Возвращаясь к примеру с Logger’ом, мы можем использовать новый API даты/времени, чтобы узнать, когда сообщения были отправлены.

Снова компилируем это с помощью javac и преобразуем его в байткод Dalvik с D8, который дешугарит его для поддержки на всех версиях API.

Можете даже запушить это на свой девайс, чтобы убедиться, что оно работает.

Если на этом устройстве API 26 и выше, появится месседж Hello. Если нет — увидим следующее:

D8 справился с лямбдами, метод референсами, но не сделал ничего для работы с LocalDateTime , и это очень печально.

Разработчикам приходится использовать свои собственные реализации или обертки над date/time api, либо использовать библиотеки по типу ThreeTenBP для работы со временем, но почему то, что ты можешь написать руками, не может сделать D8?

Эпилог

Отсутствие поддержки всех новых API Java 8 остается большой проблемой в экосистеме Android. Ведь вряд ли каждый из нас может позволить указать 26 min API в своем проекте. Библиотеки, поддерживающие и Android и JVM, не могут позволить себе использовать API, представленный нам 5 лет назад!

И даже несмотря на то, что саппорт Java 8 теперь является частью D8, каждый разработчик все равно должен явно указывать source и target compatibility на Java 8. Если вы пишете собственные библиотеки, то можете усилить эту тенденцию, выкладывая библиотеки, которые используют Java 8 байткод (даже если вы не используете новые фичи языка).

Над D8 ведется очень много работ, поэтому, кажется, в будущем с поддержкой фичей языка все будет ок. Даже если вы пишете только на Kotlin, очень важно заставлять команду разработки Android поддерживать все новые версии Java, улучшать байткод и новые API.

Этот пост — письменная версия моего выступления Digging into D8 and R8.

Источник

Нюансы перехода на Kotlin, или Руководство для Android-разработчика по предательству Java

Авторизуйтесь

Нюансы перехода на Kotlin, или Руководство для Android-разработчика по предательству Java

Android-инженер Константин Михайловский рассказал на dou.ua о своем опыте перехода с Java на язык программирования Kotlin в Android-проекте

Итак, на дворе 2018-й год. Если вы — Android-инженер и уже успели полностью или даже отчасти «пересесть» на язык программирования Kotlin, если полагаться на актуальные рекомендации Google и не беспочвенные восторги многих разработчиков от опыта использования языка, которые я и сам разделяю, вы на правильном пути. С большой вероятностью вы успели попасться на большинство «ошибок новичка», описанных в этой статье (и, что немаловажно, докопаться до причины их возникновения).

Однако, если вы ещё лишь задумываетесь о переходе на Kotlin — будь это в рамках разработки новых фич вашего проекта на Java и написания первых тестов к нему, либо разработка нового проекта с чистого листа — сейчас всё ещё прекрасное время для этого. Советы из статьи будут вам полезны на определённых этапах такого переходного периода.

Вероятно, самый закономерный вопрос, который первым делом придет вам в голову в процессе предательства Java в пользу Kotlin, — «с чего же начать?».

Открываем двери для Kotlin

В то время как на просторах интернета, в том числе в официальной документации языка и на платформе для разработчиков, можно найти доступные руководства по созданию Kotlin-проекта и/или настройки Kotlin в Android Studio и Gradle, в этом разделе я сфокусируюсь лишь на первых потенциальных ловушках на вашем пути предательства Джавы.

Sportmaster Lab , Санкт-Петербург, Москва, Новосибирск, можно удалённо , От 100 000 до 400 000 ₽

Если в своём существующем проекте вы используете annotation processing или библиотеки, которые на нём базируются (в большинстве случаев это DI-фреймворки вроде Dagger 2 и других, Data Binding, Butterknife), без предварительных правок в build.gradle файлах на уровне ваших модулей, они просто перестанут собираться без подключённого плагина kapt (собственный annotation processor у Kotlin):

Также чрезвычайно важный шаг — замена всех вхождений annotationProcessor конфигурации в вашем build.gradle на kapt .

Генерация стабов в данный момент поддерживается из-под коробки.

Подготовка сознания к Null Safety

Важное отличие Kotlin от Java — поддержка первым nullable-типов null-safety «из-под коробки». Поэтому не поленитесь начать знакомство с языком с раздела официальной документации, который довольно исчерпывающе раскрывает основные аспекты этого мощного механизма и работы с ним (в том числе при двухстороннем взаимодействии с Java-кодом).

Первое свидание с Kotlin: data classes

До того как Google объявил, что поддерживает Kotlin на официальном уровне, довольно часто можно было встретить среди рекомендаций — начать процесс перехода с написания unit-тестов на этом языке. И пусть этот совет имеет огромный смысл, ведь unit-тесты — действительно наименее агрессивный путь экспансии Kotlin на кодовую базу и при этом свободный от рисков наткнуться на странные ошибки, связанные с interop двух языков. Однако вряд ли вы испытаете катарсис, который способен принести «сладкий» и лаконичный синтаксис языка, подталкивающий к тем же приемам функционального программирования (unit-тесты в большинстве случаев — исключительно императивный стиль). Попробуйте параллельно начать делать вкрапления языка с написания POJO с помощью data-классов, о которых вы наверняка, даже не будучи знакомым с языком на практике, могли слышать раньше.

Предположим, вам нужно написать класс сущности для фильма Movie. Одной строчки кода будет достаточно!

Читайте также:  Как завести аккаунт для андроид

Помимо лаконичности здесь вы получаете:

— Переопределённые методы equals() , hashCode() и toString() под капотом;

— Immutable класс, неявно наследующийся от Any (в отличие от Object в Java) с immutable-полями (но это не точно) и неявными публичными геттерами и сеттерами для каждого. Создание экземпляра такого класса будет выглядеть так (обратите внимание на отсутствие ключевого слова new ):

— Метод copy() , который позволяет клонировать экземпляр данного класса и может быть полезен в том случае, если вы, например, пожелаете создать новый неизменяемый объект на основе существующего, но с отличающимися значениями одного или нескольких полей (при условии, что они не private). Такой подход будет для вас первым шагом навстречу функциональному стилю:

Предупреждение № 1 При создании экземпляра такого класса на Java с помощью copy() вам придётся определить значения для каждого из полей.

Предупреждение № 2 На всякий случай предупрежу, что этот метод недоступен для экземпляров обычных (не data) классов.

— Поддержка значений по умолчанию, которой можно заменить использование Builder-паттерна.

Имейте в виду, что data-классы пока что не могут наследоваться друг от друга. Однако вы можете счесть полезными sealed-классы, неявно абстрактные. Например, в тех случаях, когда вам необходимо определить различные состояния загрузки данных или экрана. Или в любых других ситуациях, где ограниченные иерархии классов будут уместными.

Data-классы + Parcelable

Начиная с версии 1.1.4, больше нет необходимости писать boilerplate-реализации методов parcelable для поддержки де/сериализации ваших объектов, так как за вас это сделает аннотация @Parcelize.

Только не забудьте применить Android Extensions plugin:

И определить значение experimental-флага как true .

Экспериментальный статус расширения (к моменту написания этого материала) указывает на тот факт, что перед вами всё ещё не окончательный вариант этого API. Есть вероятность глубоко зарытых багов в его работе (пока что я с таковыми не сталкивался, но возможно всё). И обновление API в будущем потенциально способно «поломать» ваш код, и вам стоит использовать эту аннотацию в продакшн-коде на свой страх и риск.

Data-классы в сочетании с часто используемыми библиотеками

Если вы используете замечательную Room Persistence Library, вы всё так же при подключённом kapt можете писать data-классы для сущностей вашей базы данных, которые прекрасно работают с Room-аннотациями.

С Nullable-свойствами также нет никаких проблем. Но на тот случай, если вам вдруг станет любопытно так же, как нашей команде в своё время, поддерживает ли Room значения по умолчанию Kotlin, вас ждёт разочарование.

А что насчёт библиотек для сериализации данных?

В случае с одним из самых популярных в этой области GSON, необходимости в дополнительных конвертерах и плагинах нет. Библиотека будет сериализовать ваши POJO с таким же успехом, как и их Java-версии:

Однако так же, как и в случае с Room, значения по умолчанию не поддерживаются.

Для совместимости Jackson c Kotlin вам необходимо добавить зависимость специального модуля.

Аналогичным образом дела обстоят и с Moshi, для совместимости с которым потребуется добавить зависимость:

Сводим Kotlin с Java: Interoperability

Предположим, теперь вы готовы зайти дальше уровня data-классов и начать писать на Kotlin более сложные классы для ваших активити, фрагментов, presenter-, view model-, interactor-, repository- и других классов в зависимости от вашей архитектуры.

Разумеется, с некоторой вероятностью в перспективе вы планируете избавиться от Джавы в вашем проекте чуть более, чем полностью. Но перед этим вам придётся пройти длинный путь устранения конфликтов между языками, которые могут разрушить вашу жизнь (по крайней мере на пару часов).

Так как Kotlin был изначально спроектирован как JVM-язык, полностью совместимый с Java и наоборот, вы без труда можете наследоваться от существующих Java-классов, обращаться к ним и применять Java-аннотации к вашим Kotlin-классам и методам.

Существует несколько НО, образовавшихся под влиянием идеологических различий между двумя языками (о ключевых вы можете прочитать здесь).

Going Static

Например, в Kotlin нет ключевого слова static , просто смиритесь с этим.

Но не стоит отчаиваться, ведь в вашем распоряжении есть companion object . Это механизм объявления объекта внутри класса, способного содержать внутри константы и методы, синтаксически доступные со стороны Java в таком же виде, как и статические поля или методы Java. Но для того, чтобы при компиляции сгенерировались и статический метод класса, в котором находится этот объект, и метод этого объекта сам по себе, пометьте его как @JvmStatic (по умолчанию без этой аннотации метод companion-объекта будет вам доступен с помощью ссылки на Companion инстанс). Эта страница документации подробно раскрывает тему companion objects, создания синглтонов и object expressions в Kotlin.

Но прежде чем вы заключите все ваши константы в companion object-ы по всему проекту, возьмите во внимание тот факт, что такие объекты не так дешевы, как кажутся. Очень рекомендую всем, кто переходит на Kotlin, ознакомиться с циклом статей «Kotlin’s hidden costs» (part 1, part 2, part 3), открывающих обратную сторону (с точки зрения байткода) синтаксического сахара языка. Рекомендую обратить особое внимание на советы об использовании inline-функций для оптимизации производительности ваших лямбда-выражений, а также на советы по избежанию избыточной автоупаковки / автораспаковки «под капотом».

Коллекции

Когда речь идёт о коллекциях, Kotlin полностью полагается на классы стандартной библиотеки Java, расширяя их возможности с помощью дополнительных функций для их объявления ( listOf() , mapOf() , etc) и их модификаций и преобразований. Они довольно часто оказываются полезными и удобными в использовании, и с коллекциями как таковыми в Kotlin в целом всё довольно прозрачно. Ну… почти 😉 Обратите внимание на то, что List, Map и Set — это алиасы для неизменяемых JDK-реализаций коллекций, и попытки изменения их содержимого выльются в UnsupportedOperationException, что логично.

Generics

Обобщения в Kotlin тоже поддерживаются, но существуют важные различия в их реализации между двумя языками, пренебрежение которыми может привести вас к непредвиденным конфузам. Эта разница тщательно описана в документации и множестве тематических статей, поэтому вместо дублирования фактов просто оставлю ссылки на самые, на мой взгляд, ценные статьи, раскрывающие ключевые для дженериков в Kotlin концепции ковариантности, контравариантности и инвариантности:

Возвращаемся к unit-тестам

Если вы работаете по TDD/BDD или хотя бы пишете unit-тесты к уже готовой бизнес-логике вашего приложения (ремарка: всегда, при малейшей возможности, покрывайте ваш код тестами), высока вероятность, что вы будете использовать для этих нужд Mockito.

Однако первым камнем преткновения при написании unit-тестов к Kotlin-классам может оказаться отсутствие поддержки со стороны Mockito из-под коробки создания моков к final-классам. В Kotlin классы всегда final по умолчанию, до тех пор, пока вы явно не обозначите их как open. Согласно «Effective Java», 3rd Edition, Item 19: Design and document for inheritance or else prohibit it, это вполне резонное решение. В данной ситуации вы можете поступить так:

  • либо открыть тестируемый класс для наследования с помощью упомянутого модификатора open ;
  • либо применить небольшой хак к Mockito, создав файл с названием org.mockito.plugins.MockMaker , обязательно в папке test/resources/mockito-extensions . Внутри он должен содержать строку: mock-maker-inline .

Она подключит плагин, который будет генерировать моки для final-классов, что позволит вам писать к ним жизнеспособные тесты.

Также от себя хочу порекомендовать библиотеку Ника Хаармана mockito-kotlin, которая предоставляет множество полезных вспомогательных функций. Они способны с помощью возможностей Котлина подсластить инициализацию моков, верификацию обращений к ним, etc, с помощью Mockito.

Читайте также:  Звук аськи для андроида

А например, вспомогательные inline-методы, возможностями которых воспользовался автор библиотеки, позволяют определять поведение мока сразу же при его инициализации. Например, можем сразу же задать возвращаемое значение для метода получения фильмов мок-объекта MoviesRepository:

Kotlin Android Extensions

Написание любого класса для Activity мы чаще всего начинаем с определения layout-а внутри onCreate метода. Если вы используете базирующиеся на annotation-processing альтернативы для получения вьюх из xml-файла классическому findViewById -подходу или используете его же, посмотрите в сторону плагина Kotlin Android Extensions (не путайте с Android KTX 🙂 ), в который вы можете с некоторой вероятностью влюбиться с первых же строк кода.

С помощью него вы сможете ссылаться на вьюхи по их идентификаторам, указанным в xml, напрямую, так как доступные синтетические свойства для них уже сгенерированы.

Больше об этом плагине и его подключении можно прочитать, перейдя по этой ссылке.

Примечание И сразу остановлюсь на опасном моменте при обращении к синтетическим свойствам, определяющим идентификаторы вьюх, с которым столкнулась вся наша команда. Если вы используете include-блоки в ваших xml-layouts, убедитесь, что каждый из таких блоков НЕ переопределяет идентификатор корневого layout во включённом layout-файле.

Например, при такой конфигурации вас ждёт неминуемый креш:

В данном случае исходный id включенного TextView (randomTextView) переопределён и таким образом не может быть найден. Оптимальным решением будет вообще при такой возможности оставить саму include-область без идентификатора, чтобы избежать конфузов.

Kotlin и Data Binding

Если вы используете в проекте DataBinding и не имеете возможности быстро мигрировать на Kotlin Android Extensions, вы можете обнаружить множество ошибок компиляции после перехода на kapt.

Чтобы вернуть поддержку совместимости Data Binding с Котлином, необходимо добавить в список зависимости в build.gradle зависимость компилятора:

X.y.z. здесь определяют текущую версию Gradle: они должны совпадать.

Потенциальные ловушки при общении с SDK, написанными на Java

В то время, как вам неизбежно придётся работать с SDK, написанными на Java (включая, собственно, Android SDK), нужно всегда оставаться на стороже, когда речь идёт о nullable аргументах (по умолчанию в Java), открытых для переопределения методов.

Предположим, вам нужно переопределить onActivityResult в вашем Activity.

Замечаете что-нибудь подозрительное в этом сниппете?

Всегда есть шанс случайно упустить оператор ? после типа аргумента метода — в данном случае Intent, — который допускал бы null-значения. Здесь же, с точки зрения Kotlin-кода, data не может быть null ни при каких обстоятельствах, и, вне зависимости от того, укажете ли вы тип Intent как nullable или нет, вы не получите ни предупреждения, ни ошибки от компилятора, так как оба варианта сигнатуры допустимы. Но поскольку получение не пустой data не гарантировано, так как в случаях с SDK вы не можете это проконтролировать, получение null в данном случае приведёт к NPE. В случае, если вы укажете Intent?-тип, любые попытки вызвать метод такого объекта приведут к ошибке на этапе компиляции без должной проверки на null посредством ?-оператора.

Работаем с лямбдами: Android SDK

Как вы могли заметить в одном из сниппетов кода выше, с функции высшего порядка избавляют нас от большого количества бойлерплейт-кода — в том числе, когда речь идёт о реализации собственных click listenerов. Начать постижение дзен в написании красивых и производительных лямбд (и, к примеру, понять, в каких случаях уместно пренебречь фигурными скобками <> для достижения максимальной лаконичности), можно с этой страницы документации.

В этой статье мы сфокусируемся лишь на паре интересных кейсов, при которых лямбды могут значительно упростить (или наоборот, при неведении — усложнить) жизнь.

Предположим, что мы уже используем Kotlin Android Extensions в проекте. Давайте реализуем простые адаптер в связке с ViewHolder для списка фильмов на основе RecyclerView, каждый элемент в котором кликабелен, и выбор каждого должен быть каким-то образом обработан извне.

Вы наверняка заметили (Movie) -> Unit в качестве последнего параметра конструктора. В данном случае Movie выступает в качестве типа получателя, а Unit — в качестве результата выполнения функции и, таким образом, заменяет гипотетический click listener интерфейс, который вам бы пришлось реализовывать, имей вы дело с Java.

Ещё один участок, который мог привлечь ваше внимание, — класс MovieHolder сам по себе. Как видите, он реализует интерфейс LayoutContainer, который преобразовывает ViewHolder класс элемента списка в контейнер и таким образом активирует кеширование для вьюхи элемента (стратегию кеширования вы вполне можете кастомизировать).

Важно Не забудьте переопределить свойство containerView: View в конструкторе вашего ViewHolder.

И, наконец, вы определённо могли заметить выражение с with, которое, на самом деле, представляет из себя вызов одноимённой встроенной функции, позволяющей вам вызывать серию из методов текущего объекта.

Ещё одна ремарка Обратите внимание, что return внутри лямбды возвращает нас не из функции, в которой лямбда вызывается, а из самого лямбда-выражения.

Kotlin vs RxJava(2): And then… Nothing!

Kotlin значительно устраняет многословность RxJava в связке с Java (при условии, что вы не использовали Retrolambda или Jack) и полностью совместим с ней. Однако вселенная — место не самое идеальное, и на ловушки можно наткнуться и в этом тандеме.

Спойлер: всё из-за скобочек <>.

В своё время я стал жертвой печально известной проблемы с andThen оператором, которая подробно описана в статье «Kotlin and Rx2. How I wasted 5 hours because of wrong brackets». Передача лямбды в качестве параметра метода приведёт к разочаровывающему экзистенциальному ничему. Достаточно написать простой тест для выражения вроде

чтобы убедиться в этом воочию: содержимое andThen не выполнится. Всё потому, что пока в случае с большинством операторов вроде flatMap , defer , fromAction и огромного количества других, в качестве их аргументов ожидается действительно лямбда, при такой записи с andThen ожидается Completable/Observable/SingleSource . Проблема решается использованием обыкновенных круглых скобок () вместо фигурных <>.

К моменту публикации статьи этот баг всё ещё живёт застывшим в состоянии «under discussion».

Выводы

Эта статья покрывает лишь небольшой объём распространённых камней преткновения и вопросов, с которыми вы можете столкнуться в процессе перехода с Java на Kotlin. Без достаточного практического опыта он не всегда может оказаться столь же простым, каким кажется со стороны.

Однако, на мой взгляд, он того стоит. Даже решение открыть себя для новый язык программирования, выйти из зоны комфорта Java послужит важной вехой в вашем развитии как Android-разработчика. Так что при любой возможности — плавно, итеративно попробуйте перестроить своё сознание, свой проект на Kotlin. Помните, что все эти маленькие вкрапления боли, описанные в статье, — ничто по сравнению с тем, насколько вам приятно будет писать код на этом языке.

Полезные ссылки и ресурсы

  • Посмотрите в сторону Android KTX. Это набор extension-функций Android SDK, которые значительно сэкономят ваше время на написание кода для заполнения Bundle, работы с Shared Preferences, bitmap-изображениями, анимациями, span-ами и многим другим. Помимо официальной документации, я бы порекомендовал ознакомиться с этой статьей-экскурсией по KTX — «Exploring KTX for Android».
  • Обращайтесь время от времени к Kotlin Styleguide for Android.
  • Книжка «Kotlin in Action» от создателей языка Светланы Исаковой и Дмитрия Жемерова. Вы точно не пожалеете о её прочтении!

Источник

Оцените статью