Статьи

Java 8 для Android: более чистый код с лямбда-выражениями

Лямбда-выражения могут помочь вам удалить шаблонный код из ваших проектов и с легкостью обрабатывать огромные объемы данных. Посмотрите, как благодаря этому углубленному взгляду на функции Java 8 вы можете начать использовать в своих проектах Android сегодня.

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

К сожалению, разработчики Android не будут ощущать преимущества этих функций какое-то время, так как Google экспериментировал с переносом Java 8 на платформу Android через Jack (Java Compiler Kit), прежде чем отвергнуть Jack в пользу собственной поддержки Java 8 в Android Studio.

Теперь, с выпуском Android Studio 3.0, у нас наконец-то появилась версия набора инструментов Android, который имеет встроенную поддержку некоторых наиболее важных функций Java 8.

В этой серии я покажу вам, как удалить тонну шаблонного кода из ваших проектов, с легкостью обрабатывать огромные объемы данных и даже использовать более функциональный стиль в Java-программировании на Java 8. Мы будем подробно рассмотрим функции Java 8, которые вы можете начать использовать уже сегодня.

Когда вы закончите эту серию, вы будете готовы использовать все следующие функции Java 8 в своих проектах Android:

  • лямбда-выражения
  • ссылки на методы
  • методы по умолчанию
  • методы статического интерфейса
  • тип аннотации
  • повторяющиеся аннотации
  • функциональные интерфейсы
  • Stream API

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

Прежде чем вы сможете начать использовать какие-либо функции Java 8, вы должны убедиться, что ваша среда разработки настроена для поддержки этой версии Java.

Если у вас еще не установлена ​​Java 8, вам нужно загрузить последнюю версию JDK8 и обновить путь JDK в Android Studio, чтобы он указывал на пакет JDK8:

  • Запустите Android Studio.
  • Выберите « Файл»> «Структура проекта» на панели инструментов Android Studio.
  • Обновите поле Местоположение JDK, чтобы оно указывало на недавно загруженный пакет JDK8.

Если вы не уверены, какую версию Java вы установили, вы можете проверить, открыв окно терминала (если вы пользователь Mac) или командную строку (если вы используете Windows), а затем запустив следующую команду: команда:

1
java -version

Если он вернет билд 1.8 или выше, значит, вы готовы!

Вам также необходимо установить Android Studio 3.0 Preview 1 или более поздней версии, хотя для уменьшения вероятности возникновения ошибок и других странных действий рекомендуется установить последнюю версию Android Studio 3.0 — будь то бета-версия, предварительный просмотр или в идеале, стабильная версия Android Studio 3.0 (которая еще не была доступна на момент написания статьи).

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

В следующих разделах я покажу вам, как включить поддержку Java 8 и как отключить Retrolambda и Jack, если это необходимо.

Предполагая, что вы ранее не включили Jack или не добавили Retrolambda в качестве зависимости проекта, первым шагом является открытие файла build.gradle уровня build.gradle и build.gradle того, что вы используете версию 3.0.0-alpha1 (или выше) Плагин Gradle для Android:

