27

Dlaczego dostawcy obsługują tylko konstruktory no-arg?Java 8 Dostawca z argumentami w konstruktorze

Jeżeli konstruktor domyślny jest obecny, mogę to zrobić:

create(Foo::new) 

Ale jeśli tylko konstruktor pobiera ciąg, muszę to zrobić:

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

W jaki sposób kompilator może odgadnąć, że argument ma być "cześć"? – assylias

+1

To jest moje pytanie, chciałem się dowiedzieć, czy możliwe jest poinformowanie Dostawcy, że "cześć" jest argumentem dla konstruktora. Próba wykonania tego może być złym zwyczajem w programowaniu funkcjonalnym i może się myliłem nawet w myśleniu o tym, ale wiedząc, dlaczego tego nie robić, nie zasługuję na jedno zdanie. Imo – cahen

+3

Twoje pytanie po prostu nie ma sensu. Piszesz "Dlaczego dostawcy pracują tylko z konstruktorami bez-arg?", A następnie udowodnisz, że 'Dostawca' * działa * z dostarczonymi argumentami, np. Przy użyciu wyrażenia lambda. Wydaje się więc, że twoje aktualne pytanie brzmi "dlaczego odniesienie do metody działa tylko wtedy, gdy parametry funkcjonalne pasują do parametrów docelowych", a odpowiedź brzmi, ponieważ do tego właśnie odnoszą się odwołania do metod. Jeśli lista parametrów nie jest zgodna, użyj wyrażenia lambda, które już pokazałeś w swoim pytaniu. Ponieważ to właśnie jest wyrażenie lambda (nie wyłącznie) ... – Holger

Odpowiedz

34

To tylko ograniczenie składnia odwołania do metody - nie można przekazać żadnego z argumentów. Tak właśnie działa składnia.

9

Dlaczego dostawcy działają tylko z konstruktorami bez argumentów?

Ponieważ 1-Arg konstruktor jest izomorficzny z interfejsem SAM z 1 argumentu i 1 wartości powrotu, takie jak java.util.function.Function<T,R> jest R apply(T).

Z drugiej strony Supplier<T> 's T get() jest izomorficznym konstruktorem zerowym.

Są po prostu niezgodne. Zarówno metoda create() musi być polimorficzna, aby akceptować różne interfejsy funkcjonalne i działać inaczej, w zależności od tego, które argumenty są dostarczane lub trzeba napisać ciało lambda, aby działał jako kod kleju między dwoma sygnaturami.

Jakie są twoje niezaspokojone oczekiwania tutaj? Co stanie się, Twoim zdaniem, ,?

34

Ale konstruktor 1-arg dla T że trwa String jest kompatybilny z Function<String,T>:

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

Który konstruktor zostanie wybrany jest traktowana jako problem wyboru przeciążenie, w oparciu o kształcie typu docelowego.

+0

Dobra odpowiedź. Dokładnie to, czego szukałem ... z wyjątkiem ... to nie działa w moim [przypadku] (https://stackoverflow.com/questions/47434766/why-does-a-meteth-reference-to-ctor- wymagać rzutów) ?! – GhostCat

24

Jeśli lubisz referencje Metoda tyle można napisać metodę bind przez siebie i używać go:

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

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

Interfejs Supplier<T> reprezentuje funkcję z podpisem () -> T, co oznacza, że ​​nie wymaga żadnych parametrów i powroty coś w rodzaju T. Odniesienia metod, które podajesz jako argumenty, muszą zostać podpisane, aby zostać przekazanym.

Jeśli chcesz utworzyć Supplier<Foo>, który działa z konstruktorem, możesz użyć ogólnej metody wiązania, którą sugeruje @Tagir Valeev, lub bardziej wyspecjalizowany.

Jeśli chcesz, aby Supplier<Foo> zawsze używał tego ciągu "hello", możesz zdefiniować go na dwa różne sposoby: jako metodę lub zmienną Supplier<Foo>.

metoda:

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

zmienna:

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

można przekazać w metodzie z odniesieniem metodą (create(WhateverClassItIsOn::makeFoo);), a zmienna może być przekazywana w po prostu używając nazwy create(WhateverClassItIsOn.makeFoo);.

Ta metoda jest nieco bardziej preferowana, ponieważ jest łatwiejsza w użyciu poza kontekstem, w którym jest przekazywana jako odniesienie do metody, a także może być używana w instancji, która wymaga osobnego specjalistycznego interfejsu funkcjonalnego, który jest również w szczególności () -> T lub jest to () -> Foo.

Jeśli chcesz użyć Supplier który może przybrać dowolny ciąg znaków jako argumentu, należy użyć coś takiego sposobu wiązania @Tagir wspomniano, pomijając konieczność dostarczenia Function:

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

można przekazać to jako taki argument: create(makeFooFromString("hello"));

Chociaż, być może powinieneś zmienić wszystkie wywołania "make ...", aby wywoływać ... "tylko po to, żeby było trochę bardziej zrozumiałe.

0

Sparuj dostawcę z interfejsem FunctionalInterface.

Oto przykładowy kod, który zestawiłem, aby zademonstrować "powiązanie" odwołania do konstruktora z określonym konstruktorem za pomocą funkcji, a także różne sposoby definiowania i wywoływania "fabrycznych" referencji konstruktora.

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); 
     } 
    } 
}