Dostawca Java 8 z argumentami w konstruktorze


82

Dlaczego dostawcy wspierają tylko konstruktorów bez argonu?

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

create(Foo::new)

Ale jeśli jedyny konstruktor przyjmuje String, muszę to zrobić:

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

9
Jak kompilator mógł zgadnąć, że argumentem jest „cześć”?
assylias

6
Twoje pytanie po prostu nie ma sensu. Piszesz: „Dlaczego dostawcom tylko pracę bez-Arg konstruktorów?”, A następnie udowodnić sobie, że Supplier czyni pracę z dostarczonych argumentów, czyli 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 są zgodne z parametrami docelowymi”, a odpowiedź brzmi: ponieważ do tego służą odwołania do metod. Jeśli lista parametrów nie jest zgodna, użyj wyrażenia lambda, jak już pokazałeś w swoim pytaniu. Ponieważ do tego służy wyrażenie lambda (nie tylko)…
Holger

Odpowiedzi:


62

To tylko ograniczenie składni odwołania do metody - nie możesz przekazać żadnego z argumentów. Po prostu działa składnia.


69

Ale konstruktor 1-argowy do Ttego przyjmuje a Stringjest zgodny z Function<String,T>:

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

Wybrany konstruktor jest traktowany jako problem z wyborem przeciążenia w oparciu o kształt typu docelowego.


47

Jeśli tak bardzo lubisz odwołania do metod, możesz napisać bindmetodę samodzielnie i użyć jej:

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>Interfejs reprezentuje funkcję z podpisem () -> T, co oznacza, że nie wymaga żadnych parametrów i zwraca coś typu T. Odwołania do metod, które podajesz jako argumenty, muszą następować po tym podpisie, aby mogły zostać przekazane.

Jeśli chcesz stworzyć coś, Supplier<Foo>co działa z konstruktorem, możesz użyć ogólnej metody bind, którą sugeruje @Tagir Valeev, lub stworzyć bardziej wyspecjalizowaną.

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

metoda:

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

zmienna:

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

Możesz przekazać metodę za pomocą odwołania do metody ( create(WhateverClassItIsOn::makeFoo);), a zmienną można przekazać po prostu za pomocą nazwy create(WhateverClassItIsOn.makeFoo);.

Ta metoda jest nieco bardziej preferowana, ponieważ jest łatwiejsza do użycia poza kontekstem, ponieważ jest przekazywana jako odniesienie do metody, a także może być używana w przypadku, gdy ktoś wymaga własnego wyspecjalizowanego interfejsu funkcjonalnego, który jest również () -> Tlub jest () -> Fookonkretny .

Jeśli chcesz użyć Supplierargumentu, który może przyjmować dowolny ciąg znaków jako argument, powinieneś użyć czegoś takiego jak wspomniana metoda bind @Tagir, omijając potrzebę podania Function:

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

Możesz przekazać to jako argument w następujący sposób: create(makeFooFromString("hello"));

Chociaż, może powinieneś zmienić wszystkie wywołania „wykonaj…” na „dostarczaj…”, żeby było to trochę jaśniejsze.


12

Dlaczego dostawcy współpracują tylko z konstruktorami bez argonu?

Ponieważ konstruktor 1-argowy jest izomorficzny z interfejsem SAM z 1 argumentem i 1 wartością zwracaną, taką jak java.util.function.Function<T,R>'s R apply(T).

Z drugiej strony Supplier<T>„s T get()jest izomorficzna z konstruktora zerowy arg.

Po prostu nie są kompatybilne. Albo twoja create()metoda musi być polimorficzna, aby akceptować różne interfejsy funkcjonalne i działać inaczej w zależności od dostarczonych argumentów, albo musisz napisać treść lambda, która będzie działać jako kod klejący między dwoma podpisami.

Jakie jest twoje niespełnione oczekiwanie? Co Twoim zdaniem powinno się wydarzyć?


3
To byłaby lepsza odpowiedź, gdyby została napisana z nieco większym naciskiem na komunikację. Posiadanie zarówno „izomorficznego”, jak i „interfejsu SAM” w pierwszym zdaniu wydaje się przesadą w przypadku witryny, która istnieje po to, aby pomóc ludziom w czymś, czego nie rozumieją.
L. Blanc

1

Sparuj dostawcę z FunctionalInterface.

Oto przykładowy kod, który utworzyłem, aby zademonstrować „wiązanie” odniesienia konstruktora do określonego konstruktora za pomocą funkcji Function, a także różne sposoby definiowania i wywoływania odniesień do konstruktora „fabrycznego”.

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

Szukając rozwiązania sparametryzowanego Supplier problemu, powyższe odpowiedzi okazały się pomocne i zastosowałem sugestie:

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

Jest wywoływany w ten sposób:

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

Nie całkiem zadowolony z obfitego parametru funkcji statycznej, sięgnąłem dalej i za pomocą funkcji Function.identity () doszedłem do następującego wyniku:

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

Wywołanie teraz bez statycznego parametru funkcji:

failedMessageSupplier(msgPrefix, customMsg)

Ponieważ Function.identity()zwraca funkcję typuObject , podobnie jak późniejsze wywołanie funkcji apply(msgString), Stringwymagane jest rzutowanie na - lub jakikolwiek typ, który jest podawany za pomocą metody apply ().

Metoda ta pozwala np. Na użycie wielu parametrów, dynamiczne przetwarzanie napisów, przedrostki stałych łańcuchowych, sufiksy i tak dalej.

Używanie tożsamości powinno teoretycznie mieć również niewielką przewagę nad String :: new, który zawsze tworzy nowy ciąg.

Jak już zauważył Jacob Zimmerman, prostsza sparametryzowana forma

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

jest zawsze możliwe. To, czy ma to sens w kontekście, zależy, czy nie.

Jak również opisano powyżej, wywołania odwołania do metody statycznej wymagają, aby numer odpowiedniej metody i typ zwracanych parametrów / parametrów odpowiadał oczekiwaniom metody pochłaniającej funkcję (strumień).


0

Jeśli masz konstruktora new Klass(ConstructorObject), możesz użyć Function<ConstructorObject, Klass>tego:

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);
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.