จะระบุประเภทฟังก์ชั่นสำหรับวิธี void (ไม่ใช่ Void) ใน Java8 ได้อย่างไร?


143

ฉันกำลังเล่นกับ Java 8 เพื่อหาวิธีการทำงานในฐานะพลเมืองชั้นหนึ่ง ฉันมีตัวอย่างต่อไปนี้:

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

สิ่งที่ฉันพยายามทำคือส่งวิธีdisplayIntไปที่วิธีmyForEachโดยใช้การอ้างอิงวิธี ในการรวบรวมสร้างข้อผิดพลาดดังต่อไปนี้:

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

void cannot be converted to Voidคอมไพเลอร์บ่นว่า ฉันไม่ทราบวิธีระบุประเภทของฟังก์ชั่นอินเทอร์เฟซในลายเซ็นของmyForEachรหัสที่รวบรวม ฉันรู้ว่าฉันสามารถเปลี่ยนประเภทdisplayIntการVoidส่งคืนเป็นแล้วกลับnullได้ อย่างไรก็ตามอาจมีสถานการณ์ที่ไม่สามารถเปลี่ยนวิธีที่ฉันต้องการส่งผ่านที่อื่นได้ มีวิธีง่าย ๆ ที่จะนำมาใช้ซ้ำdisplayIntเหมือนเดิมหรือไม่?

คำตอบ:


244

คุณพยายามใช้ประเภทอินเตอร์เฟสที่ไม่ถูกต้อง ประเภทFunctionไม่เหมาะสมในกรณีนี้เนื่องจากได้รับพารามิเตอร์และมีค่าส่งคืน คุณควรใช้Consumer แทน (เดิมชื่อ Block)

ประเภทฟังก์ชั่นถูกประกาศเป็น

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

อย่างไรก็ตามประเภทผู้บริโภคเข้ากันได้กับที่คุณกำลังมองหา:

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

ดังนั้น Consumer จึงเข้ากันได้กับวิธีการที่ได้รับ T และคืนค่าอะไร (void) และนี่คือสิ่งที่คุณต้องการ

ตัวอย่างเช่นถ้าฉันต้องการแสดงองค์ประกอบทั้งหมดในรายการฉันสามารถสร้างผู้บริโภคสำหรับสิ่งนั้นด้วยการแสดงออกแลมบ์ดา:

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

คุณสามารถเห็นข้างบนว่าในกรณีนี้นิพจน์แลมบ์ดารับพารามิเตอร์และไม่มีค่าส่งคืน

ตอนนี้ถ้าฉันต้องการใช้การอ้างอิงวิธีการแทนการแสดงออกแลมบ์ดาเพื่อสร้างการบริโภคของประเภทนี้แล้วฉันต้องการวิธีที่ได้รับสตริงและส่งกลับเป็นโมฆะใช่มั้ย

ฉันสามารถใช้วิธีการอ้างอิงชนิดต่าง ๆ ได้ แต่ในกรณีนี้เราจะใช้ประโยชน์จากการอ้างอิงวิธีวัตถุโดยใช้printlnวิธีในSystem.outวัตถุดังนี้

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

หรือฉันสามารถทำได้

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

printlnวิธีการที่เหมาะสมเพราะมันได้รับความคุ้มค่าและมีชนิดกลับเป็นโมฆะเช่นเดียวกับacceptวิธีการในการบริโภค

ดังนั้นในรหัสของคุณคุณต้องเปลี่ยนวิธีการของคุณลายเซ็น:

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

และจากนั้นคุณควรจะสามารถสร้างผู้บริโภคโดยใช้การอ้างอิงวิธีคงที่ในกรณีของคุณโดยทำ:

myForEach(theList, Test::displayInt);

ท้ายที่สุดคุณสามารถกำจัดmyForEachวิธีการของคุณโดยสิ้นเชิงและทำ:

theList.forEach(Test::displayInt);

เกี่ยวกับฟังก์ชั่นเป็นพลเมืองชั้นหนึ่ง

ความจริงก็คือ Java 8 จะไม่ทำหน้าที่เป็นพลเมืองชั้นหนึ่งเนื่องจากฟังก์ชั่นโครงสร้างจะไม่ถูกเพิ่มเข้าไปในภาษา Java จะเสนอวิธีทางเลือกในการสร้างการใช้งานของส่วนต่อประสานการทำงานจากการแสดงออกแลมบ์ดาและการอ้างอิงวิธีการ การแสดงออกแลมบ์ดาและการอ้างอิงเมธอดจะถูกผูกไว้กับการอ้างอิงวัตถุดังนั้นสิ่งที่เรามีคือวัตถุที่เป็นพลเมืองชั้นหนึ่ง สิ่งสำคัญคือการทำงานมีเพราะเราสามารถส่งวัตถุเป็นพารามิเตอร์ผูกไว้กับการอ้างอิงตัวแปรและคืนพวกเขาเป็นค่าจากวิธีการอื่น ๆ แล้วพวกเขาก็ค่อนข้างให้บริการวัตถุประสงค์ที่คล้ายกัน


1
โดยวิธีการที่Blockมีการเปลี่ยนแปลงConsumerในรุ่นล่าสุดของ JDK 8 API
Edwin Dalorzo

