コンストラクターに引数を持つJava8サプライヤー


82

サプライヤーが引数なしのコンストラクターのみをサポートするのはなぜですか?

デフォルトのコンストラクターが存在する場合、これを行うことができます。

create(Foo::new)

しかし、唯一のコンストラクターが文字列を取る場合、私はこれをしなければなりません:

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

9
コンパイラは、引数が「hello」であると想定されているとどのように推測できますか?
assylias 2015

6
あなたの質問は単に意味がありません。「サプライヤーが引数なしのコンストラクターでのみ機能するのはなぜですか?」と書くと、提供された引数でSupplier 機能することを証明します。つまり、ラムダ式を使用する場合です。したがって、実際の質問は「関数パラメータがターゲットパラメータと一致する場合にのみメソッド参照が機能するのはなぜですか」であり、その答えはメソッド参照の目的であるためです。パラメータリストが一致しない場合は、質問ですでに示したようにラムダ式を使用してください。それがラムダ式の目的であるため(排他的ではありません)…
Holger 2015

回答:


62

これは、メソッド参照構文の制限にすぎません。引数を渡すことはできません。これが構文の仕組みです。


69

しかし、1、引数のコンストラクタTそれがかかる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");

メソッドreference(create(WhateverClassItIsOn::makeFoo);)を使用してメソッドを渡すことができ、名前を使用するだけで変数を渡すことができますcreate(WhateverClassItIsOn.makeFoo);

メソッド参照として渡されるのコンテキストの外側を使用する方が簡単であるため、この方法は、少しより好ましく、また、誰もあり、独自の特殊な機能インタフェース必要とすることをインスタンスに使用することができるのです() -> Tか、ある() -> Foo具体的に。

Supplier任意の文字列を引数として取ることができるを使用する場合は、Function:を指定する必要性をバイパスして、前述のバインドメソッド@Tagirのようなものを使用する必要があります。

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

これを次のような引数として渡すことができます。 create(makeFooFromString("hello"));

ただし、少し明確にするために、すべての「make ...」呼び出しを「supply ...」呼び出しに変更する必要があるかもしれません。


12

サプライヤーが引数なしのコンストラクターのみを使用するのはなぜですか?

1引数のコンストラクターは、1つの引数と1つの戻り値(java.util.function.Function<T,R>'sなど)を持つSAMインターフェイスと同型であるためR apply(T)です。

一方、Supplier<T>'sT get()は、引数がゼロのコンストラクターと同型です。

それらは単に互換性がありません。create()メソッドは、さまざまな関数型インターフェイスを受け入れ、提供される引数に応じて異なる動作をするために多態性である必要があります。または、2つのシグネチャ間のグルーコードとして機能するラムダボディを作成する必要があります。

ここで満たされていない期待は何ですか?あなたの意見では何起こるべきですか?


3
コミュニケーションにもう少し重点を置いて書かれていれば、これはより良い答えになるでしょう。最初の文に「同形」と「SAMインターフェース」の両方があることは、理解できないことで人々を助けるために存在するサイトにとってはやり過ぎのように思えます。
L.・ブラン

1

サプライヤーをFunctionalInterfaceとペアリングします。

これは、コンストラクター参照を特定のコンストラクターに関数で「バインド」する方法と、「ファクトリー」コンストラクター参照を定義および呼び出すさまざまな方法を示すためにまとめたサンプルコードです。

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必要とされる- )または任意のタイプ、(適用を用いて供給されています。

この方法では、たとえば、複数のパラメーター、動的な文字列処理、文字列定数のプレフィックス、サフィックスなどを使用できます。

IDの使用は、理論的にはString :: newよりもわずかに優れている必要があります。これにより、常に新しい文字列が作成されます。

Jacob Zimmermanがすでに指摘しているように、より単純なパラメーター化された形式

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);
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.