Чому постачальники підтримують лише конструктори без аргументів?
Якщо присутній конструктор за замовчуванням, я можу зробити це:
create(Foo::new)
Але якщо єдиний конструктор бере рядок, я повинен зробити це:
create(() -> new Foo("hello"))
Чому постачальники підтримують лише конструктори без аргументів?
Якщо присутній конструктор за замовчуванням, я можу зробити це:
create(Foo::new)
Але якщо єдиний конструктор бере рядок, я повинен зробити це:
create(() -> new Foo("hello"))
Supplier
робить роботу з поставленими аргументами, тобто при використанні лямбда - вираження. Отож здається, що ваше фактичне запитання: "чому посилання на метод працює, лише якщо функціональні параметри відповідають цільовим параметрам", і відповідь така, адже саме для цього призначені посилання на методи. Якщо список параметрів не збігається, використовуйте лямбда-вираз, як ви вже показали у своєму запитанні. Тому що для цього призначений вираз лямбда (не виключно) ...
Відповіді:
Це лише обмеження синтаксису посилання на метод - яке ви не можете передати в жодному з аргументів. Це просто те, як працює синтаксис.
Але конструктор 1-arg для T
цього приймає a String
сумісний з Function<String,T>
:
Function<String, Foo> fooSupplier = Foo::new;
Вибраний конструктор розглядається як проблема вибору перевантаження на основі форми цільового типу.
Якщо вам так подобаються посилання на методи, ви можете написати bind
метод самостійно і використовувати його:
public static <T, R> Supplier<R> bind(Function<T,R> fn, T val) {
return () -> fn.apply(val);
}
create(bind(Foo::new, "hello"));
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"));
Хоча, можливо, вам слід змінити всі дзвінки "зробити ..." на дзвінки "поставити ...", щоб зробити це трохи зрозумілішим.
Чому постачальники працюють лише з конструкторами без аргументів?
Оскільки 1-аргументний конструктор ізоморфний інтерфейсу SAM з 1 аргументом та 1 повернутим значенням, наприклад, java.util.function.Function<T,R>
s R apply(T)
.
З іншого боку Supplier<T>
, T get()
він ізоморфний конструктору нульових аргументів.
Вони просто не сумісні. Або ваш create()
метод повинен бути поліморфним, щоб приймати різні функціональні інтерфейси і діяти по-різному, залежно від того, які аргументи подані, або вам потрібно написати лямбда-тіло, щоб діяти як код склеювання між двома підписами.
Яке ваше незадоволене сподівання тут? Що має статися на вашу думку?
Поєднайте постачальника з функціональним інтерфейсом.
Ось зразок коду, який я зібрав, щоб продемонструвати "прив'язку" посилання на конструктор до конкретного конструктора з функцією, а також різні способи визначення та виклику посилань на конструктор "фабрика".
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);
}
}
}
Шукаючи рішення параметризованої 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);
}
завжди можливо. Чи має це сенс у контексті чи ні, залежить.
Як також було описано вище, для посилань статичного методу потрібні номер відповідного методу та тип повернення / параметрів, щоб відповідати очікуваним методом, що споживає функції (потік).
Якщо у вас є конструктор для, 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);
}
}