1
2
3
4
5
6
7
8
9
buildscript {
 
 repositories {
     google()
     jcenter()
 }
 
 dependencies {
     classpath ‘com.android.tools.build:gradle:3.0.0-alpha6’

Затем откройте каждый файл на уровне модуля build.gradle котором вы хотите использовать функции Java 8, и установите для уровня языка исходного кода и версии сгенерированного байт-кода Java значение JavaVersion.VERSION_1_8 :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
android {
  compileSdkVersion 26
  buildToolsVersion «26.0.1»
  defaultConfig {
 
      applicationId «com.jessicathornsby.myapplication»
      minSdkVersion 26
      targetSdkVersion 26
      versionCode 1
      versionName «1.0»
      testInstrumentationRunner «android.support.test.runner.AndroidJUnitRunner»
 
//Add the following block//
 
      compileOptions {
          sourceCompatibility JavaVersion.VERSION_1_8
          targetCompatibility JavaVersion.VERSION_1_8
      }
  }

Компилятор Jack может устареть, но пока он включен, ваш проект будет использовать поддержку Java 8, предоставляемую Jack, а не поддержку, предоставляемую стандартным набором инструментов Android.

Использование устаревшего инструмента никогда не является хорошей идеей, но есть некоторые дополнительные причины, по которым вам следует перейти с компилятора Jack, если вы этого еще не сделали.

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

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

Чтобы отключить компилятор Jack, откройте файл build.gradle уровня build.gradle и удалите раздел jackOptions , но убедитесь, что вы оставили блок compileOptions без изменений:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
android {
 
  compileSdkVersion 26
  buildToolsVersion «26.0.1»
  defaultConfig {
 
      applicationId «com.jessicathornsby.myapplication»
      minSdkVersion 26
      targetSdkVersion 26
      versionCode 1
      versionName «1.0»
 
//Remove the entire jackOptions section//
 
      jackOptions {
          enabled true
      }
 
      testInstrumentationRunner «android.support.test.runner.AndroidJUnitRunner»
 
//Don’t remove the compileOptions section//
 
      compileOptions {
          sourceCompatibility JavaVersion.VERSION_1_8
          targetCompatibility JavaVersion.VERSION_1_8
 
      }
  }

Как и Джек, Retrolambda не поддерживает сторонние библиотеки, которые используют функции языка Java 8. Если ваш проект настроен на использование плагина Retrolambda, то вы должны удалить этот плагин, чтобы ваш проект мог вернуться к цепочке инструментов по умолчанию.

Откройте файл build.gradle уровня build.gradle и удалите Retrolambda как зависимость проекта:

1
2
3
4
5
6
dependencies {
      classpath ‘com.android.tools.build:gradle:3.0.0-beta2’
       
//Remove the following line//
 
      classpath ‘me.tatarka:gradle-retrolambda:3.7.0’

Затем удалите плагин Retrolambda из каждого из ваших файлов build.gradle уровня build.gradle :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
apply plugin: ‘com.android.application’
 
//Remove the following line//
 
apply plugin: ‘me.tatarka.retrolambda’
 
android {
 
//Don’t delete the compileOptions block!//
 
compileOptions {
      sourceCompatibility JavaVersion.VERSION_1_8
      targetCompatibility JavaVersion.VERSION_1_8
 
  }
}

Самый простой способ проверить, что ваш проект теперь может поддерживать Java 8, — написать быстрое лямбда-выражение и посмотреть, все ли ваш проект компилируется.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.view.View;
import android.widget.Toast;
 
public class MainActivity extends AppCompatActivity {
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
 
//Implement the onClickListener using a lambda expression//
 
      Button button = (Button) findViewById(R.id.button);
      if (button != null) {
          button.setOnClickListener((View view) -> Toast.makeText(this,
                  «I was written in Java 8!», Toast.LENGTH_LONG).show());
 
      }
  }
}
Проверьте свое лямбда-выражение, запустив тост Java 8

Убедитесь, что ваш проект все еще компилируется. Для этого выберите « Синхронизировать» в появившемся баннере или выберите « Инструменты»> «Android»> «Синхронизировать проект с файлами Gradle» на панели инструментов Android Studio.

Если Android Studio не выдает никаких ошибок, тогда вы готовы начать использовать все функции, перечисленные в начале этой статьи, включая лямбда-выражения!

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

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

Эта функция устраняет давнишнее разочарование, которое испытывали многие разработчики Android с Java: в качестве объектно-ориентированного языка передача блоков кода всегда была более сложной, чем должна быть. Например, если вы хотите создать новый поток и затем передать некоторый код этому потоку, то вам, как правило, нужно создать экземпляр потока с анонимной реализацией интерфейса Runnable — это большая работа, чтобы просто передать некоторый код! Предоставляя простой способ передачи функции в метод, лямбда-выражения могут упростить некоторые из наиболее распространенных задач, которые вы будете выполнять как разработчик Android.

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

Вы создаете лямбда-выражение, используя следующий синтаксис:

1
(argument) -> {expression body}

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

Аргумент — это один или несколько параметров, которые почти всегда заключены в скобки. Даже если ваше лямбда-выражение не имеет никаких параметров, вам все равно придется указывать пустые скобки, например:

1
() -> System.out.println(«This lambda expression has no parameters»);

Исключением из этого правила является случай, когда у вашего метода есть один параметр с выводом его типа, и в этом случае вы можете опустить круглые скобки:

1
textView.setOnLongClickListener(event -> System.out.println(«Long Click»));

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

1
(parameter1, parameter2) -> System.out.println(«Parameters: » + parameter1 + «, » + parameter2);

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

1
2
3
4
5
6
Button button = (Button) findViewById(R.id.button);
  if (button != null) {
      button.setOnClickListener((View view) -> Log.d(«debug», «Button clicked»));
 
  }
}

Тело выражения — это код, который вы хотите выполнить, который может быть одним выражением или несколькими строками кода. Если вы хотите выполнить несколько строк, вам нужно создать блок операторов, окружив этот раздел кода фигурными скобками:

1
2
3
4
5
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(view -> {
  Log.d(«debug», «Button clicked»);
  Toast.makeText(this,
          «I was written in Java 8!», Toast.LENGTH_LONG).show();

Если ваше выражение возвращает значение, то оно должно быть возвращено с оператором return, например:

1
2
3
4
(parameter1) -> {
  System.out.println(«Parameter: » + parameter1);
  return «return value»;
}

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

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

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

1
2
3
4
5
6
7
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       doSomething();
   }
});

Переписав приведенный выше код с помощью лямбда-выражения, мы можем удалить все следующее:

  • создание экземпляра класса: new View.OnClickListener()
  • модификатор доступа, имя метода и тип: public void onClick(View view)
  • и типы параметров, так что вам не нужно писать представление View view

Это означает, что мы можем реализовать точно такую ​​же функциональность, используя одну строку:

1
button.setOnClickListener(view -> doSomething());

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

До Java 8 назначение кода для запуска в дополнительном потоке требовало создания анонимного класса, который реализует интерфейс Runnable :

01
02
03
04
05
06
07
08
09
10
Runnable r = new Runnable(){
   @Override
   public void run(){
       System.out.println(«My runnable»);
 
   }
};
 
Thread thread = new Thread(r);
thread.start();

В качестве альтернативы вы можете создать новый поток с анонимной реализацией интерфейса Runnable :

1
2
3
4
5
6
7
8
9
Thread thread = new Thread(new Runnable() {
   @Override
   public void run(){
       System.out.println(«My runnable»);
 
   }
});
 
thread.start();

Замена этого анонимного класса лямбда-выражением может сделать эту часто выполняемую задачу намного более краткой:

1
2
3
4
5
Runnable r = () -> { System.out.println(«My runnable»);
 
//Start the new thread//
 
new Thread(r).start();

Наконец, если вы используете библиотеку RxJava или RxAndroid , то вы можете использовать лямбда-выражения, чтобы помочь вам создавать наблюдаемые.

Здесь мы создаем простую Observable которая генерирует строку hello world всем своим Observers :

1
2
3
4
5
6
7
Observable.just(«Hello, world!»)
  .subscribe(new Action1<String>() {
      @Override
      public void call(String s) {
          Log.d(TAG, s);
      }
  });

Использование лямбда-выражения позволяет заменить весь этот код Action1 одной строкой:

1
2
Observable.just(«Hello, world!»)
  .subscribe(s -> Log.d(TAG, s));

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

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

Первая функция — это меню намеренных действий Android Studio, которое может автоматически преобразовывать любой совместимый анонимный класс в эквивалентное лямбда-выражение. Это идеально, если вы когда-либо не знаете, как написать конкретный фрагмент кода в лямбда-формате: просто напишите его как обычно, а затем используйте функцию автоматического преобразования в меню действий намерения.

Чтобы автоматически преобразовать анонимный класс в лямбда-выражение:

  • Наведите курсор на анонимный класс, и Android Studio должна отобразить всплывающую подсказку, сообщающую, что она может преобразовать этот раздел кода в лямбда-выражение.
  • Нажмите клавиши Alt / Option-Enter на Mac или используйте сочетание клавиш Alt-Enter, если вы пользователь Windows или Linux.
  • Выберите пункт « Заменить на лямбду» в контекстном меню.

Выберите Replace with lambda в появившемся контекстном меню.

Кроме того, вы можете использовать инструмент Android Studio Inspection, чтобы пометить каждый анонимный класс, который вы потенциально можете заменить лямбда-выражением, во всем проекте. Затем вы можете либо переписать каждый анонимный класс вручную, либо позволить функции автоматического преобразования в Android Studio показать, как это делается.

Чтобы выделить каждый анонимный класс, который Android Studio потенциально может заменить лямбда-выражением:

  • Выберите « Анализ»> «Запустить проверку по имени» на панели инструментов Android Studio.
  • В появившемся всплывающем окне начните вводить анонимный тип, который можно заменить на лямбду , а затем выберите этот параметр, когда он появится в раскрывающемся меню.

Выберите Анонимный тип можно заменить на лямбду из всплывающего окна Введите имя инспекции

  • В следующем окне выберите Весь проект, чтобы пометить каждый анонимный класс в вашем проекте. Кроме того, вы можете указать отдельные модули или файлы, в которых Android Studio должна выполнять эту проверку.
  • Нажмите ОК .
  • Выберите « Анализ»> «Проверить код» на панели инструментов Android Studio.

Теперь должна появиться панель результатов проверки и отобразить список всех анонимных классов, которые можно заменить лямбда-выражением. Чтобы ближе познакомиться с анонимным классом, просто дважды щелкните этот класс в окне результатов проверки , и Android Studio откроет файл и приведет вас к точной строке, содержащей этот конкретный анонимный класс.

Чтобы заменить выбранный анонимный класс лямбда-выражением, нажмите кнопку « Заменить на лямбду» .

В окне «Результаты проверки» выберите кнопку «Заменить на лямбду».

Если Android Studio не открывает окно «Результаты проверки» автоматически, вы можете запустить его вручную, выбрав « Вид»> «Инструменты Windows»> «Результаты проверки» на панели инструментов «Android Studio». Если результаты проверки не отображаются в подменю « Окна» Windows , вам может потребоваться сначала выбрать « Анализ»> «Проверка кода…» на панели инструментов Android Studio.

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

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

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

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

А пока посмотрите другие наши посты о разработке приложений для Android!

  • Android SDK
    Введение в компоненты архитектуры Android
    Жестяная мегали
  • Android SDK
    Что такое мгновенные приложения для Android?
  • Android SDK
    Java против Kotlin: стоит ли использовать Kotlin для разработки под Android?
  • Android SDK
    Отправка данных с помощью HTTP-клиента Retrofit 2 для Android
    Чике Мгбемена