2015-09-16 19 views
5

Nếu tôi tạo ra một giao diện chức năng:Làm thế nào để trực tiếp sử dụng một chức năng như một loại chức năng trong Java 8

@FunctionalInterface 
public class Consumer2<T1, T2> { 
    void accept(T1 t1, T2 t2); 

    default Consumer1<T2> curry(T1 t1) { 
     return (t2) -> accept(t1, t2); 
    } 
} 

Bây giờ, nếu tôi có một lớp:

public class MyClass { 
    public void printStrings(String a, String b) { 
     System.out.println(a + ": " + b); 
    } 
} 

MyClass myClass = new MyClass(); 

Bây giờ, nếu tôi muốn sử dụng giao diện chức năng của tôi, tôi có thể:

Consumer2<String, String> printString = myClass::printStrings; 
printString.curry("hello").accept("world"); 

Nhưng tôi không thể làm điều gì đó như:

myClass::printStrings.curry("hello").accept("world"); 

có ý nghĩa, bởi vì Java không có cách nào biết rằng myClass::printStrings có thể được áp dụng cho giao diện chức năng Consumer2. Để làm điều này, tôi đã tạo ra một lớp tiện ích:

public class F { 
    public static <T1, T2> Consumer2<T1, T2> c2(Consumer2<T1, T2> fn) { 
     return fn; 
    } 
} 

Sau đó, tôi có thể:

F.c2(myClass::printStrings).curry("hello").accept("world"); 

Thậm chí, điều này sẽ làm việc:

((Consumer2<String, String>)myClass::printStrings).curry("hello").accept("world"); 

Chừng nào có một số cách cho Java 8 để hiểu rằng loại chức năng trong trường hợp này. Vì vậy, câu hỏi là, cách tốt nhất để làm điều đó là gì, trong khi có thể tránh được bản mẫu?

+1

Tôi đoán lớp 'Consumer2' được coi là 'giao diện Consumer2' ... – Holger

Trả lời

2

Giải pháp của bạn liên quan đến phương thức F.c2 thú vị, nhưng ví dụ của bạn quá nhân tạo. Nếu bạn hỏi, làm thế nào để viết tốt hơn mã này

F.c2(myClass::printStrings).curry("hello").accept("world"); 

Sau đó, tôi chắc chắn sẽ khuyên bạn nên viết như thế này:

myClass.printStrings("hello", "world"); 

Nếu bạn muốn hỏi làm thế nào để liên kết các thông số định sẵn cho tài liệu tham khảo phương pháp , Tôi khuyên bạn nên sử dụng hàm lambda thay thế:

Consumer1<String> fn = str -> myClass.printStrings("hello", str); 
fn.accept("world"); 

Có thể bạn muốn xem xét trường hợp khi chức năng của bạn không được biết trong thời gian biên dịch. Trong trường hợp này, nó được trả về từ một phương thức khác hoặc được truyền cho phương thức hiện tại làm tham số phương thức hoặc được lưu trữ trong biến/trường. Trong tất cả các trường hợp này, nó đã là giao diện chức năng và bạn có thể sử dụng trực tiếp curry:

Consumer2<String, String> getConsumer2() { return myClass::printStrings; } 

getConsumer2().curry("hello").accept("world"); 

Vì vậy, nói chung tôi không thấy vấn đề ở đây. Nếu bạn vẫn nghĩ rằng việc áp dụng tách lạng bộ tài liệu tham khảo phương pháp rất hữu ích, tôi sẽ tạo ra một curry phương pháp tĩnh (mặc dù tôi đoán, nó thực sự là một "ràng buộc", không phải "cà ri") trong giao diện Consumer1:

static <T1, T2> Consumer1<T2> curry(Consumer2<T1, T2> c2, T1 t1) { 
    return c2.curry(t1); 
} 

Và sử dụng nó như thế này:

Consumer1.curry(myClass::printStrings, "hello").accept("world"); 
4

Bạn không phải currying nhưng thực hiện một phần ứng dụng chức năng. Các hoạt động này có liên quan, nhưng không giống nhau. Currying có nghĩa là biến đổi số Consumer2<T1, T2> thành số Function<T1,Consumer1<T2>>. Khi áp dụng hàm đã kết hợp đó vào giá trị T1, bạn sẽ nhận được những gì mà phương thức của bạn đang thực hiện hiệu quả.

Dễ dàng hơn khi sử dụng tên được thiết lập bind khi ràng buộc giá trị cho tham số của hàm là thứ gì đó, mọi nhà phát triển đều hiểu mà không cần phải đi sâu vào thế giới lập trình hàm.

Điều đó nói rằng, tốt nhất là hãy nhớ rằng hiện tại interface s có thể có phương thức static, vì vậy không cần các lớp tiện ích như vậy. Hơn nữa, một phương pháp static chỉ trả về đối số của nó là ít sử dụng riêng của nó, vì vậy bạn có thể kết hợp nó với phương pháp tiếp theo mà nó được cho là để phục vụ. Sau đó, nó thực hiện các mục đích tương tự như phương pháp dụ và có thể được cung cấp như là một tình trạng quá tải đơn giản:

@FunctionalInterface 
public interface Consumer2<T1, T2> { 
    void accept(T1 t1, T2 t2); 

    default Consumer1<T2> bind(T1 t1) { 
     return bind(this, t1); 
    } 
    static <T,U> Consumer1<U> bind(Consumer2<? super T, ? super U> c, T t) { 
     return u -> c.accept(t, u); 
    } 
} 
public interface Consumer1<T1> extends Consumer<T1> {} 

public class MyClass { 
    public static void printStrings(String a, String b) { 
     System.out.println(a + ": " + b); 
    } 

    public static void main(String[] args) { 
     Consumer2.bind(MyClass::printStrings, "hello").accept("world"); 
    } 
} 

Mặt khác, khi bạn sử dụng hiện tiêu chuẩn interface s ConsumerBiConsumer bạn không có lựa chọn nào khác để phục vụ một phương thức tiện ích trong một lớp khác với interface s này. Nhưng tin tốt là nó dễ dàng để làm cho các giải pháp phù hợp sau đó, như bạn không thể cung cấp bất cứ điều gì khác ngoài một phương pháp static:

class FunctionUtil { 
    static <T,U> Consumer<U> bind(BiConsumer<? super T, ? super U> c, T t) { 
     return u -> c.accept(t, u); 
    } 
} 
public class MyClass { 
    public static void printStrings(String a, String b) { 
     System.out.println(a + ": " + b); 
    } 

    public static void main(String[] args) { 
     FunctionUtil.bind(MyClass::printStrings, "hello").accept("world"); 
    } 
} 
Các vấn đề liên quan