JavaRush /จาวาบล็อก /Random-TH /เป็นที่นิยมเกี่ยวกับนิพจน์แลมบ์ดาใน Java พร้อมตัวอย่างและ...
Стас Пасинков
ระดับ
Киев

เป็นที่นิยมเกี่ยวกับนิพจน์แลมบ์ดาใน Java พร้อมตัวอย่างและงาน ส่วนที่ 1

เผยแพร่ในกลุ่ม
บทความนี้เหมาะกับใคร?
  • สำหรับผู้ที่คิดว่าตนรู้จัก Java Core ดีอยู่แล้ว แต่ไม่มีความรู้เกี่ยวกับ lambda expression ใน Java หรือบางทีคุณอาจเคยได้ยินบางอย่างเกี่ยวกับแลมบ์ดามาแล้วแต่ไม่มีรายละเอียด
  • สำหรับผู้ที่มีความเข้าใจเกี่ยวกับสำนวนแลมบ์ดาบ้าง แต่ยังกลัวและใช้งานไม่ปกติ
หากคุณไม่ได้อยู่ในหมวดหมู่เหล่านี้ คุณอาจพบว่าบทความนี้น่าเบื่อ ไม่ถูกต้อง และโดยทั่วไปแล้ว "ไม่เจ๋ง" ในกรณีนี้ อย่าลังเลที่จะผ่านไป หรือหากคุณเชี่ยวชาญหัวข้อนี้เป็นอย่างดี ให้เสนอความคิดเห็นในความคิดเห็นว่าฉันจะปรับปรุงหรือเสริมบทความได้อย่างไร เนื้อหาไม่ได้อ้างคุณค่าทางวิชาการใด ๆ มีความแปลกใหม่น้อยกว่ามาก ในทางกลับกัน: ในนั้นฉันจะพยายามอธิบายสิ่งที่ซับซ้อน (สำหรับบางคน) ให้ง่ายที่สุดเท่าที่จะทำได้ ฉันได้รับแรงบันดาลใจในการเขียนโดยขอให้อธิบายสตรีม API ฉันคิดเกี่ยวกับมันและตัดสินใจว่าหากไม่เข้าใจสำนวนแลมบ์ดา ตัวอย่างบางส่วนของฉันเกี่ยวกับ "สตรีม" ก็คงไม่สามารถเข้าใจได้ เริ่มจากแลมบ์ดากันก่อน เป็นที่นิยมเกี่ยวกับนิพจน์แลมบ์ดาใน Java  พร้อมตัวอย่างและงาน  ตอนที่ 1 - 1ความรู้อะไรที่จำเป็นในการทำความเข้าใจบทความนี้:
  1. ความเข้าใจในการเขียนโปรแกรมเชิงวัตถุ (ต่อไปนี้จะเรียกว่า OOP) ได้แก่ :
    • ความรู้เกี่ยวกับคลาสและวัตถุคืออะไรความแตกต่างระหว่างสิ่งเหล่านี้คืออะไร
    • ความรู้เกี่ยวกับอินเทอร์เฟซคืออะไร แตกต่างจากคลาสอย่างไร การเชื่อมต่อระหว่างอินเทอร์เฟซและคลาสคืออะไร (อินเทอร์เฟซและคลาส)
    • ความรู้ว่าวิธีการคืออะไร เรียกมันอย่างไร วิธีนามธรรมคืออะไร (หรือวิธีการที่ไม่มีการดำเนินการ) พารามิเตอร์/ข้อโต้แย้งของวิธีการคืออะไร วิธีส่งผ่านวิธีการเหล่านั้น
    • ตัวดัดแปลงการเข้าถึง วิธีการ/ตัวแปรแบบสแตติก วิธีการ/ตัวแปรขั้นสุดท้าย
    • การสืบทอด (คลาส, อินเทอร์เฟซ, การสืบทอดหลายอินเทอร์เฟซ)
  2. ความรู้เกี่ยวกับ Java Core: ข้อมูลทั่วไป, คอลเลกชัน (รายการ), เธรด
