Nhà cung cấp Java 8 với các đối số trong hàm tạo


81

Tại sao các nhà cung cấp chỉ hỗ trợ các nhà xây dựng không tranh luận?

Nếu phương thức khởi tạo mặc định hiện diện, tôi có thể thực hiện việc này:

create(Foo::new)

Nhưng nếu hàm tạo duy nhất nhận một Chuỗi, tôi phải làm điều này:

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

9
Làm thế nào trình biên dịch có thể đoán rằng đối số được cho là "xin chào"?
assylias

6
Câu hỏi của bạn chỉ đơn giản là không có ý nghĩa. Bạn viết “Tại sao các nhà cung cấp duy nhất làm việc với không-arg constructor?”, Sau đó bạn chứng minh mình là một Supplier không làm việc với các đối số cung cấp, tức là khi sử dụng một biểu thức lambda. Vì vậy, có vẻ như câu hỏi thực tế của bạn là "tại sao tham chiếu phương thức chỉ hoạt động nếu các tham số chức năng khớp với tham số mục tiêu" và câu trả lời là, bởi vì đó là tham chiếu phương thức dùng để làm gì. Nếu danh sách tham số không khớp, hãy sử dụng biểu thức lambda như bạn đã hiển thị trong câu hỏi của mình. Bởi vì đó là những gì biểu thức lambda dành cho (không riêng)…
Holger

Câu trả lời:


62

Đó chỉ là một giới hạn của cú pháp tham chiếu phương thức - mà bạn không thể chuyển vào bất kỳ đối số nào. Nó chỉ là cách thức hoạt động của cú pháp.


69

Tuy nhiên, hàm tạo 1-arg cho Trằng nhận a Stringtương thích với Function<String,T>:

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

Phương thức khởi tạo nào được chọn được coi như một vấn đề lựa chọn quá tải, dựa trên hình dạng của kiểu đích.


46

Nếu bạn thích tham chiếu phương thức nhiều như vậy, bạn có thể tự viết một bindphương thức và sử dụng nó:

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

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

14

Các Supplier<T>giao diện đại diện cho một chức năng với một chữ ký của () -> T, có nghĩa là nó sẽ không có tham số và trả về một cái gì đó kiểu T. Các tham chiếu phương thức mà bạn cung cấp làm đối số phải tuân theo chữ ký đó để được chuyển vào.

Nếu bạn muốn tạo một phương Supplier<Foo>thức hoạt động với hàm tạo, bạn có thể sử dụng phương pháp liên kết chung mà @Tagir Valeev đề xuất hoặc bạn tạo một phương thức chuyên biệt hơn.

Nếu bạn muốn một chuỗi Supplier<Foo>luôn sử dụng "hello"Chuỗi đó , bạn có thể xác định nó bằng một trong hai cách khác nhau: dưới dạng một phương thức hoặc một Supplier<Foo>biến.

phương pháp:

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

Biến đổi:

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

Bạn có thể truyền vào phương thức với một tham chiếu phương thức ( create(WhateverClassItIsOn::makeFoo);) và biến có thể được chuyển vào chỉ cần sử dụng tên create(WhateverClassItIsOn.makeFoo);.

Phương thức này được ưu tiên hơn một chút vì nó dễ sử dụng hơn bên ngoài ngữ cảnh được truyền dưới dạng tham chiếu phương thức và nó cũng có thể được sử dụng trong trường hợp ai đó yêu cầu giao diện chức năng chuyên biệt của riêng họ cũng được () -> Thoặc () -> Foocụ thể .

Nếu bạn muốn sử dụng một Suppliercó thể lấy bất kỳ Chuỗi nào làm đối số, bạn nên sử dụng một cái gì đó như phương thức ràng buộc @Tagir đã đề cập, bỏ qua nhu cầu cung cấp Function:

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

Bạn có thể chuyển nó như một đối số như sau: create(makeFooFromString("hello"));

Mặc dù, có lẽ bạn nên thay đổi tất cả các cuộc gọi "thực hiện ..." thành các cuộc gọi "cung cấp ...", chỉ để làm cho nó rõ ràng hơn một chút.


12

Tại sao các nhà cung cấp chỉ làm việc với các nhà xây dựng không tranh luận?

Bởi vì hàm tạo 1 đối số là đẳng cấu với giao diện SAM với 1 đối số và 1 giá trị trả về, chẳng hạn như java.util.function.Function<T,R>'s R apply(T).

Mặt khác Supplier<T>, T get()là đẳng cấu với một phương thức khởi tạo đối số không.

Chúng chỉ đơn giản là không tương thích. create()Phương thức của bạn cần phải đa hình để chấp nhận các giao diện chức năng khác nhau và hoạt động khác nhau tùy thuộc vào đối số nào được cung cấp hoặc bạn phải viết phần thân lambda để hoạt động như mã keo giữa hai chữ ký.

Kỳ vọng chưa được đáp ứng của bạn ở đây là gì? Điều gì sẽ xảy ra theo ý kiến ​​của bạn?


3
Đây sẽ là một câu trả lời tốt hơn nếu nó được viết với một chút nhấn mạnh vào giao tiếp. Có cả "isomorphic" và "SAM interface" trong câu đầu tiên có vẻ như quá mức cần thiết đối với một trang web tồn tại để giúp mọi người về điều gì đó mà họ không hiểu.
L. Blanc

1

Ghép nối Nhà cung cấp với Giao diện Chức năng.

Dưới đây là một số mã mẫu mà tôi đã tổng hợp lại để chứng minh "ràng buộc" một tham chiếu hàm tạo với một hàm tạo cụ thể với Hàm cũng như các cách khác nhau để xác định và gọi tham chiếu hàm tạo "nhà máy".

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

Khi tìm kiếm giải pháp cho Suppliervấn đề tham số hóa , tôi thấy các câu trả lời trên hữu ích và áp dụng các đề xuất:

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

Nó được gọi như thế này:

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

Vẫn chưa hoàn toàn hài lòng với tham số hàm tĩnh dồi dào, tôi đã tìm hiểu thêm và với Function.identity () , tôi đã đi đến kết quả sau:

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

Lời mời bây giờ không có tham số hàm tĩnh:

failedMessageSupplier(msgPrefix, customMsg)

Kể từ khi Function.identity()trở lại một chức năng của các loại Object, và do đó, các cuộc gọi tiếp theo của apply(msgString), một dàn diễn viên để Stringđược yêu cầu - hoặc bất cứ loại, áp dụng () đang được nuôi dưỡng bằng.

Phương thức này cho phép ví dụ như sử dụng nhiều tham số, xử lý chuỗi động, tiền tố hằng số chuỗi, hậu tố, v.v.

Việc sử dụng danh tính về mặt lý thuyết cũng có một chút lợi thế so với String :: new, điều này sẽ luôn tạo ra một chuỗi mới.

Như Jacob Zimmerman đã chỉ ra, dạng tham số đơn giản hơn

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

luôn luôn có thể. Điều này có ý nghĩa trong bối cảnh hay không là tùy thuộc.

Như đã mô tả ở trên, các lệnh gọi tham chiếu Phương thức tĩnh yêu cầu số lượng và kiểu trả về / tham số của phương thức tương ứng để khớp với những tham số mà phương thức sử dụng hàm (luồng) mong đợi.


0

Nếu bạn có một hàm tạo new Klass(ConstructorObject)thì bạn có thể sử dụng Function<ConstructorObject, Klass>như sau:

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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.