ส่วนต่อประสานการทำงานที่ใช้ใน Java 8 คืออะไร?


154

ฉันเจอคำศัพท์ใหม่ใน Java 8: "functional interface" ฉันเท่านั้นที่สามารถหาหนึ่งใช้ประโยชน์จากมันขณะที่ทำงานกับการแสดงออกแลมบ์ดา

Java 8 มีอินเทอร์เฟซการทำงานบางอย่างในตัวและหากเราต้องการกำหนดอินเตอร์เฟสการทำงานใด ๆ เราสามารถใช้@FunctionalInterfaceคำอธิบายประกอบได้ มันจะช่วยให้เราประกาศเพียงวิธีเดียวในอินเทอร์เฟซ

ตัวอย่างเช่น:

@FunctionalInterface
interface MathOperation {
    int operation(int a, int b);
}

มีประโยชน์อย่างไรใน Java 8 นอกเหนือจากการทำงานกับการแสดงออกแลมบ์ดา ?

(คำถามที่นี่แตกต่างจากที่ฉันถามมันถามว่าทำไมเราถึงต้องมีอินเตอร์เฟซที่ใช้งานได้ในขณะที่ทำงานกับการแสดงออกแลมบ์ดาคำถามของฉันคือ: ทำไมการใช้งานอื่น ๆ


1
มันดูซ้ำไปที่ลิงค์นี้ พวกเขายังพูดถึงเหตุผลที่ควรมีเพียงวิธีเดียวในส่วนต่อประสานการทำงาน stackoverflow.com/questions/33010594/…
Kulbhushan Singh

1
@KulbhushanSingh ฉันเห็นคำถามนี้ก่อนโพสต์ ... ทั้งสองคำถามต่างกันไป ...
Madhusudan

คำตอบ:


127

@FunctionalInterfaceคำอธิบายประกอบมีประโยชน์สำหรับการตรวจสอบเวลารวบรวมรหัสของคุณ คุณไม่สามารถมีมากกว่าหนึ่งวิธีนอกจากstaticนี้defaultและวิธีนามธรรมที่แทนที่วิธีการในอินเทอร์เฟซObjectของคุณ@FunctionalInterfaceหรืออื่น ๆ ที่ใช้เป็นส่วนต่อประสานการทำงาน

แต่คุณสามารถใช้ lambdas โดยไม่มีคำอธิบายประกอบนี้ได้เช่นเดียวกับคุณสามารถแทนที่วิธีการโดยไม่ต้องใส่@Overrideคำอธิบายประกอบ

จากเอกสาร

ส่วนต่อประสานการทำงานมีวิธีนามธรรมอย่างหนึ่งอย่างแน่นอน เนื่องจากวิธีการเริ่มต้นมีการใช้งานพวกเขาจึงไม่เป็นนามธรรม หากอินเตอร์เฟสประกาศวิธีนามธรรมแทนที่หนึ่งในวิธีสาธารณะของ java.lang.Object ที่ไม่นับรวมกับวิธีนามธรรมของอินเทอร์เฟซนับตั้งแต่การดำเนินการของอินเทอร์เฟซใด ๆ จะมีการใช้งานจาก java.lang.Object หรืออื่น ๆ

สิ่งนี้สามารถใช้ในการแสดงออกแลมบ์ดา:

public interface Foo {
  public void doSomething();
}

สิ่งนี้ไม่สามารถใช้ในการแสดงออกแลมบ์ดา:

public interface Foo {
  public void doSomething();
  public void doSomethingElse();
}

แต่สิ่งนี้จะทำให้เกิดข้อผิดพลาดในการรวบรวม :

@FunctionalInterface
public interface Foo {
  public void doSomething();
  public void doSomethingElse();
}

คำอธิบายประกอบ '@FunctionalInterface' ไม่ถูกต้อง Foo ไม่ใช่อินเทอร์เฟซที่ใช้งานได้


43
เพื่อให้แม่นยำยิ่งขึ้นคุณต้องมีวิธีนามธรรมหนึ่งวิธีที่ไม่ได้แทนที่วิธีการในjava.lang.Objectอินเทอร์เฟซที่ใช้งานได้
Holger

9
…และมันแตกต่างกันเล็กน้อยในการ“ ไม่มีมากกว่าหนึ่งpublicวิธีนอกเหนือจากstaticและdefault” …
โฮล

4
ยังไม่เข้าใจว่ามีจุดใด ทำไมทุกคนบนโลกนี้จึงรบกวนตรวจสอบว่าอินเทอร์เฟซของตนมีกี่วิธี อินเทอร์เฟซของตัวทำเครื่องหมายยังคงมีจุดและวัตถุประสงค์เฉพาะ เอกสารและคำตอบจะอธิบายเฉพาะสิ่งที่มันทำไม่ใช่วิธีการใช้งานใด ๆ เลย และ "ใช้" เป็นสิ่งที่ OP ต้องการ ดังนั้นฉันจะไม่แนะนำคำตอบนี้
saran3h

1
@VNT ข้อผิดพลาดในการรวบรวมรับไคลเอ็นต์ของอินเตอร์เฟสนี้ แต่อินเตอร์เฟสนั้นไม่สามารถเปลี่ยนแปลงได้ เมื่อใช้คำอธิบายประกอบนี้ข้อผิดพลาดในการรวบรวมอยู่บนอินเทอร์เฟซดังนั้นคุณต้องแน่ใจว่าไม่มีใครจะทำลายลูกค้าของอินเทอร์เฟซของคุณ
Sergii Bishyr

2
สิ่งนี้แสดงวิธีใช้ แต่ไม่ได้อธิบายว่าทำไมเราจึงต้องการมัน
ชีค

14

เอกสารทำให้ย่อมเป็นความแตกต่างระหว่างวัตถุประสงค์

ชนิดคำอธิบายประกอบแบบให้ข้อมูลที่ใช้เพื่อระบุว่าการประกาศชนิดอินเตอร์เฟสมีวัตถุประสงค์เพื่อเป็นอินเตอร์เฟสการทำงานตามที่กำหนดโดย Java Language Specification

และกรณีการใช้งาน

โปรดทราบว่าอินสแตนซ์ของอินเทอร์เฟซที่ใช้งานได้สามารถสร้างได้ด้วยการแสดงออกแลมบ์ดาการอ้างอิงเมธอดหรือการอ้างอิงคอนสตรัคเตอร์

การใช้ถ้อยคำไม่ได้ จำกัด กรณีการใช้งานอื่นโดยทั่วไป เนื่องจากจุดประสงค์หลักคือเพื่อระบุอินเทอร์เฟซที่ใช้งานได้คำถามที่แท้จริงของคุณจึงลดลงถึง“ มีกรณีการใช้งานอื่น ๆ สำหรับส่วนต่อประสานการทำงานอื่น ๆ นอกเหนือจากการแสดงออกแลมบ์ดา

เนื่องจากส่วนต่อประสานการทำงานเป็นโครงสร้างภาษา Java ที่กำหนดโดย Java Language Specification จึงมีเพียงข้อมูลจำเพาะเท่านั้นที่สามารถตอบคำถามได้:

JLS §9.8 ฟังก์ชั่นการเชื่อมต่อ :

...

นอกเหนือจากกระบวนการปกติในการสร้างอินสแตนซ์ของอินเทอร์เฟซโดยการประกาศและสร้างอินสแตนซ์ของคลาส (§15.9) อินสแตนซ์ของอินเทอร์เฟซที่ใช้งานได้สามารถสร้างได้ด้วยนิพจน์อ้างอิงวิธีการ

ดังนั้น Java Language Specification ไม่ได้พูดเป็นอย่างอื่นกรณีใช้งานเดียวที่กล่าวถึงในส่วนนั้นคือการสร้างอินสแตนซ์ของอินเทอร์เฟซที่มีนิพจน์อ้างอิงเมธอดและนิพจน์แลมบ์ดา (ซึ่งรวมถึงการอ้างอิงคอนสตรัคตามที่ระบุไว้ในรูปแบบหนึ่งของการแสดงออกวิธีการอ้างอิงในสเปค)

ดังนั้นในหนึ่งประโยคไม่ไม่มีกรณีการใช้งานอื่นสำหรับ Java 8


อาจจะขอมากเกินไปหรือไม่เกี่ยวข้อง (คุณสามารถเลือกที่จะไม่ตอบ) แต่สิ่งที่คุณจะแนะนำเมื่อมีคนสร้างโปรแกรมอรรถประโยชน์public static String generateTaskId()เมื่อเทียบกับเพื่อให้ "ทำงาน" คนอื่นเลือกที่จะเขียนมันpublic class TaskIdSupplier implements Supplier<String>ด้วยgetวิธีการใช้ การใช้งานรุ่นที่มีอยู่ นั่นคือการใช้อินเตอร์เฟซที่ใช้งานผิดวัตถุประสงค์โดยเฉพาะการใช้ซ้ำSupplierจาก JDK ในตัวหรือไม่ PS: ฉันหาสถานที่ที่ดีกว่าไม่ได้ / ถาม & ตอบเพื่อถามสิ่งนี้ ยินดีที่จะย้ายหากคุณสามารถแนะนำ
Naman

1
@Naman TaskIdSupplierคุณไม่ได้ทำวิธียูทิลิตี้การทำงานมากขึ้นเมื่อคุณสร้างชั้นชื่อ ตอนนี้คำถามคือเหตุผลที่คุณสร้างคลาสที่มีชื่อ ServiceLoaderมีสถานการณ์ที่ดังกล่าวเป็นชื่อประเภทเป็นสิ่งจำเป็นเช่นเมื่อคุณต้องการที่จะสนับสนุนการดำเนินการหาทางที่มี มีอะไรผิดปกติอยู่กับปล่อยให้มันดำเนินการSupplierแล้ว แต่เมื่อคุณไม่ต้องการมันอย่าสร้างมันขึ้นมา เมื่อคุณต้องการเพียงแค่Supplier<String>มันก็เพียงพอแล้วที่จะใช้DeclaringClass::generateTaskIdและกำจัดความจำเป็นในการเรียนอย่างชัดเจนเป็นจุดของคุณสมบัติภาษานี้
Holger

พูดตามตรงฉันกำลังมองหาเหตุผลสำหรับคำแนะนำที่ฉันดำเนินต่อไป ด้วยเหตุผลบางอย่างในที่ทำงานฉันไม่ได้รู้สึกว่าการTaskIdSupplierใช้งานนั้นคุ้มค่ากับความพยายาม แต่แล้วแนวคิดของการServiceLoaderข้ามความคิดของฉันไปโดยสิ้นเชิง พบคำถามไม่กี่ระหว่างการอภิปรายเหล่านี้เรากำลังมีเช่นคืออะไรการใช้งานSupplierของpublicการดำรงอยู่เมื่อหนึ่งสามารถไปข้างหน้าและพัฒนาอินเตอร์เฟซของตัวเอง? และทำไมไม่public static Supplier<String> TASK_ID_SUPPLIER = () ->...เป็นค่าคงที่ทั่วโลก? . (1/2)
Naman

1
@Naman วิธีการใช้สำนวนแทนฟังก์ชันใน Java เป็นวิธีการและการประเมินฟังก์ชั่นเหล่านั้นเหมือนกับการเรียกใช้ ไม่ควรที่จะเป็นนักพัฒนาที่ถูกบังคับให้ทำแทนvariable.genericMethodName(args) meaningfulMethodName(args)การใช้ประเภทคลาสเพื่อแสดงฟังก์ชั่นไม่ว่าจะผ่านการอ้างอิงแลมบ์ดานิพจน์ / วิธีหรือคลาสที่สร้างขึ้นด้วยตนเองเป็นเพียงยานพาหนะที่จะส่งผ่านฟังก์ชันรอบ ๆ (ในกรณีที่ไม่มีประเภทฟังก์ชั่นที่แท้จริงใน Java) ควรทำเมื่อจำเป็นเท่านั้น
Holger

1
เมื่อคุณมีส่วนย่อยของโค้ดขนาดเล็กเท่านั้นที่ถูกส่งผ่านไปคุณสามารถสร้างนิพจน์แลมบ์ดาได้ เมื่อใดก็ตามที่มีความต้องการที่จะเรียกมันเหมือนวิธีการ (ซึ่งรวมถึงสถานการณ์ที่มีความจำเป็นในการทดสอบเมื่อส่วนของโค้ดไม่สำคัญ) ให้สร้างวิธีการตั้งชื่อที่สามารถเรียกใช้และใช้การอ้างอิงเมธอดหรือคลาสการแสดงออกแลมด้า / ชัดเจน การห่อหุ้มการโทรเพื่อส่งผ่านเมื่อจำเป็น ค่าคงที่มีประโยชน์เฉพาะเมื่อคุณไม่เชื่อถือประสิทธิภาพของการแสดงออกแลมบ์ดาหรือการอ้างอิงวิธีการที่ฝังอยู่ในรหัสของคุณหรือกล่าวอีกนัยหนึ่งพวกเขาแทบไม่จำเป็นเลย
Holger

12

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


10

ไม่ใช่เลย. นิพจน์แลมบ์ดาเป็นเพียงจุดเดียวของคำอธิบายประกอบนั้น


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

1
ตรงไปยังจุดและคำตอบที่ถูกต้องแม้ว่าจะเป็นเพียงเล็กน้อย ผมเอาเวลาที่จะเพิ่มคำตอบเนื้อหามากขึ้นพูดในสิ่งเดียวกันด้วยคำพูดมากขึ้น ...
โฮล

5

นิพจน์แลมบ์ดาสามารถกำหนดให้กับประเภทอินเตอร์เฟสที่ใช้งานได้ แต่สามารถอ้างอิงวิธีการและคลาสที่ไม่ระบุชื่อได้

สิ่งหนึ่งที่ดีเกี่ยวกับการเชื่อมต่อการทำงานที่เฉพาะเจาะจงในการjava.util.functionที่พวกเขาสามารถจะประกอบด้วยการสร้างฟังก์ชั่นใหม่ (ชอบFunction.andThenและFunction.compose, Predicate.andฯลฯ ) เนื่องจากวิธีการเริ่มต้นที่มีประโยชน์ที่พวกเขามี


คุณควรทำอย่างละเอียดในความคิดเห็นนี้เพิ่มเติม สิ่งที่เกี่ยวกับการอ้างอิงวิธีการและฟังก์ชั่นใหม่
K.Nicholas

5

อินเทอร์เฟซที่มีวิธีการทางนามธรรมเพียงวิธีเดียวเรียกว่า ไม่จำเป็นต้องใช้ @FunctionalInterface แต่เป็นวิธีที่ดีที่สุดในการใช้กับส่วนต่อประสานการทำงานเพื่อหลีกเลี่ยงการเพิ่มวิธีการพิเศษโดยไม่ตั้งใจ หากส่วนต่อประสานนั้นมีการเพิ่มความคิดเห็นด้วย @FunctionalInterface และเราพยายามที่จะมีวิธีนามธรรมมากกว่าหนึ่งวิธีมันจะพ่นข้อผิดพลาดของคอมไพเลอร์

package com.akhi;
    @FunctionalInterface
    public interface FucnctionalDemo {

      void letsDoSomething();
      //void letsGo();      //invalid because another abstract method does not allow
      public String toString();    // valid because toString from Object 
      public boolean equals(Object o); //valid

      public static int sum(int a,int b)   // valid because method static
        {   
            return a+b;
        }
        public default int sub(int a,int b)   //valid because method default
        {
            return a-b;
        }
    }

3

ฟังก์ชั่นการเชื่อมต่อ:

  • แนะนำใน Java 8
  • อินเทอร์เฟซที่ประกอบด้วยวิธีการ "นามธรรมเดียว"

ตัวอย่างที่ 1:

   interface CalcArea {   // --functional interface
        double calcArea(double rad);
    }           

ตัวอย่างที่ 2:

interface CalcGeometry { // --functional interface
    double calcArea(double rad);
    default double calcPeri(double rad) {
        return 0.0;
    }
}       

ตัวอย่างที่ 3:

interface CalcGeometry {  // -- not functional interface
    double calcArea(double rad);
    double calcPeri(double rad);
}   

คำอธิบายประกอบ Java8 - @FunctionalInterface

  • ตรวจสอบหมายเหตุประกอบว่าอินเตอร์เฟสมีวิธีนามธรรมเพียงวิธีเดียว ถ้าไม่ยกข้อผิดพลาด
  • แม้ว่า @FunctionalInterface หายไป แต่ก็ยังคงเป็นฟังก์ชันการใช้งาน (หากมีวิธีการแบบนามธรรมเดียว) คำอธิบายประกอบช่วยหลีกเลี่ยงข้อผิดพลาด
  • ส่วนต่อประสานการทำงานอาจมีวิธีคงที่และค่าเริ่มต้นเพิ่มเติม
  • เช่น Iterable <>, Comparable <>, Comparator <>

การประยุกต์ใช้ฟังก์ชั่นอินเตอร์เฟซ:

  • วิธีการอ้างอิง
  • แลมบ์ดานิพจน์
  • การอ้างอิงคอนสตรัค

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


สองตัวอย่างแรกของคุณควรมีคำว่า 'นามธรรม' หรือไม่
sofs1

1
@ sofs1 วิธีการประกาศในอินเทอร์เฟซโดยค่าเริ่มต้นทั้งสาธารณะและนามธรรม คุณต้องใช้คำหลักที่เป็นนามธรรมในกรณีของวิธีการในระดับนามธรรม อย่างไรก็ตามมันเป็นเรื่องปกติที่จะใช้คำสำคัญที่เป็นนามธรรมสำหรับวิธีการในอินเทอร์เฟซเช่นกัน พวกเขาอนุญาตให้ใช้งานร่วมกับจาวาเวอร์ชันเก่าได้ แต่ก็ไม่สนับสนุน
Ketan

2

คุณสามารถใช้แลมบ์ดาใน Java 8

public static void main(String[] args) {
    tentimes(inputPrm - > System.out.println(inputPrm));
    //tentimes(System.out::println);  // You can also replace lambda with static method reference
}

public static void tentimes(Consumer myFunction) {
    for (int i = 0; i < 10; i++)
        myFunction.accept("hello");
}

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับJava LambdasและFunctionalInterfaces


1

@FunctionalInterface เป็นคำอธิบายประกอบใหม่ออกวางจำหน่ายกับ Java 8 และให้ประเภทเป้าหมายสำหรับการแสดงออกแลมบ์ดาและมันใช้ในการตรวจสอบเวลารวบรวมของรหัสของคุณ

เมื่อคุณต้องการใช้:

1- อินเตอร์เฟซของคุณต้องไม่มีวิธีนามธรรมมากกว่าหนึ่งวิธีมิฉะนั้นจะมีข้อผิดพลาดในการรวบรวม

1- อินเทอร์เฟซของคุณควรจะบริสุทธิ์ซึ่งหมายความว่าอินเทอร์เฟซที่ใช้งานได้ตั้งใจที่จะใช้งานโดยคลาสไร้สัญชาติส่วนเกินของบริสุทธิ์คือComparatorอินเทอร์เฟซเนื่องจากไม่ขึ้นอยู่กับสถานะของผู้ใช้งานในกรณีนี้ไม่มีข้อผิดพลาด จะไม่สามารถใช้แลมบ์ดากับอินเทอร์เฟซชนิดนี้ได้

java.util.functionแพคเกจประกอบด้วยต่างๆอเนกประสงค์อินเตอร์เฟซที่ทำงานเช่นPredicate, Consumer, และFunctionSupplier

นอกจากนี้โปรดทราบว่าคุณสามารถใช้ lambdas โดยไม่มีคำอธิบายประกอบนี้


1

นอกเหนือจากคำตอบอื่น ๆ ฉันคิดว่าเหตุผลหลักที่ "ทำไมการใช้ Interface Interface นอกเหนือจากโดยตรงกับการแสดงออกแลมบ์ดา" สามารถเกี่ยวข้องกับธรรมชาติของภาษา Java ซึ่งเป็น Object Oriented

คุณลักษณะหลักของการแสดงออกของแลมบ์ดาคือ: 1. สามารถส่งผ่านได้ประมาณ 2 ครั้งและสามารถดำเนินการในอนาคตในเวลาที่กำหนด (หลายครั้ง) ตอนนี้เพื่อรองรับฟีเจอร์นี้ในภาษาอื่น ๆ บางภาษาจัดการกับเรื่องนี้ได้ง่ายๆ

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

var myFunction = function (...) {
    ...;
}
alert(myFunction(...));

หรือผ่าน ES6 คุณสามารถใช้ฟังก์ชั่นลูกศร

const myFunction = ... => ...

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

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

ActionListener listener = event -> ...;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.