เอาล่ะ มาเริ่มกันเลย

ประวัติเล็กน้อย

นิพจน์ Lambda มาจาก Java จากการเขียนโปรแกรมเชิงฟังก์ชัน และจากคณิตศาสตร์ ในช่วงกลางศตวรรษที่ 20 ในอเมริกา โบสถ์อลอนโซแห่งหนึ่งทำงานที่มหาวิทยาลัยพรินซ์ตัน ซึ่งชื่นชอบคณิตศาสตร์และนามธรรมทุกประเภทมาก คริสตจักรอลอนโซเป็นผู้คิดค้นแคลคูลัสแลมบ์ดา ซึ่งในตอนแรกเป็นชุดของแนวคิดเชิงนามธรรมบางอย่าง และไม่เกี่ยวข้องกับการเขียนโปรแกรมเลย ในเวลาเดียวกัน นักคณิตศาสตร์เช่น Alan Turing และ John von Neumann ทำงานที่มหาวิทยาลัยพรินซ์ตันแห่งเดียวกัน ทุกอย่างมารวมกัน: Church คิดค้นระบบแคลคูลัสแลมบ์ดา ทัวริงได้พัฒนาเครื่องคำนวณเชิงนามธรรมของเขา ซึ่งปัจจุบันรู้จักกันในชื่อ "เครื่องทัวริง" ฟอน นอยมันน์ได้เสนอแผนภาพสถาปัตยกรรมของคอมพิวเตอร์ ซึ่งเป็นพื้นฐานของคอมพิวเตอร์สมัยใหม่ (และปัจจุบันเรียกว่า "สถาปัตยกรรมของฟอน นอยมันน์") ในเวลานั้น แนวคิดของโบสถ์อลอนโซไม่ได้รับชื่อเสียงมากเท่ากับงานของเพื่อนร่วมงานของเขา (ยกเว้นสาขาคณิตศาสตร์ที่ "บริสุทธิ์") อย่างไรก็ตาม หลังจากนั้นไม่นาน John McCarthy บางคน (ซึ่งสำเร็จการศึกษาจากมหาวิทยาลัยพรินซ์ตันในช่วงเวลาของเรื่องซึ่งเป็นพนักงานของสถาบันเทคโนโลยีแมสซาชูเซตส์) เริ่มสนใจแนวคิดของ Church จากพื้นฐานเหล่านี้ ในปี 1958 เขาได้สร้างภาษาการเขียนโปรแกรมเชิงฟังก์ชันภาษาแรก Lisp และ 58 ปีต่อมา แนวคิดเกี่ยวกับการเขียนโปรแกรมเชิงฟังก์ชันรั่วไหลเข้าสู่ Java ในอันดับที่ 8 ผ่านไปไม่ถึง 70 ปี... อันที่จริง นี่ไม่ใช่ระยะเวลาที่ยาวนานที่สุดในการนำแนวคิดทางคณิตศาสตร์ไปใช้ในทางปฏิบัติ

สาระการเรียนรู้แกนกลาง

