27

Tại sao các nhà cung cấp chỉ hỗ trợ các nhà thầu không có arg?Nhà cung cấp Java 8 với các đối số trong hàm dựng

Nếu constructor mặc định là hiện nay, tôi có thể làm điều này:

create(Foo::new) 

Nhưng nếu các nhà xây dựng chỉ mất một String, tôi phải làm điều này:

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

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

+1

đó là câu hỏi của tôi, tôi muốn biết nếu nó có thể để cho các nhà cung cấp biết bằng cách nào đó rằng "hello" là một đối số cho các nhà xây dựng. Cố gắng làm điều này có thể là một thực hành không tốt trong lập trình hàm và tôi có thể sai khi nghĩ về nó, nhưng biết tại sao không làm điều đó không xứng đáng với một câu trả lời là – cahen

+3

Câu hỏi của bạn đơn giản là không có ý nghĩa. Bạn viết “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 có arg?”, Sau đó bạn tự chứng minh rằng một 'Nhà cung cấp' * làm việc với các đối số được 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 phương pháp tham chiếu chỉ hoạt động nếu các tham số chức năng khớp với thông số đích" và câu trả lời là vì đó là phương pháp tham chiếu phương pháp. 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à biểu thức lambda dành cho (không độc quyền)… – Holger

Trả lời

34

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

9

Tại sao các nhà cung cấp chỉ làm việc với các nhà thầu không có arg?

Vì hàm tạo 1-arg là đẳng cấu đối với giao diện SAM có 1 đối số và 1 giá trị trả lại, chẳng hạn nhưcủa java.util.function.Function<T,R>.

Mặt khác, Supplier<T> 'T get() là đẳng cấu đối với một hàm tạo zero arg.

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

Kỳ vọng không mong đợi của bạn ở đây là gì? Điều gì nên xảy ra theo ý kiến ​​của bạn?

34

Nhưng, một constructor 1-arg cho T mà phải mất một String tương thích với Function<String,T>:

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

Những nhà xây dựng được chọn sẽ được coi là một vấn đề lựa chọn quá tải, dựa trên hình dạng của các loại mục tiêu.

+0

Câu trả lời hay. Chính xác những gì tôi đang tìm kiếm ... ngoại trừ ... nó không hoạt động trong trường hợp của tôi [https://stackoverflow.com/questions/47434766/why-does-a-method-reference-to-ctor- yêu cầu ném) ?! – GhostCat

24

Nếu bạn thích tài liệu tham khảo phương pháp rất nhiều, bạn có thể viết một phương pháp bind một mình 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")); 
6

Giao diện Supplier<T> đạ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ó thông số và lợi nhuận một cái gì đó của loại 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 Supplier<Foo> 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 làm cho một chuyên ngành hơn.

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

phương pháp:

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

biến:

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

Bạn có thể vượt qua trong các phương pháp với một tài liệu tham khảo phương pháp (create(WhateverClassItIsOn::makeFoo);), và các biến thể được thông qua trong chỉ đơn giản bằng cách sử dụng tên create(WhateverClassItIsOn.makeFoo);.

Phương pháp này thích hợp hơn một chút vì dễ sử dụng bên ngoài ngữ cảnh được tham chiếu như phương thức tham chiếu phương pháp và 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 riêng của họ cũng là () -> T hoặc là () -> Foo cụ thể.

Nếu bạn muốn sử dụng một Supplier có thể mất bất kỳ String như một đối số, bạn nên sử dụng một cái gì đó giống như phương pháp ràng buộc @Tagir đề cập, bỏ qua sự cần thiết phải cung cấp các Function:

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

Bạn có thể vượt qua đây là một đối số như sau: create(makeFooFromString("hello"));

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

0

Ghép nối Nhà cung cấp với một FunctionalInterface. Dưới đây là một số mã mẫu tôi đặt cùng nhau để chứng minh "ràng buộc" một tham chiếu hàm tạo cho một hàm tạo cụ thể với Hàm và các cách khác nhau để định nghĩa và gọi tham chiếu hàm dựng "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); 
     } 
    } 
} 
Các vấn đề liên quan