Làm cách nào để chỉ định các loại hàm cho các phương thức void (không phải Void) trong Java8?


143

Tôi đang chơi xung quanh với Java 8 để tìm hiểu chức năng của các công dân hạng nhất. Tôi có đoạn trích sau:

package test;

import java.util.*;
import java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

Những gì tôi đang cố gắng làm là truyền phương thức displayIntcho phương thức myForEachbằng cách sử dụng tham chiếu phương thức. Để trình biên dịch tạo ra lỗi sau:

src/test/Test.java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

Trình biên dịch phàn nàn rằng void cannot be converted to Void. Tôi không biết cách chỉ định loại giao diện chức năng trong chữ ký myForEachsao cho mã biên dịch. Tôi biết tôi chỉ đơn giản là có thể thay đổi kiểu trả về của displayIntđể Voidvà sau đó trở về null. Tuy nhiên, có thể có những tình huống không thể thay đổi phương pháp tôi muốn vượt qua ở một nơi khác. Có một cách dễ dàng để tái sử dụng displayIntnhư nó là?

Câu trả lời:


244

Bạn đang cố gắng sử dụng loại giao diện sai. Loại Hàm không phù hợp trong trường hợp này vì nó nhận được một tham số và có giá trị trả về. Thay vào đó, bạn nên sử dụng Consumer (trước đây gọi là Block)

Kiểu hàm được khai báo là

interface Function<T,R> {
  R apply(T t);
}

Tuy nhiên, loại Người tiêu dùng tương thích với loại bạn đang tìm kiếm:

interface Consumer<T> {
   void accept(T t);
}

Như vậy, Consumer tương thích với các phương thức nhận T và không trả lại gì (void). Và đây là những gì bạn muốn.

Chẳng hạn, nếu tôi muốn hiển thị tất cả các yếu tố trong danh sách, tôi có thể chỉ cần tạo một người tiêu dùng cho điều đó bằng biểu thức lambda:

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

Bạn có thể thấy ở trên rằng trong trường hợp này, biểu thức lambda nhận được một tham số và không có giá trị trả về.

Bây giờ, nếu tôi muốn sử dụng tham chiếu phương thức thay vì biểu thức lambda để tạo mức tiêu thụ của loại này, thì tôi cần một phương thức nhận Chuỗi và trả về void, phải không?.

Tôi có thể sử dụng các loại tham chiếu phương thức khác nhau, nhưng trong trường hợp này, hãy tận dụng tham chiếu phương thức đối tượng bằng cách sử dụng printlnphương thức trong System.outđối tượng, như sau:

Consumer<String> block = System.out::println

Hoặc tôi có thể đơn giản làm

allJedi.forEach(System.out::println);

Các printlnphương pháp là phù hợp vì nó nhận được một giá trị và có một kiểu trả về bãi bỏ, giống như các acceptphương pháp trong tiêu dùng.

Vì vậy, trong mã của bạn, bạn cần thay đổi chữ ký phương thức của mình thành một phần như:

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

Và sau đó, bạn sẽ có thể tạo một người tiêu dùng, bằng cách sử dụng tham chiếu phương thức tĩnh, trong trường hợp của bạn bằng cách thực hiện:

myForEach(theList, Test::displayInt);

Cuối cùng, bạn thậm chí có thể thoát khỏi myForEachphương pháp của mình hoàn toàn và chỉ cần làm:

theList.forEach(Test::displayInt);

Về chức năng là công dân hạng nhất

Tất cả đã được nói, sự thật là Java 8 sẽ không có các chức năng như các công dân hạng nhất vì một loại hàm cấu trúc sẽ không được thêm vào ngôn ngữ. Java đơn giản sẽ cung cấp một cách khác để tạo ra các triển khai các giao diện chức năng từ các biểu thức lambda và các tham chiếu phương thức. Cuối cùng các biểu thức lambda và các tham chiếu phương thức sẽ bị ràng buộc với các tham chiếu đối tượng, do đó tất cả những gì chúng ta có là các đối tượng là công dân hạng nhất. Điều quan trọng là chức năng có ở đó vì chúng ta có thể truyền các đối tượng làm tham số, ràng buộc chúng với các tham chiếu biến và trả về chúng dưới dạng các giá trị từ các phương thức khác, sau đó chúng phục vụ một mục đích tương tự.