นิพจน์แลมบ์ดาก็เป็นฟังก์ชันเช่นนี้ คุณสามารถคิดว่านี่เป็นวิธีการปกติใน Java ข้อแตกต่างเพียงอย่างเดียวคือสามารถส่งผ่านไปยังวิธีอื่นเป็นอาร์กิวเมนต์ได้ ใช่ มันเป็นไปได้ที่จะส่งผ่านไม่เพียงแต่ตัวเลข สตริง และ cat ไปยังเมธอด แต่ยังรวมถึงวิธีอื่นด้วย! เมื่อใดที่เราต้องการสิ่งนี้? เช่น ถ้าเราต้องการให้โทรกลับบ้าง เราต้องการวิธีการที่เราเรียกเพื่อให้สามารถเรียกวิธีอื่นที่เราส่งผ่านไปได้ นั่นคือเพื่อให้เรามีโอกาสที่จะส่งการโทรกลับครั้งหนึ่งในบางกรณีและอีกการโทรกลับในบางกรณี และวิธีการของเราซึ่งจะยอมรับการโทรกลับของเราก็จะเรียกพวกเขา ตัวอย่างง่ายๆ คือการเรียงลำดับ สมมติว่าเราเขียนการเรียงลำดับที่ซับซ้อนซึ่งมีลักษณะดังนี้:
public void mySuperSort() {
    // ... do something here
    if(compare(obj1, obj2) > 0)
    // ... and here we do something
}
โดยที่ifเราเรียกเมธอดcompare()ว่า ส่งวัตถุสองชิ้นที่เราเปรียบเทียบไปที่นั่น และเราต้องการค้นหาว่าวัตถุใดที่ "ใหญ่กว่า" เราจะใส่อันที่ "มากกว่า" ก่อนอันที่ "เล็กกว่า" ฉันเขียน "เพิ่มเติม" ในเครื่องหมายคำพูดเพราะเรากำลังเขียนวิธีการสากลที่ไม่เพียงแต่สามารถเรียงลำดับจากน้อยไปมากเท่านั้น แต่ยังเรียงลำดับจากมากไปน้อยด้วย (ในกรณีนี้ "มากกว่า" จะเป็นวัตถุที่เล็กกว่าโดยพื้นฐานแล้ว และในทางกลับกัน) . เพื่อกำหนดกฎสำหรับวิธีที่เราต้องการจัดเรียง เราต้องส่งต่อกฎนั้นไปยังไฟล์mySuperSort(). ในกรณีนี้ เราจะสามารถ “ควบคุม” วิธีการของเราในขณะที่มันถูกเรียกได้ แน่นอน คุณสามารถเขียนวิธีแยกกันสองวิธีmySuperSortAsc()สำหรับmySuperSortDesc()การเรียงลำดับจากน้อยไปหามากและจากมากไปหาน้อย หรือส่งพารามิเตอร์บางตัวภายในเมธอด (เช่นbooleanถ้าtrueเรียงลำดับจากน้อยไปหามาก และหากfalseเรียงลำดับจากมากไปน้อย) แต่จะเป็นอย่างไรถ้าเราไม่ต้องการเรียงลำดับโครงสร้างที่ไม่ธรรมดา แต่ยกตัวอย่าง รายการอาร์เรย์สตริงล่ะ? วิธีการของเราจะmySuperSort()รู้วิธีจัดเรียงอาร์เรย์สตริงเหล่านี้ได้ อย่างไร ถึงขนาด? ตามความยาวของคำทั้งหมด? อาจจะเรียงตามตัวอักษร ขึ้นอยู่กับแถวแรกในอาร์เรย์? แต่จะเกิดอะไรขึ้นหากในบางกรณี เราต้องเรียงลำดับรายการอาร์เรย์ตามขนาดของอาร์เรย์ และในอีกกรณีหนึ่ง ตามความยาวรวมของคำในอาร์เรย์ ฉันคิดว่าคุณเคยได้ยินเกี่ยวกับตัวเปรียบเทียบแล้ว และในกรณีเช่นนี้ เราก็เพียงส่งออบเจ็กต์ตัวเปรียบเทียบไปยังวิธีการเรียงลำดับของเรา ซึ่งเราจะอธิบายกฎเกณฑ์ที่เราต้องการเรียงลำดับ เนื่องจากมีการใช้วิธีมาตรฐานsort()บนหลักการเดียวกันกับmySuperSort()ในตัวอย่าง ฉันจะใช้วิธีsort()มาตรฐาน
String[] array1 = {"Mother", "soap", "frame"};
String[] array2 = {"I", "Very", "I love", "java"};
String[] array3 = {"world", "work", "May"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

Comparator<String[]> sortByLength = new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
};

Comparator<String[]> sortByWordsLength = new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        int length1 = 0;
        int length2 = 0;
        for (String s : o1) {
            length1 += s.length();
        }
        for (String s : o2) {
            length2 += s.length();
        }
        return length1 - length2;
    }
};

arrays.sort(sortByLength);
ผลลัพธ์:
  1. แม่ล้างกรอบ
  2. สันติภาพแรงงานอาจ
  3. ฉันรักจาวาจริงๆ
ที่นี่อาร์เรย์จะถูกจัดเรียงตามจำนวนคำในแต่ละอาร์เรย์ อาร์เรย์ที่มีคำน้อยกว่าจะถือว่า "เล็กกว่า" นั่นเป็นสาเหตุที่มันมาตอนเริ่มต้น คำที่มีคำมากกว่านั้นถือว่า "มากกว่า" และจบลงที่ตอนท้าย หากsort()เราส่งตัวเปรียบเทียบอื่น ไปยัง method (sortByWordsLength)ผลลัพธ์จะแตกต่างออกไป:
  1. สันติภาพแรงงานอาจ
  2. แม่ล้างกรอบ
  3. ฉันรักจาวาจริงๆ
ตอนนี้อาร์เรย์จะถูกจัดเรียงตามจำนวนตัวอักษรทั้งหมดในคำของอาร์เรย์ดังกล่าว ในกรณีแรกมีตัวอักษร 10 ตัวใน 12 ตัวที่สองและใน 15 ตัวที่สาม หากเราใช้ตัวเปรียบเทียบเพียงตัวเดียว เราจะไม่สามารถสร้างตัวแปรแยกต่างหากสำหรับมันได้ แต่เพียงแค่สร้างอ็อบเจ็กต์ของคลาสที่ไม่ระบุชื่อตรงตำแหน่ง เวลาที่เรียกวิธีsort()การ เช่นนั้น:
String[] array1 = {"Mother", "soap", "frame"};
String[] array2 = {"I", "Very", "I love", "java"};
String[] array3 = {"world", "work", "May"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
ผลลัพธ์จะเหมือนกับในกรณีแรก ภารกิจที่ 1 เขียนตัวอย่างนี้ใหม่เพื่อที่จะเรียงลำดับอาร์เรย์ไม่เรียงลำดับตามจำนวนคำในอาร์เรย์จากน้อยไปหามาก แต่เรียงลำดับจากมากไปน้อย เรารู้ทั้งหมดนี้แล้ว เรารู้วิธีส่งผ่านวัตถุไปยังวิธีการ เราสามารถส่งสิ่งนี้หรือวัตถุนั้นไปยังวิธีการได้ ขึ้นอยู่กับสิ่งที่เราต้องการในขณะนี้ และภายในวิธีที่เราส่งผ่านวัตถุนั้น วิธีการที่เราเขียนการดำเนินการจะถูกเรียกว่า . คำถามเกิดขึ้น: นิพจน์แลมบ์ดาเกี่ยวอะไรกับมัน? เนื่องจากแลมบ์ดาเป็นวัตถุที่มีวิธีเดียวเท่านั้น มันเหมือนกับวัตถุวิธีการ วิธีการห่อในวัตถุ พวกเขามีไวยากรณ์ที่ผิดปกติเล็กน้อย (แต่จะเพิ่มเติมในภายหลัง) ลองมาดูที่รายการนี้อีกครั้ง
arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
ที่นี่เรานำรายการของเราarraysและเรียกวิธีการของมันsort()โดยที่เราส่งวัตถุตัวเปรียบเทียบด้วยวิธีเดียวcompare()(มันไม่สำคัญสำหรับเราว่ามันเรียกว่าอะไร เพราะมันเป็นสิ่งเดียวในวัตถุนี้ เราจะไม่พลาด) วิธีการนี้รับพารามิเตอร์สองตัว ซึ่งเราจะใช้งานต่อไป หากคุณทำงานในIntelliJ IDEAคุณอาจเห็นว่ามันเสนอรหัสนี้ให้คุณสั้นลงอย่างมากได้อย่างไร:
arrays.sort((o1, o2) -> o1.length - o2.length);
นั่นคือวิธีที่หกบรรทัดกลายเป็นหนึ่งบรรทัดสั้น ๆ 6 บรรทัดถูกเขียนใหม่เป็นบรรทัดสั้นหนึ่งบรรทัด มีบางอย่างหายไป แต่ฉันรับประกันว่าไม่มีอะไรสำคัญหายไป และโค้ดนี้จะทำงานได้เหมือนกับคลาสที่ไม่ระบุชื่อทุกประการ ภารกิจที่ 2 หาวิธีเขียนวิธีแก้ปัญหา 1 ใหม่โดยใช้ lambdas (เป็นทางเลือกสุดท้าย ขอให้IntelliJ IDEAเปลี่ยนคลาสที่ไม่ระบุชื่อของคุณให้เป็น lambda)

พูดคุยเกี่ยวกับอินเทอร์เฟซ

โดยพื้นฐานแล้วอินเทอร์เฟซเป็นเพียงรายการวิธีการเชิงนามธรรม เมื่อเราสร้างคลาสและบอกว่าจะใช้อินเทอร์เฟซบางประเภท เราต้องเขียนในชั้นเรียนของเราถึงการใช้งานวิธีการที่ระบุไว้ในอินเทอร์เฟซ (หรือเป็นทางเลือกสุดท้าย ไม่ใช่เขียน แต่ทำให้คลาสเป็นนามธรรม ). มีอินเทอร์เฟซที่มีวิธีการที่แตกต่างกันมากมาย (เช่นList) มีอินเทอร์เฟซที่มีเพียงวิธีเดียวเท่านั้น (เช่น Comparator หรือ Runnable เดียวกัน) มีอินเทอร์เฟซต่างๆ ที่ไม่มีวิธีเดียวเลย (เรียกว่าอินเทอร์เฟซแบบมาร์กเกอร์ เช่น Serializable) อินเทอร์เฟซที่มีเพียงวิธีเดียวเรียกอีกอย่างว่าอินเทอร์เฟซที่ใช้งานได้ ใน Java 8 พวกเขาจะถูกทำเครื่องหมายด้วย คำอธิบาย ประกอบ พิเศษ @FunctionalInterface เป็นการเชื่อมต่อกับวิธีเดียวที่เหมาะสำหรับการใช้นิพจน์แลมบ์ดา ดังที่ฉันได้กล่าวไว้ข้างต้น นิพจน์แลมบ์ดาเป็นวิธีการที่รวมอยู่ในวัตถุ และเมื่อเราส่งวัตถุดังกล่าวไปที่ไหนสักแห่ง อันที่จริง เราก็ผ่านวิธีเดียวนี้ไป ปรากฎว่ามันไม่สำคัญสำหรับเราว่าวิธีนี้เรียกว่าอะไร สิ่งที่สำคัญสำหรับเราคือพารามิเตอร์ที่เมธอดนี้ใช้ และอันที่จริงแล้วคือโค้ดของเมธอดเอง โดยพื้นฐานแล้วนิพจน์แลมบ์ดาคือ การใช้อินเทอร์เฟซการทำงาน เมื่อเราเห็นอินเทอร์เฟซด้วยวิธีเดียว นั่นหมายความว่าเราสามารถเขียนคลาสที่ไม่ระบุชื่อดังกล่าวใหม่ได้โดยใช้แลมบ์ดา หากอินเทอร์เฟซมีมากกว่าหนึ่งหรือน้อยกว่าหนึ่งวิธี นิพจน์แลมบ์ดาจะไม่เหมาะกับเรา และเราจะใช้คลาสที่ไม่ระบุชื่อ หรือแม้แต่คลาสปกติ ถึงเวลาที่จะเจาะเข้าไปในแลมบ์ดา :)

