вторник, 27 февраля 2018 г.

Язык Котлин. Часть 2. Анонимные функции, лямбда-выражения и функции высшего порядка

В конце предыдущей части в примере использовалось лямбда-выражение. Напомню, что лямбда-выражение — это сокращённая запись анонимной, то есть необъявленной функции, не имеющей имени. Анонимная функция — это функция, не имеющая имени, тело которой непосредственно используется в качестве аргумента другой функции.

Пример анонимной функции:

fun(x: Int, y: Int): Int { 
    return x + y
}

Функции с одной строкой кода в своём теле лучше записывать вообще в одну строку:

fun(x: Int, y: Int): Int = x + y

Использование анонимной функции:

operationSum(7, 8, fun(x: Int, y: Int): Int = x + y) 

Использование сокращённой записи анонимной функции в виде лямбда-выражения:

operationSum(7, 8, {x, y -> x + y}) 

В качестве третьего параметра вписана анонимная функция, которая, например, будет использоваться внутри функции operationSum для суммирования первых двух параметров.

Лямбда-выражения заключаются в фигурные скобки и могут быть многострочными. Функции, принимающие в качестве параметра другие функции или лямбды называются функциями высшего порядка.

В случае, если в лямбда-выражение передаётся только один параметр,  запись можно сократить ещё больше, убрав оператор -> и всё, что слева от него. В этом случае доступ к переданному параметру внутри тела лямбда-выражения осуществляется через автоматически созданную переменную it:

operationInc(7, {it + 1})

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

Лямбда-выражения можно присваивать переменным:

val sum = { x: Int, y: Int -> x + y }
val s = sum(2, 4)     // s будет равно 6

Помимо функций высшего порядка, лямбда-выражения можно передавать и в интерфейсы с одним абстрактным методом для автоматической реализации этого метода переданным кодом, если типы параметров передаваемой функции или лямбда-выражения совпадают с типами параметров абстрактного метода интерфеса. Пока это относится только к Java-интерфейсам, но, возможно, в будущем это будет относиться и к Котлин-интерфейсам. Реальный пример кода (использующего стандартную библиотеку архитектурных компонентов LiveData) с передачей лямбда-выражения в интерфейс наблюдателя за данными:

  /*
   * Подключение наблюдателя за изменениями данных.
   * Подлежащие наблюдению данные заключены в тип LiveData<t> и находятся в переменной этого типа leavDataList.
   * Для наблюдения за ними вызываем LiveData-метод observe, и передаём ему контекст владельца наблюдателя (это основная активность)
   * и реализацию интерфейса наблюдателя.
   */
  leavDataList.observe(this@MainActivity, Observer { newLeavDataList -> newLeavDataList?.let { recyclerViewAdapter.addItems(it) } })

В вышеприведённом примере мы реализуем интерфейс Observer, передав в него лямбда-выражение readoutsList -> readoutsList?.let { recyclerViewAdapter.addItems(it) }.

Интерфейс Observer имеет единственный абстрактный метод onChanged, принимающий данные любого типа (можно открыть Java-код интерфейса и посмотреть). Поэтому переданное интерфейсу лямбда-выражение автоматически преобразуются в реализацию единственного абстрактного метода интерфейса. Такое преобразование называется SAM-преобразованием (Single Abstract Method Conversions).

В лямбда-выражение передаются данные newLeavDataList. Они же оказываются на входе реализованного таким образом метода onChanged. Метод, в случае, если данные не null, добавляет их в адаптер. Кстати, для выполнения добавления только в том случае, если данные не null, реализовано с помощью метода данных let с использованием другого лямбда-выражения. О таком способе реализации null-безопасности рассказывалось в конце предыдущей части.

понедельник, 26 февраля 2018 г.

Язык Котлин. Часть 1. Null-безопасность.

17 мая 2017 года на конференции Google I/O было объявлено об официальной поддержке языка Kotlin для разработки Android-приложений. Первый релиз языка вышел 15 февраля 2016 года. Плагин для поддержки Kotlin теперь входит в поставку Android Studio 3.0 (релиз этой версии студии состоялся 25 октября 2017 года). Так что теперь, очевидно, Котлин стал наиболее перспективным языком программирования под Android.

Основное преимущество Котлина над Java — компактность исходного кода. Объёмы исходников сокращаются почти наполовину, что не может не радовать, поскольку Java-запись всегда напрягала своей размашистостью.

Кое-что про Котлин я уже недавно писал в своём основном блоге. Теперь же я хочу остановиться на некоторых особенностях языка.

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

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

Null-безопасность

Обращение к полю null-объекта

Что сразу бросается в глаза в нижеприведённом примере?

val phone = phoneBook?.presidents?.putin?.phone 

Да, знаки вопроса перед операторами вызова (точками), а вовсе не фамилия putin. Знаки вопроса — это общее проявление null-безопасности языка. В приведённом примере без знака вопроса Android Studio укажет на ошибку, потому что если в одном из явно не инициализированных полей будет null, то программа выбросит исключение NullPointerException.

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

Тем не менее, если нужно вернуться к исключениям, достаточно вместо ? поставить !!

val phone = phoneBook!!.presidents!!.putin!!.phone

Оператор !! — это как бы обещание программиста, что на момент вызова поле точно не будет иметь значение null. Иначе, как уже было сказано, в процессе выполнения программы возникнет исключение NullPointerException.

Типы данных и null

По умолчанию все типы данных в Котлин не могут содержать null. Чтобы разрешить типу данных содержать null, после указания типа ставится знак вопроса:

val str String? = null 

Оператор «Элвис» 

Присваивание заведомо непустого значения переменной:

val len = b?.length ?: -1 

В случае пустого поля b в переменную len возвращается -1. Иначе возвращается длина объекта b. Для этого служит оператор «Элвис» ?: названный так за схожесть получившегося смайлика с причёской Элвиса Пресли.

Null-безопасность оператора as

В случае невозможности привести типы исключение ClassCastException не возникает и возвращается null:

val integerVar: Int? = unknownTypeVar as? Int 

Если unknownTypeVar можно привести к типу Int, то integerVar = unknownTypeVar, иначе integerVar = null.

Коллекции. Фильтрация null-значений

Из одной коллекции, имеющей среди значений null, можно перенести в другую коллекцию только не-null значения. Для этого у коллекции есть метод фильтра filterNotNull:

val nullableList: List<int> = [1, 2, null, 4]
val intList: List<int> = nullableList.filterNotNull()

Метод let. Быстрая проверка на null

Выполнить код только в том случае, если используемая переменная не равна null, можно с помощью оператора безопасного вызова ?. и метода let:

val fruitBasket = ...

apple?.let {
  println("Добавление яблока в корзину!")
  fruitBasket.add(it)
}

Если apple не null, выполнить т.н. «лямбда-выражение» в фигурных скобках, куда значение apple передано через it. Про лямбда-выражение читайте в следующей части.