1
Nhân tiện, Blockđã được thay đổi Consumertrong các bản phát hành mới nhất của API JDK 8
Edwin Dalorzo

4
Đoạn cuối nhấn mạnh một số sự kiện quan trọng: Các biểu thức và tham chiếu phương thức Lambda không chỉ là một bổ sung cú pháp. Chính JVM cung cấp các kiểu mới để xử lý các tham chiếu phương thức hiệu quả hơn so với các lớp ẩn danh (quan trọng nhất MethodHandle) và bằng cách này, trình biên dịch Java có thể tạo mã hiệu quả hơn, ví dụ như triển khai lambdas mà không đóng như các phương thức tĩnh, do đó ngăn chặn việc tạo ra các lớp học thêm.
Feuermurmel 17/12/14

7
Và phiên bản chính xác Function<Void, Void>Runnable.
OrangeDog

2
@OrangeDog Điều này không hoàn toàn đúng. Trong các bình luận RunnableCallablegiao diện, người ta viết rằng chúng được sử dụng cùng với các luồng . Hậu quả khó chịu của điều đó là một số công cụ phân tích tĩnh (như Sonar) sẽ khiếu nại nếu bạn gọi run()phương thức trực tiếp.
Denis

Xuất phát hoàn toàn từ nền tảng Java, tôi tự hỏi những trường hợp cụ thể mà Java không thể coi các chức năng là công dân hạng nhất ngay cả sau khi giới thiệu các giao diện chức năng và lambdas kể từ Java8 là gì?
Vivek Sethi

21

Khi bạn cần chấp nhận một hàm làm đối số không có đối số và không trả về kết quả (void), theo tôi, tốt nhất vẫn là có một cái gì đó như

  public interface Thunk { void apply(); }

ở đâu đó trong mã của bạn. Trong các khóa học lập trình chức năng của tôi, từ 'thunk' đã được sử dụng để mô tả các chức năng đó. Tại sao nó không có trong java.util.feft nằm ngoài tầm hiểu biết của tôi.

Trong các trường hợp khác, tôi thấy rằng ngay cả khi java.util.function có thứ gì đó phù hợp với chữ ký tôi muốn - nó vẫn không cảm thấy đúng khi đặt tên giao diện không khớp với việc sử dụng hàm trong mã của tôi. Tôi đoán đó là một điểm tương tự được đưa ra ở đâu đó ở đây về 'Runnable' - đó là một thuật ngữ liên quan đến lớp Thread - vì vậy trong khi nó có thể có chữ ký của anh ấy tôi cần, nó vẫn có khả năng gây nhầm lẫn cho người đọc.


Đặc biệt Runnablekhông cho phép các ngoại lệ được kiểm tra, làm cho nó trở nên vô dụng đối với nhiều ứng dụng. Vì Callable<Void>nó cũng không hữu ích, bạn cần xác định nó có vẻ như của riêng bạn. Xấu xí.
Jesse Glick

Liên quan đến vấn đề ngoại lệ được kiểm tra, các hàm mới của Java trong cuộc đấu tranh chung với điều đó. đó là một trình chặn lớn đối với một số thứ như Stream API. Thiết kế rất kém về phía họ.
2223059

0

Đặt loại trả về Voidthay vì voidreturn null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}

HOẶC LÀ

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});

-2

Tôi cảm thấy bạn nên sử dụng giao diện Người tiêu dùng thay vì Function<T, R>.

Người tiêu dùng về cơ bản là một giao diện chức năng được thiết kế để chấp nhận một giá trị và không trả lại gì (tức là void)

Trong trường hợp của bạn, bạn có thể tạo một người tiêu dùng ở nơi khác trong mã của bạn như thế này:

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

Sau đó, bạn có thể thay thế myForEachmã của mình bằng đoạn mã dưới đây:

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

Bạn coi myFactor là một đối tượng hạng nhất.


2
Điều này thêm gì ngoài các câu trả lời hiện có?
Matthew đọc
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.