ไวยากรณ์

ไวยากรณ์ทั่วไปเป็นดังนี้:
(параметры) -> {тело метода}
นั่นคือวงเล็บซึ่งอยู่ข้างในคือพารามิเตอร์ของวิธีการ "ลูกศร" (ซึ่งเป็นอักขระสองตัวในแถว: ลบและมากกว่า) หลังจากนั้นเนื้อหาของวิธีการจะอยู่ในเครื่องหมายปีกกาเช่นเคย พารามิเตอร์สอดคล้องกับพารามิเตอร์ที่ระบุในอินเทอร์เฟซเมื่ออธิบายวิธีการ หากคอมไพลเลอร์สามารถกำหนดประเภทของตัวแปรได้อย่างชัดเจน (ในกรณีของเรา เป็นที่ทราบกันดีอยู่แล้วว่าเรากำลังทำงานกับอาร์เรย์ของสตริง เนื่องจากมันถูกListพิมพ์อย่างแม่นยำด้วยอาร์เรย์ของสตริง) ดังนั้นประเภทของตัวแปรString[]ไม่จำเป็นต้อง ถูกเขียน
หากคุณไม่แน่ใจ ให้ระบุประเภท แล้ว IDEA จะเน้นเป็นสีเทาหากไม่จำเป็น
คุณสามารถอ่านเพิ่มเติมได้ในบทช่วยสอนของ Oracleเป็นต้น สิ่งนี้เรียกว่า" การพิมพ์เป้าหมาย" คุณสามารถตั้งชื่อตัวแปรใดๆ ก็ได้ ไม่จำเป็นต้องระบุในอินเทอร์เฟซ หากไม่มีพารามิเตอร์ ก็ให้ใส่เพียงวงเล็บเท่านั้น หากมีพารามิเตอร์เพียงตัวเดียว ให้ระบุเพียงชื่อตัวแปรที่ไม่มีวงเล็บ เราได้จัดเรียงพารามิเตอร์แล้ว ตอนนี้เกี่ยวกับเนื้อความของนิพจน์แลมบ์ดาเอง ภายในวงเล็บปีกกา ให้เขียนโค้ดเหมือนกับวิธีปกติ หากโค้ดทั้งหมดของคุณมีเพียงบรรทัดเดียว คุณไม่จำเป็นต้องเขียนวงเล็บปีกกาเลย (เช่นเดียวกับ ifs และ loops) หากแลมบ์ดาของคุณส่งคืนบางสิ่ง แต่เนื้อหาของมันประกอบด้วยหนึ่งบรรทัดก็returnไม่จำเป็นต้องเขียนเลย returnแต่ถ้าคุณมีเครื่องหมายปีกกา คุณจะต้องเขียน . . อย่างชัดเจน เช่นเดียวกับวิธีปกติ

