Подтвердить что ты не робот

Ссылка на метод Java8, используемая как объект Function для объединения функций

Есть ли способ в Java8 использовать ссылку метода как объект Function для использования своих методов, например:

Stream.of("ciao", "hola", "hello")
    .map(String::length.andThen(n -> n * 2))

Этот вопрос не связан с Stream, он используется как пример, я хотел бы получить ответ о ссылке метода

4b9b3361

Ответ 1

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

import java.util.function.*;

class Test {
    public static void main(String[] args) {
        Function<String, Integer> function = combine(String::length, n -> n * 2);
        System.out.println(function.apply("foo"));
    }

    public static <T1, T2, T3> Function<T1, T3> combine(
        Function<T1, T2> first,
        Function<T2, T3> second) {
        return first.andThen(second);
    }
}

Затем вы можете поместить его в класс утилиты и импортировать его статически.

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

import java.util.function.*;

class Test {
    public static void main(String[] args) {
        Function<String, Integer> function = asFunction(String::length).andThen(n -> n * 2);
        System.out.println(function.apply("foo"));
    }

    public static <T1, T2> Function<T1, T2> asFunction(Function<T1, T2> function) {
        return function;     
    }
}

Ответ 2

Вы можете просто сохранить его в переменной:

Function<String, Integer> toLength = String::length;
Stream.of("ciao", "hola", "hello")
      .map(toLength.andThen(n -> n * 2));

Или вы можете использовать актерский состав, но он менее читабельный, IMO:

Stream.of("ciao", "hola", "hello")
      .map(((Function<String, Integer>) String::length).andThen(n -> n * 2));

Ответ 3

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

Stream.of("ciao", "hola", "hello")
      .map(((Function<String, Integer>) String::length).andThen(n -> n * 2))

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


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

Function<String, Integer> fun = String::length

Stream.of("ciao", "hola", "hello")
      .map(fun.andThen(n -> n * 2));

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

public static <T, X, U> Function<T, U> chain(Function<T, X> fun1, Function<X, U> fun2)
{
    return fun1.andThen(fun2);
}

Stream.of("ciao", "hola", "hello")
      .map(chain(String::length, n -> n * 2));

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

Ответ 4

Вы также можете использовать

Function.identity().andThen(String::length).andThen(n -> n * 2)

Проблема заключается в том, что String::length не обязательно a Function; он может соответствовать многим функциональным интерфейсам. Он должен использоваться в контексте, который предоставляет тип цели, а контекст может быть: присвоением, вызовом метода, литьем.

Если Function может предоставить статический метод только для целевого ввода, мы могли бы сделать

    Function.by(String::length).andThen(n->n*2)

static <T, R> Function<T, R> by(Function<T, R> f){ return f; }

Например, я использую этот метод в функциональном интерфейсе

static <T> AsyncIterator<T> by(AsyncIterator<T> asyncIterator)

Синтаксис сахара для создания AsyncIterator из выражения лямбда или ссылки на метод.

Этот метод просто возвращает аргумент asyncIterator, который кажется немного странным. Объяснение:

Так как AsyncIterator является функциональным интерфейсом, экземпляр может быть создан с помощью лямбда-выражения или ссылки на метод в трех контекстах:

 // Assignment Context
 AsyncIterator<ByteBuffer> asyncIter = source::read;
 asyncIter.forEach(...);

 // Casting Context
 ((AsyncIterator<ByteBuffer>)source::read)
     .forEach(...);

 // Invocation Context
 AsyncIterator.by(source::read)
     .forEach(...);

Третий вариант выглядит лучше, чем два других, и цель этого метода.

Ответ 5

Вы можете использовать литой

Stream.of("ciao", "hola", "hello")
        .map(((Function<String, Integer>) String::length)
                .andThen(n -> n * 2))
        .forEach(System.out::println);

печатает

8
8
10

Ответ 6

Вы можете написать:

Stream.of("ciao", "hola", "hello").map(String::length).map(n -> n * 2);

Ответ 7

Мы можем использовать функцию reduce для объединения нескольких функций в одну.

public static void main(String[] args) {
    List<Function<String, String>> normalizers = Arrays.asList(
    str -> {
        System.out.println(str);
        return str;
    }, 
    String::toLowerCase,
    str -> {
        System.out.println(str);
        return str;
    },
    String::trim,
    str -> {
        System.out.println(str);
        return str;
    });

    String input = " Hello World ";
    normalizers.stream()
               .reduce(Function.identity(), (a, b) -> a.andThen(b))
               .apply(input);
}

Выход:

 Hello World 
 hello world 
hello world