4
ย่อหน้าสุดท้ายคัดค้านข้อเท็จจริงที่สำคัญบางประการ: การแสดงออกของแลมบ์ดาและการอ้างอิงเมธอดเป็นมากกว่าการเพิ่มทางวากยสัมพันธ์ JVM ให้ประเภทใหม่ในการจัดการวิธีการอ้างอิงได้อย่างมีประสิทธิภาพมากกว่าคลาสนิรนาม (ที่สำคัญที่สุดMethodHandle) และการใช้สิ่งนี้คอมไพเลอร์ Java สามารถสร้างโค้ดที่มีประสิทธิภาพมากขึ้นเช่นการใช้แลมบ์ดาโดยไม่ต้องปิดเป็นวิธีคงที่ คลาสเพิ่มเติม
Feuermurmel

7
และรุ่นที่ถูกต้องคือFunction<Void, Void> Runnable
OrangeDog

2
@OrangeDog นี้ไม่เป็นความจริงโดยสิ้นเชิง ในความคิดเห็นไปRunnableและCallableอินเตอร์เฟซที่มันเขียนว่าพวกเขาควรจะนำมาใช้ร่วมกับหัวข้อ ผลที่น่ารำคาญคือเครื่องมือวิเคราะห์แบบคงที่ (เช่น Sonar) จะบ่นถ้าคุณเรียกrun()ใช้เมธอดโดยตรง
เดนิส

มาอย่างหมดจดจากพื้นหลัง Java ฉันสงสัยว่าโดยเฉพาะอย่างยิ่งในกรณีที่ Java ไม่สามารถปฏิบัติหน้าที่เป็นพลเมืองชั้นหนึ่งได้แม้หลังจากการแนะนำ interface การทำงานและ lambdas ตั้งแต่ Java8?
Vivek Sethi

21

เมื่อคุณจำเป็นต้องยอมรับฟังก์ชั่นเป็นอาร์กิวเมนต์ที่ไม่มีข้อโต้แย้งและไม่ส่งคืนผลลัพธ์ (เป็นโมฆะ) ในความคิดของฉันมันยังดีที่สุดที่จะมีบางสิ่งบางอย่างเช่น

  public interface Thunk { void apply(); }

บางแห่งในรหัสของคุณ ในหลักสูตรการเขียนโปรแกรมการทำงานของฉันคำว่า 'thunk' ถูกใช้เพื่ออธิบายฟังก์ชั่นดังกล่าว ทำไมมันไม่ได้อยู่ใน java.util.function นั้นเกินความเข้าใจของฉัน

ในกรณีอื่น ๆ ฉันพบว่าแม้เมื่อ java.util.function มีบางสิ่งที่ตรงกับลายเซ็นที่ฉันต้องการ - มันยังไม่รู้สึกเสมอไปเมื่อตั้งชื่ออินเทอร์เฟซไม่ตรงกับการใช้ฟังก์ชันในรหัสของฉัน ฉันเดาว่ามันเป็นจุดที่คล้ายกันที่เกิดขึ้นที่อื่นที่นี่เกี่ยวกับ 'Runnable' - ซึ่งเป็นคำศัพท์ที่เกี่ยวข้องกับคลาส Thread - ดังนั้นแม้ว่าเขาอาจต้องการลายเซ็นที่ฉันต้องการ แต่ก็มีแนวโน้มที่จะทำให้ผู้อ่านสับสน


โดยเฉพาะอย่างยิ่งRunnableไม่อนุญาตให้มีการตรวจสอบข้อยกเว้นทำให้มันไร้ประโยชน์สำหรับหลาย ๆ แอปพลิเคชัน เนื่องจากCallable<Void>ไม่มีประโยชน์อย่างใดอย่างหนึ่งคุณจำเป็นต้องกำหนดของคุณเองดูเหมือนว่า น่าเกลียด
Jesse Glick

ในการอ้างอิงถึงปัญหาการตรวจสอบข้อยกเว้นฟังก์ชั่นใหม่ของ Java ในการต่อสู้ทั่วไปกับที่ มันเป็นตัวบล็อกขนาดใหญ่สำหรับบางสิ่งเช่น Stream API การออกแบบที่แย่มากในส่วนของพวกเขา
2223059

0

ตั้งค่าประเภทการคืนเป็นVoidแทนที่จะเป็นvoidและreturn null

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

หรือ

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

-2

Function<T, R>ผมรู้สึกว่าคุณควรจะใช้อินเตอร์เฟซผู้บริโภคแทน

Consumer นั้นเป็นส่วนต่อประสานการทำงานที่ออกแบบมาเพื่อรับค่าและไม่ส่งคืนอะไร (เช่นเป็นโมฆะ)

ในกรณีของคุณคุณสามารถสร้างผู้บริโภคในรหัสของคุณเช่นนี้:

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

จากนั้นคุณสามารถแทนที่myForEachรหัสด้วยข้อมูลตัวอย่างด้านล่าง:

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

คุณถือว่า myFunction เป็นวัตถุชั้นหนึ่ง


2
สิ่งนี้เพิ่มนอกเหนือจากคำตอบที่มีอยู่?
Matthew อ่าน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.