ตัวอย่าง

ตัวอย่างที่ 1
() -> {}
ตัวเลือกที่ง่ายที่สุด และไร้ความหมายที่สุด :) เพราะมันทำอะไรไม่ได้เลย ตัวอย่างที่ 2
() -> ""
ก็เป็นตัวเลือกที่น่าสนใจเช่นกัน ไม่ยอมรับสิ่งใดและส่งกลับสตริงว่าง ( returnละเว้นว่าไม่จำเป็น) เหมือนกัน แต่มีreturn:
() -> {
    return "";
}
ตัวอย่างที่ 3 สวัสดีชาวโลกโดยใช้แลมบ์ดา
() -> System.out.println("Hello world!")
รับอะไรเลย ไม่ส่งคืนอะไรเลย (เราไม่สามารถใส่returnก่อนการโทรได้System.out.println()เนื่องจากประเภทการส่งคืนในเมธอดprintln() — void)เพียงแสดงคำจารึกบนหน้าจอ เหมาะอย่างยิ่งสำหรับการใช้งานอินเทอร์เฟRunnableซ ตัวอย่างเดียวกันนี้สมบูรณ์กว่า:
public class Main {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello world!")).start();
    }
}
หรือเช่นนี้:
public class Main {
    public static void main(String[] args) {
        Thread t = new Thread(() -> System.out.println("Hello world!"));
        t.start();
    }
}
หรือคุณสามารถบันทึกนิพจน์แลมบ์ดาเป็นวัตถุประเภทRunnableแล้วส่งต่อไปยังตัวสร้างthread’а:
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println("Hello world!");
        Thread t = new Thread(runnable);
        t.start();
    }
}
มาดูช่วงเวลาของการบันทึกนิพจน์แลมบ์ดาลงในตัวแปรกันดีกว่า อินเทอร์เฟซ บอกเราว่า วัตถุRunnableต้องมีเมธอด public void run()ตามอินเทอร์เฟซ วิธีการ run ไม่ยอมรับสิ่งใดเป็นพารามิเตอร์ และมันไม่คืนอะไร(void)เลย ดังนั้นเมื่อเขียนในลักษณะนี้วัตถุจะถูกสร้างขึ้นด้วยวิธีการบางอย่างที่ไม่ยอมรับหรือส่งคืนสิ่งใด ซึ่งค่อนข้างสอดคล้องกับวิธีการในrun()อินเทอร์เฟส Runnableนั่นเป็นสาเหตุที่เราสามารถใส่นิพจน์แลมบ์ดานี้ลงในตัวแปรRunnableเช่น ตัวอย่างที่ 4
() -> 42
ขอย้ำอีกครั้งว่ามันไม่ยอมรับสิ่งใดเลย แต่จะส่งคืนตัวเลข 42 นิพจน์แลมบ์ดานี้สามารถวางไว้ในตัวแปรประเภทCallableเนื่องจากอินเทอร์เฟซนี้กำหนดเพียงวิธีเดียวเท่านั้น ซึ่งมีลักษณะดังนี้:
V call(),
Vประเภทของค่าตอบแทนอยู่ ที่ไหน (ในกรณีของเรา int) ดังนั้นเราจึงสามารถเก็บนิพจน์แลมบ์ดาได้ดังนี้:
Callable<Integer> c = () -> 42;
ตัวอย่างที่ 5. แลมบ์ดาในหลายบรรทัด
() -> {
    String[] helloWorld = {"Hello", "world!"};
    System.out.println(helloWorld[0]);
    System.out.println(helloWorld[1]);
}
นี่เป็นนิพจน์แลมบ์ดาที่ไม่มีพารามิเตอร์และประเภทการส่งคืนvoid(เนื่องจากไม่มีreturn) ตัวอย่างที่ 6
x -> x
ที่นี่เรานำบางสิ่งไปเป็นตัวแปรхแล้วส่งคืน โปรดทราบว่าหากยอมรับเพียงพารามิเตอร์เดียว ก็ไม่จำเป็นต้องเขียนวงเล็บล้อมรอบพารามิเตอร์นั้น เหมือนกัน แต่มีวงเล็บ:
(x) -> x
และนี่คือตัวเลือกที่มีความชัดเจนreturn:
x -> {
    return x;
}
หรือเช่นนี้ด้วยวงเล็บและreturn:
(x) -> {
    return x;
}
หรือระบุประเภทอย่างชัดเจน (และตามด้วยวงเล็บ):
(int x) -> x
ตัวอย่างที่ 7
x -> ++x
เรายอมรับхและคืน แต่สำหรับ1มากกว่านั้น คุณยังสามารถเขียนใหม่ได้ดังนี้:
x -> x + 1
ในทั้งสองกรณี เราไม่ได้ระบุวงเล็บล้อมรอบพารามิเตอร์ เนื้อหาของวิธีการ และคำreturnเนื่องจากไม่จำเป็น ตัวเลือกที่มีวงเล็บเหลี่ยมและส่งคืนอธิบายไว้ในตัวอย่างที่ 6 ตัวอย่างที่ 8
(x, y) -> x % y
เรายอมรับบางส่วนхและ ส่งคืนส่วนที่ เหลือуของการหารxด้วย yจำเป็นต้องมีวงเล็บรอบพารามิเตอร์ที่นี่แล้ว เป็นทางเลือกเฉพาะเมื่อมีเพียงพารามิเตอร์เดียวเท่านั้น เช่นนี้โดยมีข้อบ่งชี้ประเภทที่ชัดเจน:
(double x, int y) -> x % y
ตัวอย่างที่ 9
(Cat cat, String name, int age) -> {
    cat.setName(name);
    cat.setAge(age);
}
เรายอมรับวัตถุ Cat ซึ่งเป็นสตริงที่มีชื่อและอายุจำนวนเต็ม ในวิธีการนั้น เราได้ตั้งชื่อและอายุที่ส่งผ่านให้กับ Cat เนื่องจาก ตัวแปรcatของเรา เป็นประเภทอ้างอิง วัตถุ Cat ที่อยู่นอกนิพจน์แลมบ์ดาจะเปลี่ยนไป (จะได้รับชื่อและอายุที่ส่งผ่านภายใน) เวอร์ชันที่ซับซ้อนกว่าเล็กน้อยซึ่งใช้แลมบ์ดาที่คล้ายกัน:
public class Main {
    public static void main(String[] args) {
        // create a cat and print to the screen to make sure it's "blank"
        Cat myCat = new Cat();
        System.out.println(myCat);

        // create lambda
        Settable<Cat> s = (obj, name, age) -> {
            obj.setName(name);
            obj.setAge(age);
        };

        // call the method, to which we pass the cat and the lambda
        changeEntity(myCat, s);
        // display on the screen and see that the state of the cat has changed (has a name and age)
        System.out.println(myCat);
    }

