Поиск…


Вступление

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

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

Синтаксис

  • () -> {return expression; } // Zero-arity с телом функции, чтобы вернуть значение.
  • () -> выражение // Сокращение для указанного объявления; для выражений нет точки с запятой.
  • () -> {function-body} // Побочное действие в выражении лямбда для выполнения операций.
  • parameterName -> expression // Одномерное лямбда-выражение. В лямбда-выражениях с одним аргументом скобки могут быть удалены.
  • (Тип parameterName, Тип secondParameterName, ...) -> выражение // lambda, оценивающее выражение с параметрами, перечисленными слева
  • (parameterName, secondParameterName, ...) -> expression // Сокращение, которое удаляет типы параметров для имен параметров. Может использоваться только в контекстах, которые могут быть выведены компилятором, где размер списка заданных параметров соответствует одному (и только одному) размера ожидаемых функциональных интерфейсов.

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

Сортировка списков

До Java 8 необходимо было реализовать интерфейс java.util.Comparator с анонимным (или названным) классом при сортировке списка 1 :

Java SE 1.2
List<Person> people = ...
Collections.sort(
    people,
    new Comparator<Person>() {
        public int compare(Person p1, Person p2){
            return p1.getFirstName().compareTo(p2.getFirstName());
        }
    }
);

Начиная с Java 8, анонимный класс можно заменить выражением лямбда. Обратите внимание, что типы параметров p1 и p2 могут быть опущены, так как компилятор автоматически выведет их:

Collections.sort(
    people, 
    (p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);

Пример можно упростить, используя ссылки Comparator.comparing и методы, выраженные с помощью символа :: (двойной двоеточие).

Collections.sort(
    people,
    Comparator.comparing(Person::getFirstName)
);

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

import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(people, comparing(Person::getFirstName));

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

sort(people, comparing(Person::getFirstName).thenComparing(Person::getLastName));

1 - Обратите внимание, что Collections.sort (...) работает только с коллекциями, которые являются подтипами List . API Set и Collection не подразумевает упорядочения элементов.

Сортировка карт

Вы можете сортировать записи HashMap по значению аналогичным образом. (Обратите внимание , что LinkedHashMap должен быть использован в качестве мишени. Ключи в обычной HashMap являются неупорядоченными.)

Map<String, Integer> map = new HashMap();  // ... or any other Map class
// populate the map
map = map.entrySet()
    .stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue())
    .collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
                              (k, v) -> k, LinkedHashMap::new));

Введение в Java lambdas

Функциональные интерфейсы

Lambdas может работать только на функциональном интерфейсе, который является интерфейсом только с одним абстрактным методом. Функциональные интерфейсы могут иметь любое количество default или static методов. (По этой причине они иногда называются интерфейсами единого абстрактного метода или интерфейсами SAM).

interface Foo1 {
    void bar();
}

interface Foo2 {
    int bar(boolean baz);
}

interface Foo3 {
    String bar(Object baz, int mink);
}

interface Foo4 {
    default String bar() { // default so not counted
        return "baz";
    }
    void quux();
}

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

@FunctionalInterface
interface Foo5 {
    void bar();
}

@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}

@FunctionalInterface
interface Foo6 {
    void bar();
    boolean equals(Object obj); // overrides one of Object's method so not counted
}

И наоборот, это не функциональный интерфейс, так как он имеет более одного абстрактного метода:

interface BadFoo {
    void bar();
    void quux(); // <-- Second method prevents lambda: which one should 
                 // be considered as lambda?
}

Это также не функциональный интерфейс, так как он не имеет никаких методов:

interface BlankFoo2 { }

Обратите внимание на следующее. Предположим, что у вас есть

interface Parent { public int parentMethod(); }

а также

interface Child extends Parent { public int ChildMethod(); }

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

Java 8 также предоставляет ряд общих шаблонных функциональных интерфейсов в пакете java.util.function . Например, встроенный интерфейс Predicate<T> обертывает один метод, который вводит значение типа T и возвращает boolean .


Лямбда-выражения

Основная структура выражения Лямбды:

FunctionalInterface fi = () -> System.out.println ("Hello");

fi затем проведет одиночный экземпляр класса, аналогичный анонимному классу, который реализует FunctionalInterface и где определение одного метода { System.out.println("Hello"); } . Другими словами, вышесказанное в основном эквивалентно:

FunctionalInterface fi = new FunctionalInterface() {
    @Override
    public void theOneMethod() {
        System.out.println("Hello");
    }
};

Лямбда только « в основном эквивалент» анонимного класса , потому что в лямбда, смысл выражений , как this , super или toString() ссылается на класс , внутри которого назначение происходит, а не вновь созданный объект.

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

В случаях, когда тип лямбда не определен (например, перегруженные методы), вы можете добавить бросок в лямбда, чтобы сообщить компилятору, каким должен быть его тип, например:

Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true

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

Foo2 longFoo = new Foo2() {
    @Override
    public int bar(boolean baz) {
        return baz ? 1 : 0;
    }
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };

Скобки вокруг аргумента могут быть опущены, если функция имеет только один аргумент:

Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay

Неявные возвращения

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

IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };

Доступ к локальным переменным (закрытие значений)

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

IntUnaryOperator makeAdder(int amount) {
    return (x) -> (x + amount); // Legal even though amount will go out of scope
                                // because amount is not modified
}

IntUnaryOperator makeAccumulator(int value) {
    return (x) -> { value += x; return value; }; // Will not compile
}

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


Принятие Lambdas

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

public void passMeALambda(Foo1 f) {
    f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));

Тип выражения лямбда

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

  • Прямое присвоение функциональному типу, например myPredicate = s -> s.isEmpty()
  • Передача его как параметра, который имеет функциональный тип, например stream.filter(s -> s.isEmpty())
  • Возврат его из функции, возвращающей функциональный тип, например return s -> s.isEmpty()
  • Передача его функциональному типу, например (Predicate<String>) s -> s.isEmpty()

Пока не будет выполнено такое присвоение функциональному типу, лямбда не имеет определенного типа. Для иллюстрации рассмотрим лямбда-выражение o -> o.isEmpty() . Одно и то же выражение лямбда может быть присвоено многим различным функциональным типам:

Predicate<String> javaStringPred = o -> o.isEmpty();
Function<String, Boolean> javaFunc = o -> o.isEmpty();
Predicate<List> javaListPred = o -> o.isEmpty();
Consumer<String> javaStringConsumer = o -> o.isEmpty(); // return value is ignored!
com.google.common.base.Predicate<String> guavaPredicate = o -> o.isEmpty();

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

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

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

Предположим, что у нас есть модель:

class Person {
    private final String name;
    private final String surname;

    public Person(String name, String surname){
        this.name = name;
        this.surname = surname;
    }

    public String getName(){ return name; }
    public String getSurname(){ return surname; }
}

List<Person> people = getSomePeople();

Ссылка метода экземпляра (на произвольный экземпляр)

people.stream().map(Person::getName)

Эквивалентная лямбда:

people.stream().map(person -> person.getName())

В этом примере getName() ссылка метода на метод getName() экземпляра типа Person . Поскольку известно, что это тип коллекции, будет вызван метод экземпляра (известный позже).


Справочник метода экземпляра (к конкретному экземпляру)

people.forEach(System.out::println);

Поскольку System.out является экземпляром PrintStream , ссылка метода на этот конкретный экземпляр передается в качестве аргумента.

Эквивалентная лямбда:

people.forEach(person -> System.out.println(person));

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

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

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)

В этом примере приведена ссылка на статический метод valueOf() для типа String . Следовательно, объект экземпляра в коллекции передается как аргумент valueOf() .

Эквивалентная лямбда:

 numbers.stream().map(num -> String.valueOf(num))

Ссылка на конструктор

List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)

Прочитайте « Собрать элементы потока в коллекцию», чтобы увидеть, как собирать элементы в коллекцию.

Здесь используется единственный конструктор аргументов String типа Integer , чтобы построить целое число, заданное строкой, предоставленной в качестве аргумента. В этом случае, пока строка представляет число, поток будет отображаться в «Целые». Эквивалентная лямбда:

strings.stream().map(s -> new Integer(s));

Чит-лист

Формат ссылки метода Код Эквивалентная лямбда
Статический метод TypeName::method (args) -> TypeName.method(args)
Нестатический метод (например, * ) instance::method (args) -> instance.method(args)
Нестатический метод (нет экземпляра) TypeName::method (instance, args) -> instance.method(args)
Конструктор ** TypeName::new (args) -> new TypeName(args)
Конструктор массива TypeName[]::new (int size) -> new TypeName[size]

* instance может быть любым выражением, которое оценивает ссылку на экземпляр, например getInstance()::method , this::method

** Если TypeName является TypeName внутренним классом, ссылка на конструктор действительна только в пределах экземпляра внешнего класса

Реализация нескольких интерфейсов

Иногда вам может понадобиться выражение лямбда, реализующее более одного интерфейса. Это в основном полезно с интерфейсами маркеров (например, java.io.Serializable ), поскольку они не добавляют абстрактные методы.

Например, вы хотите создать TreeSet с пользовательским Comparator а затем сериализовать его и отправить по сети. Тривиальный подход:

TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));

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

TreeSet<Long> ts = new TreeSet<>(
    (Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));

Если вы часто используете типы пересечений (например, если вы используете фреймворк, такой как Apache Spark, где почти все должно быть сериализуемым), вы можете создавать пустые интерфейсы и вместо этого использовать их в своем коде:

public interface SerializableComparator extends Comparator<Long>, Serializable {}

public class CustomTreeSet {
  public CustomTreeSet(SerializableComparator comparator) {}
}

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

Ламбдас и шаблон выживания

Существует несколько хороших примеров использования lambdas как FunctionalInterface в простых сценариях. Довольно распространенный вариант использования, который может быть улучшен lambdas, - это так называемый шаблон Execute-Around. В этом шаблоне у вас есть набор стандартного кода установки / разрыва, который необходим для нескольких сценариев, связанных с конкретным кодом конкретного случая использования. Несколько распространенных примеров этого - файлы io, базы данных io, try / catch.

interface DataProcessor {
    void process( Connection connection ) throws SQLException;;
}

public void doProcessing( DataProcessor processor ) throws SQLException{
    try (Connection connection = DBUtil.getDatabaseConnection();) {
        processor.process(connection);
        connection.commit();
    } 
}

Затем, чтобы вызвать этот метод с лямбдой, он может выглядеть так:

public static void updateMyDAO(MyVO vo) throws DatabaseException {
    doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}

Это не ограничивается операциями ввода-вывода. Он может применяться к любому сценарию, где аналогичные задачи установки / срыва применяются с незначительными вариациями. Основное преимущество этого шаблона - повторное использование кода и принудительное использование DRY (Do not Repeat Yourself).

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

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

Например, запись Runnable в анонимный класс и начало Thread выглядит так:

//Old way
new Thread(
        new Runnable(){
            public void run(){
                System.out.println("run logic...");
            }
        }
).start();

//lambdas, from Java 8
new Thread(
        ()-> System.out.println("run logic...")
).start();

Теперь, в соответствии с выше, скажем, у вас есть пользовательский интерфейс:

interface TwoArgInterface {
    int operate(int a, int b);
}

Как вы используете лямбда для реализации этого интерфейса в своем коде? То же, что и пример Runnable, показанный выше. Смотрите программу драйвера ниже:

public class CustomLambda {
    public static void main(String[] args) {

        TwoArgInterface plusOperation = (a, b) -> a + b;
        TwoArgInterface divideOperation = (a,b)->{
            if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
            return a/b;
        };

        System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
        System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));

    }
}

`return` возвращается только из лямбда, а не из внешнего метода

Метод return возвращается только из лямбда, а не из внешнего метода.

Остерегайтесь, что это отличается от Scala и Kotlin!

void threeTimes(IntConsumer r) {
  for (int i = 0; i < 3; i++) {
    r.accept(i);
  }
}

void demo() {
  threeTimes(i -> {
    System.out.println(i);
    return; // Return from lambda to threeTimes only!
  });
}

Это может привести к неожиданному поведению при попытке записи собственных языковых конструкций, как в конструкциях встроенных , такие как for петель return ведет себя по- разному:

void demo2() {
  for (int i = 0; i < 3; i++) {
    System.out.println(i);
    return; // Return from 'demo2' entirely
  }
}

В Scala и Kotlin demo и demo2 будут только печатать 0 . Но это не является более последовательным. Подход Java совместим с рефакторингом и использованием классов - return в код вверху, а приведенный ниже код ведет себя одинаково:

void demo3() {
  threeTimes(new MyIntConsumer());
}

class MyIntConsumer implements IntConsumer {
  public void accept(int i) {
    System.out.println(i);
    return;
  }
}

Таким образом, return Java более совместим с методами класса и рефакторингом, но меньше с функциями for и while builtins, они остаются особенными.

Из-за этого следующие два эквивалентны в Java:

IntStream.range(1, 4)
    .map(x -> x * x)
    .forEach(System.out::println);
IntStream.range(1, 4)
    .map(x -> { return x * x; })
    .forEach(System.out::println);

Кроме того, использование try-in-resources безопасно в Java:

class Resource implements AutoCloseable {
  public void close() { System.out.println("close()"); }
}

void executeAround(Consumer<Resource> f) {
  try (Resource r = new Resource()) {
    System.out.print("before ");
    f.accept(r);
    System.out.print("after ");
  }
}

void demo4() {
  executeAround(r -> {
    System.out.print("accept() ");
    return; // Does not return from demo4, but frees the resource.
  });
}

будет печатать before accept() after close() . В семантике Scala и Kotlin средства try-with-resources не будут закрыты, но они будут печататься before accept() .

Закрытие Java с лямбда-выражениями.

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

Локальные переменные из охватывающей области, которые используются в лямбда, должны быть final . С Java 8 (самая ранняя версия, поддерживающая lambdas), они не обязательно должны быть объявлены final во внешнем контексте, но с этим нужно обращаться. Например:

int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};

Это законно, если значение переменной n не изменяется. Если вы попытаетесь изменить переменную, внутри или вне лямбда, вы получите следующую ошибку компиляции:

«Локальные переменные, на которые ссылается выражение лямбда, должны быть окончательными или фактически окончательными ».

Например:

int n = 0;
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};
n++; // Will generate an error.

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

int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = k;
    // do something
};
n++;      // Now will not generate an error
r.run();  // Will run with i = 0 because k was 0 when the lambda was created

Естественно, тело лямбда не видит изменений исходной переменной.

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

// Does not compile ...
public IntUnaryOperator createAccumulator() {
    int value = 0;
    IntUnaryOperator accumulate = (x) -> { value += x; return value; };
    return accumulate;
}

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

// Compiles, but is incorrect ...
public class AccumulatorGenerator {
    private int value = 0;

    public IntUnaryOperator createAccumulator() {
        IntUnaryOperator accumulate = (x) -> { value += x; return value; };
        return accumulate;
    }
}

Проблема заключается в том, что это нарушает контракт на IntUnaryOperator интерфейса IntUnaryOperator котором говорится, что экземпляры должны быть функциональными и неактивными. Если такое закрытие передается во встроенные функции, которые принимают функциональные объекты, это может привести к сбоям или ошибочному поведению. Закрытия, которые инкапсулируют изменяемое состояние, должны быть реализованы как обычные классы. Например.

// Correct ...
public class Accumulator {
   private int value = 0;

   public int accumulate(int x) {
      value += x;
      return value;
   }
}

Лямбда - пример слушателя

Анонимный слушатель

До Java 8 очень распространено, что анонимный класс используется для обработки события click JButton, как показано в следующем коде. В этом примере показано, как реализовать анонимный прослушиватель в области btn.addActionListener .

JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button was pressed");
    }
});

