Looking for java Keywords? Try Ask4Keywords

Java Language Введение в 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();

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