Рубрики
Без рубрики

Ссылки на методы в Java 8

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

Автор оригинала: David Landup.

Вступление

Самым сладким синтаксическим сахаром , добавленным в Java до сих пор, безусловно, являются Лямбда-выражения .

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

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

Эти выражения широко используются в API Java Streams и фреймворке Spring Web Flux для создания реактивных динамических приложений.

Еще одна, действительно полезная функция, добавленная в Java 8, – это ссылки на методы , которые делают Лямбда-выражения намного более краткими и простыми, вызывая (ссылаясь) методы с использованием имени метода, когда Лямбда-выражение использовалось бы просто для вызова метода.

Ссылки на методы

Ссылки на методы-это, по сути, сокращенные лямбда-выражения, используемые для вызова методов.

Они состоят из двух частей:

Class::method;

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

someCodeChain.subscribe(System.out::println);

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

Мы проведем простой урок:

public class Employee {
    private int id;
    private String name;
    private int wage;
    private String position;

    // Constructor, getters and setters

    @Override
    public String toString() {
        return "Name: " + name + ", Wage: " + wage + ", Position: " + position;
    }

    public int compareTo(Employee employee) {
        if (this.wage <= employee.wage) {
            return 1;
        } else {
            return -1;
        }
    }
}

Если бы мы сформировали этот класс в коллекцию, такую как ArrayList , мы не смогли бы отсортировать его с помощью служебного метода .sort () , поскольку он не реализует Сопоставимый интерфейс.

Что мы можем сделать, так это определить новый компаратор для этих объектов при вызове метода .sort() :

Employee emp1 = new Employee(1, "David", 1200, "Developer");
Employee emp2 = new Employee(2, "Tim", 1500, "Developer");
Employee emp3 = new Employee(3, "Martha", 1300, "Developer");

ArrayList employeeList = new ArrayList<>();
employeeList.add(emp1);
employeeList.add(emp2);
employeeList.add(emp3);

Collections.sort(employeeList, new Comparator() {
    public int compare(Employee emp1, Employee emp2) {
        return emp1.compareTo(emp2);
    }
});

System.out.println(employeeList);

Запуск этого кода приведет к:

[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]

Здесь класс anonymous ( Компаратор ) определяет критерии сравнения. Мы можем сделать это намного проще и короче, используя лямбда-выражение:

Collections.sort(employeeList, (e1, e2) -> e1.compareTo(e2));

Запуск этого фрагмента кода приведет к:

[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]

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

Collections.sort(employeeList, Employee::compareTo);

И это также даст:

[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]

Типы ссылок на методы

Ссылки на методы могут использоваться в нескольких различных сценариях:

  • Статические методы: Класс::Имя статического метода
  • Методы экземпляра конкретных объектов: объект::имя метода экземпляра
  • Методы экземпляра произвольных объектов: Класс::имя метода
  • Ссылка на конструктор: Класс::новый

Давайте рассмотрим все эти типы на нескольких простых примерах.

Ссылки на статические методы

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

Давайте определим класс с помощью статического метода, а затем сошлемся на него из другого класса:

public class ClassA {
    public static void raiseToThePowerOfTwo(double num) {
        double result = Math.pow(num, 2);
        System.out.println(result);
    }
}

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

public class ClassB {
    public static void main(String[] args) {
        List integerList = new ArrayList<>();
        integerList.add(new Double(5));
        integerList.add(new Double(2));
        integerList.add(new Double(6));
        integerList.add(new Double(1));
        integerList.add(new Double(8));
        integerList.add(new Double(9));

        integerList.forEach(ClassA::raiseToThePowerOfTwo);
    }
}

Запуск этого фрагмента кода приведет к:

25.0
4.0
36.0
1.0
64.0
81.0

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

Методы экземпляра конкретных объектов

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

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

Чаще всего это иллюстрируется с помощью специального компаратора. Мы будем использовать тот же Сотрудник класс, что и раньше, и тот же список, чтобы подчеркнуть разницу между этими двумя:

public class Employee {
    private int id;
    private String name;
    private int wage;
    private String position;

    // Constructor, getters and setters

    @Override
    public String toString() {
        return "Name: " + name + ", Wage: " + wage + ", Position: " + position;
    }

    public int compareTo(Employee employee) {
        if (this.wage <= employee.wage) {
            return 1;
        } else {
            return -1;
        }
    }
}

Теперь давайте определим Пользовательский компаратор :

public class CustomComparator {
    public int compareEntities(Employee emp1, Employee emp2) {
        return emp1.compareTo(emp2);
    }
}

И, наконец, давайте составим список и отсортируем его:

Employee emp1 = new Employee(1, "David", 1200, "Developer");
Employee emp2 = new Employee(2, "Tim", 1500, "Developer");
Employee emp3 = new Employee(3, "Martha", 1300, "Developer");

ArrayList employeeList = new ArrayList<>();
employeeList.add(emp1);
employeeList.add(emp2);
employeeList.add(emp3);

// Initializing our CustomComparator
CustomComparator customComparator = new CustomComparator();

// Instead of making a call to an arbitrary Employee
// we're now providing an instance and its method
Collections.sort(employeeList, customComparator::compareEntities);

System.out.println(employeeList);

Выполнение этого кода также приведет к:

[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]

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

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

Методы экземпляра произвольных объектов

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

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

List integerList = new ArrayList<>();
integerList.add(new Integer(5));
integerList.add(new Integer(2));
integerList.add(new Integer(6));
integerList.add(new Integer(1));
integerList.add(new Integer(8));
integerList.add(new Integer(9));

// Referencing the non-static compareTo method from the Integer class
Collections.sort(integerList, Integer::compareTo);

// Referencing static method
integerList.forEach(System.out::print);

Запуск этого фрагмента кода приведет к:

125689

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

Collections.sort(integerList, (Integer a, Integer b) -> a.compareTo(b));

Здесь различие более очевидно. Если бы мы вызвали метод static , это выглядело бы так:

Collections.sort(integerList, (Integer a, Integer b) -> SomeClass.compare(a, b));

Ссылки На Конструкторы

Вы можете ссылаться на конструктор класса так же, как на статический метод.

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

// Classic instantiation
Employee employee = new Employee();

// Constructor reference
Employee employee2 = Employe::new;

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

Stream stream = names.stream().map(Employee::new);

Из-за потока имен, если присутствует Сотрудник(строковое имя) конструктор, он будет использоваться.

Другой способ, которым вы могли бы использовать ссылки на конструктор, – это когда вы хотите отобразить поток в массив, сохраняя при этом определенный тип. Если бы вы просто сопоставили его , а затем вызвали toArray () , вы получили бы массив Объектов вместо вашего конкретного типа.

Если бы мы попытались, скажем:

Employee[] employeeArray = employeeList.toArray();

Конечно, мы были бы встречены ошибкой компилятора, так как мы .toArray() возвращает массив Объекта s. Приведение этого также не поможет:

Employee[] employeeArray = (Employee[]) employeeList.toArray();

Но на этот раз это будет исключение во время выполнения – ClassCastException .

Мы можем избежать этого с помощью:

// Making a list of employees
List employeeList = Arrays.asList("David", "Scott");

// Mapping a list to Employee objects and returning them as an array
Employee[] employeeArray = employeeList.stream().map(Employee::new).toArray(Employee[]::new);

// Iterating through the array and printing information
for (int i = 0; i < employeeArray.length; i++) {
    System.out.println(employeeArray[i].toString());
}

И с этим мы получаем результат:

Name: David, Wage: 0, Position: null
Name: Scott, Wage: 0, Position: null

Вывод

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

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