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

Ссылка на метод Java 8: Как его использовать

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

Автор оригинала: Esteban Herrera.

Вступление

В Java мы можем использовать ссылки на объекты либо путем создания новых объектов:

List list = new ArrayList();
store(new ArrayList());

Или с помощью существующих объектов:

List list2 = list;
isFull(list2);

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

Если мы используем только метод объекта в другом методе, нам все равно придется передать полный объект в качестве аргумента. Не было бы более практично просто передать метод в качестве аргумента? Например:

isFull(list.size);

В Java 8, благодаря лямбда-выражениям, мы можем сделать что-то подобное. Мы можем использовать методы, как если бы они были объектами или примитивными значениями.

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

Ссылка на метод-это сокращенный синтаксис для лямбда-выражения, которое выполняет только ОДИН метод. Вот общий синтаксис ссылки на метод:

Object :: methodName

Мы знаем, что можем использовать лямбда-выражения вместо использования анонимного класса. Но иногда лямбда-выражение на самом деле является просто вызовом какого-либо метода, например:

Consumer c = s -> System.out.println(s);

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

Consumer c = System.out::println;

В ссылке на метод вы помещаете объект (или класс), содержащий метод, перед оператором :: , а имя метода после него без аргументов.

Но вы, возможно, думаете:

  • Как это становится яснее?
  • Что будет с аргументами?
  • Как это может быть допустимым выражением?
  • Я не понимаю, как создать допустимую ссылку на метод…

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

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

Другими словами:

Вместо использования АНОНИМНОГО КЛАССА вы можете использовать ЛЯМБДА-ВЫРАЖЕНИЕ И если это вызывает только один метод, вы можете использовать ССЫЛКА НА МЕТОД

Существует четыре типа ссылок на методы:

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

Давайте начнем с объяснения наиболее естественного случая, статического метода .

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

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

(args) -> Class.staticMethod(args)

Это можно превратить в следующую ссылку на метод:

Class::staticMethod

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

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

В этом случае любые аргументы (если таковые имеются), принятые методом, автоматически передаются за кулисами.

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

class Numbers {
  public static boolean isMoreThanFifty(int n1, int n2) {
    return (n1 + n2) > 50;
  }
  public static List findNumbers(
    List l, BiPredicate p) {
      List newList = new ArrayList<>();
      for(Integer i : l) {
        if(p.test(i, i + 10)) {
          newList.add(i);
        }
      }
      return newList;
  }
}

Мы можем вызвать метод find Numbers() :

List list = Arrays.asList(12,5,45,18,33,24,40);

// Using an anonymous class
findNumbers(list, new BiPredicate() {
  public boolean test(Integer i1, Integer i2) {
    return Numbers.isMoreThanFifty(i1, i2);
  }
});

// Using a lambda expression
findNumbers(list, (i1, i2) -> Numbers.isMoreThanFifty(i1, i2));

// Using a method reference
findNumbers(list, Numbers::isMoreThanFifty);

Ссылка на метод экземпляра объекта определенного типа

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

(obj, args) -> obj.instanceMethod(args)

Где передается экземпляр объекта, и один из его методов выполняется с некоторыми необязательными (- ами) параметрами (- ами).

Это можно превратить в следующую ссылку на метод:

ObjectType::instanceMethod

На этот раз преобразование не так просто. Во—первых, в ссылке на метод мы не используем сам экземпляр-мы используем его тип.

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

Например, предполагая, что этот класс:

class Shipment {
  public double calculateWeight() {
    double weight = 0;
    // Calculate weight
    return weight;
  }
}

И этот метод:

public List calculateOnShipments(
  List l, Function f) {
    List results = new ArrayList<>();
    for(Shipment s : l) {
      results.add(f.apply(s));
    }
    return results;
}

Мы можем вызвать этот метод, используя:

List l = new ArrayList();

// Using an anonymous class
calculateOnShipments(l, new Function() {
  public Double apply(Shipment s) { // The object
    return s.calculateWeight(); // The method
  }
});

// Using a lambda expression
calculateOnShipments(l, s -> s.calculateWeight());

// Using a method reference
calculateOnShipments(l, Shipment::calculateWeight);

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

Вот еще один пример, в котором мы передаем два аргумента в ссылку на метод.

В Java есть Функция интерфейс, который принимает один параметр, Бифункция , которая принимает два параметра, но нет Функция Tri , которая принимает три параметра , так что давайте сделаем один:

interface TriFunction {
  R apply(T t, U u, V v);
}

Теперь предположим, что класс с методом, который принимает два параметра, возвращает результат, как это:

class Sum {
  Integer doSum(String s1, String s2) {
    return Integer.parseInt(s1) + Integer.parseInt(s1);
  }
}

Мы можем обернуть метод dorsum() в Функция Tri реализация с использованием анонимного класса:

TriFunction anon =
  new TriFunction() {
    @Override
    public Integer apply(Sum s, String arg1, String arg2) {
      return s.doSum(arg1, arg1);
    }
};
System.out.println(anon.apply(new Sum(), "1", "4"));

Или с помощью лямбда-выражения:

riFunction lambda =
  (Sum s, String arg1, String arg2) -> s.doSum(arg1, arg1);
System.out.println(lambda.apply(new Sum(), "1", "4"));

Или просто используя ссылку на метод:

TriFunction mRef = Sum::doSum;
System.out.println(mRef.apply(new Sum(), "1", "4"));

Здесь:

  • Первый параметр типа функции Tri – это тип объекта, содержащий метод для выполнения.
  • Второй параметр типа функции Tri является типом первого параметра.
  • Третий параметр типа функции Tri является типом второго параметра.
  • Последний параметр типа функции Tri – это тип возвращаемого метода, который необходимо выполнить. Обратите внимание, как это опущено (выведено) в лямбда-выражении и ссылке на метод.
  • Может показаться странным просто видеть интерфейс, класс и то, как они используются со ссылкой на метод; но это становится более очевидным, когда вы видите анонимный класс или даже лямбда-версию.

От:

(Sum s, String arg1, String arg2) -> s.doSum(arg1, arg1)

К

Sum::doSum

Ссылка на метод экземпляра существующего объекта

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

(args) -> obj.instanceMethod(args)

Это можно превратить в следующую ссылку на метод:

obj::instanceMethod

На этот раз используется экземпляр, определенный где-то в другом месте, и аргументы (если таковые имеются) передаются за кулисами, как в случае статического метода.

Например, предполагая, что эти классы:

class Car {
  private int id;
  private String color;
  // More properties
  // And getter and setters
}
class Mechanic {
  public void fix(Car c) {
    System.out.println("Fixing car " + c.getId());
  }
}

И этот метод:

public void execute(Car car, Consumer c) {
  c.accept(car);
}

Мы можем вызвать описанный выше метод, используя:

final Mechanic mechanic = new Mechanic();
Car car = new Car();

// Using an anonymous class
execute(car, new Consumer() {
  public void accept(Car c) {
    mechanic.fix(c);
  }
});

// Using a lambda expression
execute(car, c -> mechanic.fix(c));

// Using a method reference
execute(car, mechanic::fix);

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

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

Consumer c = System.out::println;
c.accept("Hello");

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

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

(args) -> new ClassName(args)

Это можно превратить в следующую ссылку на метод:

ClassName::new

Единственное, что делает это лямбда-выражение, – это создает новый объект и мы просто ссылаемся на конструктор класса с ключевым словом new . Как и в других случаях, аргументы (если таковые имеются) не передаются в ссылке на метод.

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

Если конструктор не принимает аргументов, Поставщик выполнит эту работу:

// Using an anonymous class
Supplier> s = new Supplier() {
  public List get() {
    return new ArrayList();
  }
};
List l = s.get();

// Using a lambda expression
Supplier> s = () -> new ArrayList();
List l = s.get();

// Using a method reference
Supplier l = s.get();

Если конструктор принимает аргумент, мы можем использовать Функцию интерфейс. Например:

// Using an anonymous class
Function f =
  new Function() {
    public Integer apply(String s) {
      return new Integer(s);
    }
};
Integer i = f.apply(100);

// Using a lambda expression
Function f = s -> new Integer(s);
Integer i = f.apply(100);

// Using a method reference
Function f = Integer::new;
Integer i = f.apply(100);

Если конструктор принимает два аргумента, мы используем функцию Bi интерфейс:

// Using a anonymous class
BiFunction f = new BiFunction() {
  public Locale apply(String lang, String country) {
    return new Locale(lang, country);
  }
};
Locale loc = f.apply("en","UK");

// Using a lambda expression
BiFunction f = (lang, country) -> new Locale(lang, country);
Locale loc = f.apply("en","UK");

// Using a method reference
BiFunction f = Locale::new;
Locale loc = f.apply("en","UK");

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

Вы можете видеть, что ссылка на конструктор очень похожа на ссылку на статический метод. Разница в том, что конструктором “имя метода” является новый .

Вывод

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

Как упоминалось в начале, используйте ссылки на методы, если они делают ваш код БОЛЕЕ ПОНЯТНЫМ .

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

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

Однако это будет темой другого урока. Спасибо, что прочитали.

Этот учебник был адаптирован из главы моего Профессионального программиста Java 8 Учебное пособие .

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

  • Сериализация и десериализация в Java
  • Объяснена конкатенация Строк Java
  • Проверка моделей и пользовательских вводов на Java

Оригинал: “https://www.codementor.io/@eh3rrera/using-java-8-method-reference-du10866vx”