    private static <T extends WithNameAndAge>  void changeEntity(T entity, Settable<T> s) {
        s.set(entity, "Murzik", 3);
    }
}

interface WithNameAndAge {
    void setName(String name);
    void setAge(int age);
}

interface Settable<C extends WithNameAndAge> {
    void set(C entity, String name, int age);
}

class Cat implements WithNameAndAge {
    private String name;
    private int age;

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
ผลลัพธ์: Cat{name='null', age=0} Cat{name='Murzik', age=3} อย่างที่คุณเห็น ในตอนแรกวัตถุ Cat มีสถานะเดียว แต่หลังจากใช้นิพจน์แลมบ์ดา สถานะก็เปลี่ยนไป . นิพจน์ Lambda ทำงานได้ดีกับยาชื่อสามัญ และหากเราต้องการสร้างคลาสDogเช่น ที่จะนำไปใช้ด้วยWithNameAndAgeดังนั้นในวิธีที่main()เราสามารถดำเนินการเดียวกันกับ Dog ได้โดยไม่ต้องเปลี่ยนนิพจน์แลมบ์ดาเลย ภารกิจที่ 3 เขียนอินเทอร์เฟซการทำงานด้วยวิธีการที่รับตัวเลขและส่งกลับค่าบูลีน เขียนการใช้งานอินเทอร์เฟซดังกล่าวในรูปแบบของนิพจน์แลมบ์ดาที่ส่งคืนtrueหากตัวเลขที่ส่งผ่านหารด้วย 13 ลงตัวโดยไม่มีเศษ ภารกิจ ที่4 เขียนอินเทอร์เฟซการทำงานด้วยวิธีการที่รับสองสตริงและส่งกลับสตริงเดียวกัน เขียนการใช้งานอินเทอร์เฟซดังกล่าวในรูปแบบของแลมบ์ดาที่ส่งคืนสตริงที่ยาวที่สุด ภารกิจที่ 5 เขียนอินเทอร์เฟซการทำงานด้วยวิธีการที่ยอมรับจำนวนเศษส่วนสามตัว: a, bและcส่งกลับจำนวนเศษส่วนเดียวกัน เขียนการใช้งานอินเทอร์เฟซดังกล่าวในรูปแบบของนิพจน์แลมบ์ดาที่ส่งคืนการแบ่งแยก ใครลืมD = b^2 - 4ac . ภารกิจที่ 6 ใช้อินเทอร์เฟซการทำงานจากภารกิจที่ 5 เขียนนิพจน์แลมบ์ดาที่ส่งคืนผลลัพธ์ของการดำเนินa * b^cการ เป็นที่นิยมเกี่ยวกับนิพจน์แลมบ์ดาใน Java พร้อมตัวอย่างและงาน ส่วนที่ 2
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION