Постачальник Java 8 з аргументами в конструкторі


82

Чому постачальники підтримують лише конструктори без аргументів?

Якщо присутній конструктор за замовчуванням, я можу зробити це:

create(Foo::new)

Але якщо єдиний конструктор бере рядок, я повинен зробити це:

create(() -> new Foo("hello"))

9
Як компілятор міг здогадатися, що аргумент повинен бути "привіт"?
assylias

6
Ваше запитання просто не має сенсу. Ви пишете : «Чому постачальникам працювати тільки з без аргументів конструкторам?», То довести собі , що Supplier робить роботу з поставленими аргументами, тобто при використанні лямбда - вираження. Отож здається, що ваше фактичне запитання: "чому посилання на метод працює, лише якщо функціональні параметри відповідають цільовим параметрам", і відповідь така, адже саме для цього призначені посилання на методи. Якщо список параметрів не збігається, використовуйте лямбда-вираз, як ви вже показали у своєму запитанні. Тому що для цього призначений вираз лямбда (не виключно) ...
Холгер

Відповіді:


62

Це лише обмеження синтаксису посилання на метод - яке ви не можете передати в жодному з аргументів. Це просто те, як працює синтаксис.


69

Але конструктор 1-arg для Tцього приймає a Stringсумісний з Function<String,T>:

Function<String, Foo> fooSupplier = Foo::new;

Вибраний конструктор розглядається як проблема вибору перевантаження на основі форми цільового типу.


47

Якщо вам так подобаються посилання на методи, ви можете написати bindметод самостійно і використовувати його:

public static <T, R> Supplier<R> bind(Function<T,R> fn, T val) {
    return () -> fn.apply(val);
}

create(bind(Foo::new, "hello"));

14

Supplier<T>Інтерфейс являє собою функцію з підписом () -> T, тобто він не приймає ніяких параметрів і повертає що - то типу T. Посилання на методи, які ви надаєте як аргументи, повинні слідувати за цим підписом, щоб бути переданими.

Якщо ви хочете створити такий, Supplier<Foo>що працює з конструктором, ви можете скористатися загальним методом прив'язки, який пропонує @Tagir Valeev, або зробити більш спеціалізований.

Якщо ви хочете, Supplier<Foo>щоб завжди використовував цей "hello"рядок, ви можете визначити його одним із двох різних способів: як метод або Supplier<Foo>змінну.

метод:

static Foo makeFoo() { return new Foo("hello"); }

змінна:

static Supplier<Foo> makeFoo = () -> new Foo("hello");

Ви можете передати метод із посиланням на метод ( create(WhateverClassItIsOn::makeFoo);), а змінну можна просто передати, використовуючи ім'я create(WhateverClassItIsOn.makeFoo);.

Метод є трохи кращим, оскільки його простіше використовувати поза контекстом передачі як посилання на метод, а також його можна використовувати в тому випадку, коли комусь потрібен власний спеціалізований функціональний інтерфейс, який також () -> Tє або є () -> Fooспеціально .

Якщо ви хочете використовувати Supplierаргумент, який може прийняти будь-який рядок, вам слід використовувати щось на зразок методу прив'язки @Tagir, про який згадується, минаючи необхідність вводити Function:

Supplier<Foo> makeFooFromString(String str) { return () -> new Foo(str); }

Ви можете передати це як такий аргумент: create(makeFooFromString("hello"));

Хоча, можливо, вам слід змінити всі дзвінки "зробити ..." на дзвінки "поставити ...", щоб зробити це трохи зрозумілішим.


12

Чому постачальники працюють лише з конструкторами без аргументів?

Оскільки 1-аргументний конструктор ізоморфний інтерфейсу SAM з 1 аргументом та 1 повернутим значенням, наприклад, java.util.function.Function<T,R>s R apply(T).

З іншого боку Supplier<T>, T get()він ізоморфний конструктору нульових аргументів.

Вони просто не сумісні. Або ваш create()метод повинен бути поліморфним, щоб приймати різні функціональні інтерфейси і діяти по-різному, залежно від того, які аргументи подані, або вам потрібно написати лямбда-тіло, щоб діяти як код склеювання між двома підписами.

Яке ваше незадоволене сподівання тут? Що має статися на вашу думку?


3
Це була б краща відповідь, якби вона була написана з трохи більшим акцентом на спілкуванні. Наявність у першому реченні "ізоморфного" та "інтерфейсу SAM" здається надмірним для сайту, який існує, щоб допомогти людям у чомусь, чого вони не розуміють.
Л. Блан,

1

Поєднайте постачальника з функціональним інтерфейсом.

Ось зразок коду, який я зібрав, щоб продемонструвати "прив'язку" посилання на конструктор до конкретного конструктора з функцією, а також різні способи визначення та виклику посилань на конструктор "фабрика".

import java.io.Serializable;
import java.util.Date;

import org.junit.Test;

public class FunctionalInterfaceConstructor {

    @Test
    public void testVarFactory() throws Exception {
        DateVar dateVar = makeVar("D", "Date", DateVar::new);
        dateVar.setValue(new Date());
        System.out.println(dateVar);

        DateVar dateTypedVar = makeTypedVar("D", "Date", new Date(), DateVar::new);
        System.out.println(dateTypedVar);

        TypedVarFactory<Date, DateVar> dateTypedFactory = DateVar::new;
        System.out.println(dateTypedFactory.apply("D", "Date", new Date()));

        BooleanVar booleanVar = makeVar("B", "Boolean", BooleanVar::new);
        booleanVar.setValue(true);
        System.out.println(booleanVar);

        BooleanVar booleanTypedVar = makeTypedVar("B", "Boolean", true, BooleanVar::new);
        System.out.println(booleanTypedVar);

        TypedVarFactory<Boolean, BooleanVar> booleanTypedFactory = BooleanVar::new;
        System.out.println(booleanTypedFactory.apply("B", "Boolean", true));
    }

    private <V extends Var<T>, T extends Serializable> V makeVar(final String name, final String displayName,
            final VarFactory<V> varFactory) {
        V var = varFactory.apply(name, displayName);
        return var;
    }

    private <V extends Var<T>, T extends Serializable> V makeTypedVar(final String name, final String displayName, final T value,
            final TypedVarFactory<T, V> varFactory) {
        V var = varFactory.apply(name, displayName, value);
        return var;
    }

    @FunctionalInterface
    static interface VarFactory<R> {
        // Don't need type variables for name and displayName because they are always String
        R apply(String name, String displayName);
    }

    @FunctionalInterface
    static interface TypedVarFactory<T extends Serializable, R extends Var<T>> {
        R apply(String name, String displayName, T value);
    }

    static class Var<T extends Serializable> {
        private String name;
        private String displayName;
        private T value;

        public Var(final String name, final String displayName) {
            this.name = name;
            this.displayName = displayName;
        }

        public Var(final String name, final String displayName, final T value) {
            this(name, displayName);
            this.value = value;
        }

        public void setValue(final T value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return String.format("%s[name=%s, displayName=%s, value=%s]", getClass().getSimpleName(), this.name, this.displayName,
                    this.value);
        }
    }

    static class DateVar extends Var<Date> {
        public DateVar(final String name, final String displayName) {
            super(name, displayName);
        }

        public DateVar(final String name, final String displayName, final Date value) {
            super(name, displayName, value);
        }
    }

    static class BooleanVar extends Var<Boolean> {
        public BooleanVar(final String name, final String displayName) {
            super(name, displayName);
        }

        public BooleanVar(final String name, final String displayName, final Boolean value) {
            super(name, displayName, value);
        }
    }
}

1

Шукаючи рішення параметризованої Supplierзадачі, я знайшов вищезазначені відповіді корисними та застосував пропозиції:

private static <T, R> Supplier<String> failedMessageSupplier(Function<String,String> fn, String msgPrefix, String ... customMessages) {
    final String msgString = new StringBuilder(msgPrefix).append(" - ").append(String.join("\n", customMessages)).toString();
    return () -> fn.apply(msgString);
}

Це викликається так:

failedMessageSupplier(String::new, msgPrefix, customMsg);

Ще не цілком задоволений рясним параметром статичної функції, я копався далі і за допомогою Function.identity () я прийшов до такого результату:

private final static Supplier<String> failedMessageSupplier(final String msgPrefix, final String ... customMessages) {
    final String msgString = new StringBuilder(msgPrefix).append(" - ").append(String.join("\n", customMessages)).toString();
    return () -> (String)Function.identity().apply(msgString);
}; 

Виклик тепер без параметра статичної функції:

failedMessageSupplier(msgPrefix, customMsg)

Оскільки Function.identity()повертає функцію типу Object, як і наступний виклик apply(msgString), Stringнеобхідний привід - або незалежно від типу, яким застосовується apply ().

Цей метод дозволяє, наприклад, використовувати декілька параметрів, динамічну обробку рядків, префікси рядкових констант, суфікси тощо.

Теоретично використання ідентичності повинно мати невелике перевагу над String :: new, що завжди створюватиме новий рядок.

Як вже зазначав Якоб Циммерман, простіша параметризована форма

Supplier<Foo> makeFooFromString(String str1, String str2) { 
    return () -> new Foo(str1, str2); 
}

завжди можливо. Чи має це сенс у контексті чи ні, залежить.

Як також було описано вище, для посилань статичного методу потрібні номер відповідного методу та тип повернення / параметрів, щоб відповідати очікуваним методом, що споживає функції (потік).


0

Якщо у вас є конструктор для, new Klass(ConstructorObject)тоді ви можете використовувати Function<ConstructorObject, Klass>так:

interface Interface {
    static Klass createKlass(Function<Map<String,Integer>, Klass> func, Map<String, Integer> input) {
        return func.apply(input);
    }
}
class Klass {
    private Integer integer;
    Klass(Map<String, Integer> map) {
        this.integer = map.get("integer");
    }
    public static void main(String[] args) {
        Map<String, Integer> input = new HashMap<>();
        input.put("integer", 1);
        Klass klazz = Interface.createKlass(Klass::new, input);
        System.out.println(klazz.integer);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.