Лямбда-слушатель

Поскольку ActionListener интерфейс определяет только один метод actionPerformed() , он представляет собой функциональный интерфейс , который означает , что есть место , чтобы использовать лямбда - выражений для замены стандартного кода. Вышеприведенный пример можно переписать с использованием выражений Lambda следующим образом:

JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
    System.out.println("Button was pressed");
});

Традиционный стиль в стиле Лямбда

Традиционный способ

interface MathOperation{
    boolean unaryOperation(int num);
}

public class LambdaTry {
    public static void main(String[] args) {
        MathOperation isEven = new MathOperation() {
            @Override
            public boolean unaryOperation(int num) {
                return num%2 == 0;
            }
        };
        
        System.out.println(isEven.unaryOperation(25));
        System.out.println(isEven.unaryOperation(20));
    }
}

Лямбда-стиль

  1. Удалить имя класса и тело функционального интерфейса.
public class LambdaTry {
    public static void main(String[] args) {
        MathOperation isEven = (int num) -> {
            return num%2 == 0;
        };
        
        System.out.println(isEven.unaryOperation(25));
        System.out.println(isEven.unaryOperation(20));
    }
}
  1. Объявление необязательного типа
MathOperation isEven = (num) -> {
    return num%2 == 0;
};
  1. Необязательная скобка вокруг параметра, если она является единственным параметром
MathOperation isEven = num -> {
    return num%2 == 0;
};
  1. Дополнительные фигурные скобки, если в теле функции есть только одна строка
  2. Необязательное ключевое слово return, если в теле функции есть только одна строка
MathOperation isEven = num -> num%2 == 0;

Использование Lambdas и памяти

Поскольку Java lambdas являются закрытием, они могут «захватывать» значения переменных в охватывающей лексической области. В то время как не все лямбды захватывают что угодно - простые лямбды, такие как s -> s.length() ничего не захватывают и называются апатридами - захват lambdas требует временного объекта для хранения захваченных переменных. В этом фрагменте кода лямбда () -> j является захватывающей лямбдой и может вызывать выделение объекта при его оценке:

public static void main(String[] args) throws Exception {
    for (int i = 0; i < 1000000000; i++) {
        int j = i;
        doSomethingWithLambda(() -> j);
    }
}

Хотя это может быть не сразу очевидным, так как new ключевое слово не появляется нигде в фрагменте, этот код может создать 1,000,000,000 отдельных объектов, чтобы представлять экземпляры выражения () -> j lambda. Однако следует также отметить, что будущие версии Java 1 могут оптимизировать это, чтобы во время выполнения экземпляры лямбда были повторно использованы или были представлены каким-то другим способом.


1 - Например, Java 9 вводит факультативную фазу «ссылка» в последовательность сборки Java, которая предоставит возможность для глобальных оптимизаций, подобных этому.

Использование лямбда-выражений и предикатов для получения определенного значения (-ов) из списка

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

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

Класс личности:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() { return age; }
    public String getName() { return name; }
}

Встроенный интерфейс Predicate из пакета java.util.function.Predicate представляет собой функциональный интерфейс с методом boolean test(T t) .

Пример использования:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class LambdaExample {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Jeroen", 20));
        personList.add(new Person("Jack", 5));
        personList.add(new Person("Lisa", 19));

        print(personList, p -> p.getAge() >= 18);
    }

    private static void print(List<Person> personList, Predicate<Person> checker) {
        for (Person person : personList) {
            if (checker.test(person)) {
                System.out.print(person + " matches your expression.");
            } else {
                System.out.println(person  + " doesn't match your expression.");
            }
        }
    }
}

print(personList, p -> p.getAge() >= 18); метод принимает лямбда-выражение (потому что в Predicate используется параметр), где вы можете определить требуемое выражение. Метод проверки checker проверяет правильность этого выражения или нет: checker.test(person) .

Вы можете легко изменить это на что-то другое, например, для print(personList, p -> p.getName().startsWith("J")); , Это проверяет, начинается ли имя человека с буквы «J».



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow