JavaRush /จาวาบล็อก /Random-TH /นิพจน์แลมบ์ดาพร้อมตัวอย่าง

นิพจน์แลมบ์ดาพร้อมตัวอย่าง

เผยแพร่ในกลุ่ม
ในตอนแรก Java เป็นภาษาเชิงวัตถุโดยสมบูรณ์ ยกเว้นประเภทดั้งเดิม ทุกอย่างใน Java จะเป็นอ็อบเจ็กต์ แม้แต่อาร์เรย์ก็เป็นวัตถุ อินสแตนซ์ของแต่ละคลาสเป็นวัตถุ ไม่มีความเป็นไปได้แม้แต่ครั้งเดียวที่จะกำหนดฟังก์ชันแยกจากกัน (นอกคลาส - โดยประมาณ transl. ) และไม่มีวิธีใดที่จะส่งเมธอดเป็นอาร์กิวเมนต์หรือส่งคืนเนื้อความของเมธอดอันเป็นผลมาจากเมธอดอื่นได้ ประมาณนั้นแหละ. แต่นี่เป็นก่อน Java 8 นิพจน์แลมบ์ดาพร้อมตัวอย่าง - 1ตั้งแต่สมัยของ Swing เก่า ๆ จำเป็นต้องเขียนคลาสที่ไม่ระบุชื่อเมื่อจำเป็นต้องส่งฟังก์ชันบางอย่างไปยังวิธีการบางอย่าง ตัวอย่างเช่น นี่คือลักษณะของการเพิ่มตัวจัดการเหตุการณ์:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
ที่นี่เราต้องการเพิ่มโค้ดบางส่วนให้กับตัวฟังเหตุการณ์ของเมาส์ เรากำหนดคลาสที่ไม่ระบุชื่อMouseAdapterและสร้างวัตถุจากคลาสนั้นทันที ด้วยวิธีนี้ เราได้ส่งผ่านฟังก์ชันเพิ่มเติมไปยังaddMouseListener. กล่าวโดยสรุป มันไม่ง่ายเลยที่จะส่งวิธีง่ายๆ (ฟังก์ชันการทำงาน) ใน Java ผ่านอาร์กิวเมนต์ ข้อจำกัดนี้บังคับให้นักพัฒนา Java 8 เพิ่มคุณสมบัติ เช่น นิพจน์ Lambda ให้กับข้อกำหนดภาษา

เหตุใด Java จึงต้องการนิพจน์ Lambda

จากจุดเริ่มต้น ภาษา Java ไม่ได้มีการพัฒนามากนัก ยกเว้นสิ่งต่างๆ เช่น Annotations, Generics และอื่นๆ ประการแรก Java ยังคงเป็น Object-Oriented อยู่เสมอ หลังจากทำงานกับภาษาเชิงฟังก์ชันเช่น JavaScript แล้ว เราจะเข้าใจได้ว่า Java เป็นแบบเชิงวัตถุและพิมพ์อย่างเข้มงวดอย่างไร ไม่จำเป็นต้องใช้ฟังก์ชันใน Java ด้วยตัวมันเอง พวกมันไม่สามารถพบได้ในโลก Java ในภาษาโปรแกรมเชิงฟังก์ชัน ฟังก์ชันต่างๆ จะมาก่อน พวกมันมีอยู่ด้วยตัวของมันเอง คุณสามารถกำหนดให้กับตัวแปรและส่งผ่านอาร์กิวเมนต์ไปยังฟังก์ชันอื่นได้ JavaScript เป็นหนึ่งในตัวอย่างที่ดีที่สุดของภาษาโปรแกรมเชิงฟังก์ชัน คุณสามารถค้นหาบทความดีๆ บนอินเทอร์เน็ตที่ให้รายละเอียดเกี่ยวกับประโยชน์ของ JavaScript ในฐานะภาษาที่ใช้งานได้ ภาษาที่ใช้งานได้มีเครื่องมืออันทรงพลังเช่นการปิดซึ่งมีข้อได้เปรียบเหนือวิธีการเขียนแอปพลิเคชันแบบเดิมหลายประการ การปิดคือฟังก์ชันที่มีสภาพแวดล้อมติดอยู่ ซึ่งเป็นตารางที่เก็บการอ้างอิงถึงตัวแปรที่ไม่ใช่ในเครื่องทั้งหมดของฟังก์ชัน ใน Java การปิดสามารถจำลองได้ผ่านนิพจน์ Lambda แน่นอนว่ามีความแตกต่างระหว่างการปิดและนิพจน์ Lambda ไม่ใช่ความแตกต่างเล็กๆ น้อยๆ แต่นิพจน์ lambda เป็นทางเลือกที่ดีในการปิด ในบล็อกที่เสียดสีและตลกขบขัน Steve Yegge อธิบายว่าโลกของ Java เชื่อมโยงกับคำนามอย่างเคร่งครัดอย่างไร (เอนทิตี วัตถุ - ประมาณการแปล ) หากคุณยังไม่ได้อ่านบล็อกของเขาฉันขอแนะนำ เขาอธิบายเหตุผลที่แน่ชัดว่าทำไมนิพจน์ Lambda ถึงถูกเพิ่มลงใน Java ด้วยวิธีที่ตลกและน่าสนใจ นิพจน์ Lambda นำฟังก์ชันการทำงานมาสู่ Java ที่ขาดหายไปมานาน นิพจน์ Lambda นำฟังก์ชันการทำงานมาสู่ภาษาเช่นเดียวกับวัตถุ แม้ว่าสิ่งนี้จะไม่จริง 100% แต่คุณจะเห็นได้ว่านิพจน์ Lambda แม้จะไม่ได้ปิดอยู่ แต่ก็มีความสามารถที่คล้ายกัน ในภาษาเชิงฟังก์ชัน นิพจน์แลมบ์ดาคือฟังก์ชัน แต่ใน Java นิพจน์แลมบ์ดาจะแสดงด้วยอ็อบเจ็กต์ และต้องเชื่อมโยงกับประเภทอ็อบเจ็กต์เฉพาะที่เรียกว่าอินเทอร์เฟซการทำงาน ต่อไปเรามาดูกันว่ามันคืออะไร บทความของ Mario Fusco เรื่อง "เหตุใดเราจึงต้องใช้ Lambda Expression ใน Java" อธิบายรายละเอียดว่าเหตุใดภาษาสมัยใหม่ทั้งหมดจึงจำเป็นต้องมีความสามารถในการปิด

ข้อมูลเบื้องต้นเกี่ยวกับนิพจน์แลมบ์ดา

นิพจน์แลมบ์ดาเป็นฟังก์ชันที่ไม่ระบุชื่อ (อาจไม่ใช่คำจำกัดความที่ถูกต้อง 100% สำหรับ Java แต่จะทำให้เกิดความชัดเจน) พูดง่ายๆ ก็คือ นี่เป็นวิธีการที่ไม่มีการประกาศ เช่น โดยไม่มีตัวดัดแปลงการเข้าถึง ส่งคืนค่าและชื่อ กล่าวโดยย่อคืออนุญาตให้คุณเขียนวิธีการและใช้งานได้ทันที มันมีประโยชน์อย่างยิ่งในกรณีของการเรียกเมธอดแบบครั้งเดียวเพราะว่า ลดเวลาในการประกาศและเขียนเมธอดโดยไม่ต้องสร้างคลาส นิพจน์ Lambda ใน Java โดยทั่วไปจะมีไวยากรณ์ดังต่อไป(аргументы) -> (тело)นี้ ตัวอย่างเช่น:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
ด้านล่างนี้คือตัวอย่างบางส่วนของนิพจน์ Lambda จริง:
(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

โครงสร้างของนิพจน์แลมบ์ดา

เรามาศึกษาโครงสร้างของนิพจน์แลมบ์ดากัน:
  • นิพจน์ Lambda สามารถมีพารามิเตอร์อินพุตได้ตั้งแต่ 0 ตัวขึ้นไป
  • ประเภทของพารามิเตอร์สามารถระบุได้อย่างชัดเจนหรือได้รับจากบริบท เช่น ( int a) สามารถเขียนได้ดังนี้ ( a)
  • พารามิเตอร์อยู่ในวงเล็บและคั่นด้วยเครื่องหมายจุลภาค ตัวอย่างเช่น ( a, b) หรือ ( int a, int b) หรือ ( String a, int b, float c)
  • หากไม่มีพารามิเตอร์ คุณจะต้องใช้วงเล็บว่าง ตัวอย่างเช่น() -> 42
  • เมื่อมีพารามิเตอร์เพียงตัวเดียว หากไม่ได้ระบุประเภทอย่างชัดเจน ก็สามารถละเว้นวงเล็บได้ ตัวอย่าง:a -> return a*a
  • เนื้อความของนิพจน์ Lambda สามารถมีได้ 0 นิพจน์ขึ้นไป
  • หากเนื้อหาประกอบด้วยคำสั่งเดียว คำสั่งนั้นอาจไม่อยู่ในวงเล็บปีกกา และอาจระบุค่าที่ส่งคืนโดยไม่มีreturnคีย์เวิร์ด
  • มิฉะนั้น จำเป็นต้องใช้เครื่องหมายปีกกา (บล็อกของโค้ด) และต้องระบุค่าที่ส่งคืนที่ส่วนท้ายโดยใช้คำหลักreturn(ไม่เช่นนั้นประเภทที่ส่งคืนจะเป็นvoid)

อินเทอร์เฟซการทำงานคืออะไร

ใน Java อินเทอร์เฟซ Marker คืออินเทอร์เฟซโดยไม่ต้องประกาศวิธีการหรือฟิลด์ กล่าวอีกนัยหนึ่ง อินเทอร์เฟซโทเค็นเป็นอินเทอร์เฟซว่างเปล่า ในทำนองเดียวกัน Functional Interfaces คืออินเทอร์เฟซที่มีวิธีนามธรรมเพียงวิธีเดียวที่ประกาศไว้ java.lang.Runnableเป็นตัวอย่างของอินเทอร์เฟซการทำงาน ประกาศเพียงวิธีเดียวvoid run()เท่านั้น นอกจากนี้ยังมีอินเทอร์เฟซActionListener- ยังใช้งานได้อีกด้วย ก่อนหน้านี้ เราต้องใช้คลาสที่ไม่ระบุตัวตนเพื่อสร้างออบเจ็กต์ที่ใช้อินเทอร์เฟซที่ใช้งานได้ ด้วยนิพจน์ Lambda ทุกอย่างจะง่ายขึ้น แต่ละนิพจน์แลมบ์ดาสามารถเชื่อมโยงกับอินเทอร์เฟซการทำงานบางอย่างโดยปริยายได้ ตัวอย่างเช่น คุณสามารถสร้างการอ้างอิงไปยังRunnableอินเทอร์เฟซได้ ดังที่แสดงในตัวอย่างต่อไปนี้:
Runnable r = () -> System.out.println("hello world");
การแปลงประเภทนี้จะทำโดยปริยายเสมอเมื่อเราไม่ได้ระบุอินเทอร์เฟซที่ใช้งานได้:
new Thread(
    () -> System.out.println("hello world")
).start();
ในตัวอย่างข้างต้น คอมไพลเลอร์จะสร้างนิพจน์แลมบ์ดาโดยอัตโนมัติซึ่งเป็นการนำ อินเทอร์เฟ ซRunnableจากตัวสร้างคลาสThread: public Thread(Runnable r) { }นี่คือตัวอย่างบางส่วนของนิพจน์แลมบ์ดาและอินเทอร์เฟซการทำงานที่เกี่ยวข้อง:
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
คำอธิบาย@FunctionalInterfaceประกอบที่เพิ่มใน Java 8 ตามข้อกำหนดภาษา Java จะตรวจสอบว่าอินเทอร์เฟซที่ประกาศนั้นใช้งานได้หรือไม่ นอกจากนี้ Java 8 ยังมีอินเทอร์เฟซการทำงานสำเร็จรูปจำนวนหนึ่งสำหรับใช้กับนิพจน์ Lambda @FunctionalInterfaceจะทำให้เกิดข้อผิดพลาดในการคอมไพล์หากอินเทอร์เฟซที่ประกาศไม่ทำงาน ต่อไปนี้เป็นตัวอย่างของการกำหนดอินเทอร์เฟซการทำงาน:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
ตามคำจำกัดความที่แนะนำ ส่วนต่อประสานการทำงานสามารถมีวิธีการเชิงนามธรรมได้เพียงวิธีเดียวเท่านั้น หากคุณพยายามเพิ่มวิธีนามธรรมอื่น คุณจะได้รับข้อผิดพลาดในการคอมไพล์ ตัวอย่าง:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
ข้อผิดพลาด
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });

      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }

}
บทสรุป:
Worker вызван через анонимный класс
Worker вызван через Lambda
ที่นี่เราได้กำหนดอินเทอร์เฟซการทำงานของเราเองและใช้นิพจน์แลมบ์ดา วิธีการนี้execute()สามารถยอมรับนิพจน์แลมบ์ดาเป็นอาร์กิวเมนต์ได้

ตัวอย่างนิพจน์แลมบ์ดา

วิธีที่ดีที่สุดในการทำความเข้าใจนิพจน์ Lambda คือการดูตัวอย่างบางส่วน: สตรีมThreadสามารถเริ่มต้นได้สองวิธี:
// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
การจัดการเหตุการณ์ใน Java 8 สามารถทำได้ผ่านนิพจน์ Lambda ต่อไปนี้เป็นสองวิธีในการเพิ่มตัวจัดการเหตุการณ์ActionListenerให้กับองค์ประกอบ UI:
// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});
// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
ตัวอย่างง่ายๆ ของการแสดงองค์ประกอบทั้งหมดของอาร์เรย์ที่กำหนด โปรดทราบว่ามีวิธีใช้นิพจน์แลมบ์ดาได้มากกว่าหนึ่งวิธี ด้านล่างนี้เราสร้างนิพจน์แลมบ์ดาด้วยวิธีปกติโดยใช้ไวยากรณ์ลูกศร และเรายังใช้ตัวดำเนินการโคลอนคู่(::)ซึ่งใน Java 8 จะแปลงวิธีปกติให้เป็นนิพจน์แลมบ์ดา:
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
ในตัวอย่างต่อไปนี้ เราใช้อินเทอร์เฟซการทำงานPredicateเพื่อสร้างการทดสอบและพิมพ์รายการที่ผ่านการทดสอบนั้น วิธีนี้ทำให้คุณสามารถใส่ตรรกะลงในนิพจน์แลมบ์ดาและทำสิ่งต่างๆ ตามนิพจน์นั้นได้
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);

        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);

        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
บทสรุป:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
ด้วยการแก้ไขนิพจน์ Lambda คุณสามารถแสดงกำลังสองของแต่ละองค์ประกอบของรายการได้ โปรดสังเกตว่าเรากำลังใช้วิธีstream()การแปลงรายการปกติให้เป็นสตรีม Java 8 มีคลาสที่ยอดเยี่ยมStream( java.util.stream.Stream) มีวิธีการที่เป็นประโยชน์มากมายที่คุณสามารถใช้นิพจน์แลมบ์ดาได้ เราส่งนิพจน์แลมบ์ดาx -> x*xไปยัง เมธอด map()ซึ่งนำไปใช้กับองค์ประกอบทั้งหมดในสตรีม หลังจากนั้นเราจะใช้forEachในการพิมพ์องค์ประกอบทั้งหมดของรายการ
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
จากรายการ คุณจะต้องพิมพ์ผลรวมของกำลังสองขององค์ประกอบทั้งหมดของรายการ นิพจน์ Lambda ช่วยให้คุณบรรลุเป้าหมายนี้ได้ด้วยการเขียนโค้ดเพียงบรรทัดเดียว ตัวอย่างนี้ใช้วิธีการบิด (ลดreduce()) เราใช้วิธีmap()การยกกำลังสองให้กับแต่ละองค์ประกอบ จากนั้นใช้วิธีreduce()การยุบองค์ประกอบทั้งหมดให้เป็นตัวเลขตัวเดียว
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

ความแตกต่างระหว่างนิพจน์ Lambda และคลาสที่ไม่ระบุชื่อ

ข้อแตกต่างที่สำคัญคือการใช้คำthisหลัก สำหรับคลาสที่ไม่ระบุชื่อ คีย์เวิร์ด ' ' thisหมายถึงอ็อบเจ็กต์ของคลาสที่ไม่ระบุชื่อ ในขณะที่นิพจน์ lambda ' this' หมายถึงอ็อบเจ็กต์ของคลาสที่ใช้นิพจน์ lambda ข้อแตกต่างก็คือวิธีการรวบรวม Java รวบรวมนิพจน์แลมบ์ดาและแปลงเป็นprivateวิธีการเรียน สิ่งนี้ใช้ คำสั่ง invokedynamicซึ่งนำมาใช้ใน Java 7 สำหรับการเชื่อมโยงเมธอดแบบไดนามิก Tal Weiss อธิบายไว้ในบล็อกของเขาว่า Java รวบรวมนิพจน์แลมบ์ดาเป็นไบต์โค้ดได้อย่างไร

บทสรุป

Mark Reinhold (หัวหน้าสถาปนิกของ Oracle) เรียกว่า Lambda เป็นการแสดงออกถึงการเปลี่ยนแปลงที่สำคัญที่สุดในรูปแบบการเขียนโปรแกรมที่เคยเกิดขึ้น - สำคัญยิ่งกว่ายาสามัญเสียอีก เขาคงจะพูดถูกเพราะว่า... พวกเขาทำให้โปรแกรมเมอร์ Java มีความสามารถด้านภาษาการเขียนโปรแกรมเชิงฟังก์ชันที่ทุกคนรอคอย นอกเหนือจากนวัตกรรมต่างๆ เช่น วิธีการขยายเสมือนแล้ว นิพจน์ Lambda ยังช่วยให้คุณเขียนโค้ดคุณภาพสูงมากได้ ฉันหวังว่าบทความนี้จะช่วยให้คุณได้ดูข้อมูลเบื้องต้นของ Java 8 โชคดี :)
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION