ทำไมต้องใช้นิพจน์แลมบ์ดา
มาดูตัวอย่างสองสามอย่าง:
ตัวอย่างแรกคือการดำเนินงานในเธรดแยกต่างหากซึ่งเรามักจะนำไปใช้ดังนี้:
ผู้ปฏิบัติงานในชั้นเรียนใช้งาน {โมฆะสาธารณะเรียกใช้ () {สำหรับ (int i = 0; i <100; i ++) Dowork (); } ... } คนงาน w = คนงานใหม่ (); เธรดใหม่ (w). start ();ตัวอย่างที่สองคือวิธีการเปรียบเทียบสตริงที่กำหนดเอง (ตามความยาวสตริง) ซึ่งโดยทั่วไปแล้ว:
คลาส LengthComparator ใช้ตัวเปรียบเทียบ <String> {Public Int Compare (String First, String Second) {return Integer.Compare (first.length (), second.length ()); }} arrays.sort (สตริง, ความยาวใหม่คอมไพล์ ());ในตัวอย่างที่สามใน Javafx เพิ่มการโทรกลับไปที่ปุ่ม:
button.setOnaction (ใหม่ EventHandler <ActionEvent> () {มือจับสาธารณะ (เหตุการณ์ ActionEvent) {System.out.println ("ขอบคุณสำหรับการคลิก!");}});ตัวอย่างเหล่านี้มีสิ่งหนึ่งที่เหมือนกันซึ่งเป็นครั้งแรกที่พวกเขากำหนดบล็อกของรหัสส่งผ่านไปยังวัตถุหรือวิธีการแล้วดำเนินการ ก่อนที่จะนิพจน์แลมบ์ดา Java ไม่อนุญาตให้ส่งผ่านบล็อกรหัสโดยตรงเนื่องจาก Java มุ่งเน้นไปที่วัตถุดังนั้นวัตถุจะต้องผ่านไปเพื่อห่อหุ้มบล็อกรหัสที่จะดำเนินการลงในวัตถุ
ไวยากรณ์การแสดงออกของแลมบ์ดา
LengthComparator ในตัวอย่างที่สองด้านบนแสดงเป็นนิพจน์แลมบ์ดา:
(สตริงแรก, สตริงที่สอง) -> integer.compare (first.length (), second.length ());
-> ก่อนหน้านี้เป็นรายการพารามิเตอร์ตามด้วยตัวนิพจน์
หากเนื้อหาของคำสั่งการแสดงออกมีมากกว่าหนึ่งบรรทัดร่างกายของคำสั่งจะถูกเขียนใน {} เช่นเดียวกับฟังก์ชั่นทั่วไป:
(สตริงแรก, สตริงที่สอง) -> {ถ้า (first.length ()> second.length ()) {return 1; } else if (first.length () == second.length ()) {return 0; } else {return -1; -หากไม่มีพารามิเตอร์ () ยังคงต้องนำติดตัวไปด้วย ตัวอย่างเช่นตัวอย่างแรกด้านบนสามารถแสดงเป็น:
() -> {สำหรับ (int i = 0; i <1000; i ++) {dowork (); -หากประเภทของพารามิเตอร์สามารถอนุมานได้โดยอัตโนมัติจากบริบทคุณสามารถละเว้น:
ตัวเปรียบเทียบ <String> comp = (ครั้งแรก, วินาที) // เหมือนกับ (สตริงแรก, สตริงที่สอง) -> integer.compare (first.length (), second.length ());
หากมีพารามิเตอร์เดียวเท่านั้นและสามารถอนุมานประเภทได้โดยอัตโนมัติวงเล็บ () สามารถละเว้นได้:
// แทนที่จะเป็น (เหตุการณ์) -> หรือ (กิจกรรม ActionEvent) -> EventHandler <ActionEvent> Listener = Event -> System.out.println ("ขอบคุณสำหรับการคลิก!");ประเภทของค่าผลตอบแทนของนิพจน์แลมบ์ดาถูกอนุมานโดยอัตโนมัติดังนั้นจึงไม่จำเป็นต้องระบุ ในนิพจน์แลมบ์ดาสาขาที่มีเงื่อนไขบางแห่งมีค่าส่งคืน แต่สาขาอื่น ๆ ไม่มีค่าส่งคืนซึ่งไม่ได้รับอนุญาตเช่น:
(x) -> {ถ้า (x> = 0) {return 1; -นอกจากนี้ความแตกต่างระหว่างนิพจน์แลมบ์ดาและคำสั่งแลมบ์ดาคือนิพจน์แลมบ์ดาไม่จำเป็นต้องเขียนคำหลักส่งคืน Java Runtime จะส่งคืนผลลัพธ์ของนิพจน์เป็นค่าส่งคืนในขณะที่คำสั่ง Lambda เป็นนิพจน์ที่เขียนใน {} และต้องใช้คำหลักส่งคืนเช่น: ตัวอย่างเช่น:
// นิพจน์ lambdacomparator <string> comp1 = (ครั้งแรก, วินาที) -> integer.compare (first.length (), second.length ()); // คำสั่ง lambdacomparator <string> comp2 = (แรก, วินาที) -> {return integer.compare ส่วนต่อประสานการทำงาน
หากอินเทอร์เฟซมีวิธีนามธรรมเพียงวิธีเดียวจะเรียกว่า
อินเทอร์เฟซที่ใช้งานได้เช่น runnable, comparator ฯลฯ
ในสถานที่ใด ๆ ที่ต้องการวัตถุอินเทอร์เฟซที่ใช้งานได้คุณสามารถใช้นิพจน์แลมบ์ดา:
array.sort (คำ, (ครั้งแรก, วินาที) -> integer.compare (first.length (), second.length ()));
ที่นี่พารามิเตอร์ที่สองของการเรียงลำดับ () ต้องการวัตถุเปรียบเทียบและตัวเปรียบเทียบคือ
อินเทอร์เฟซที่ใช้งานได้ดังนั้นคุณสามารถส่งผ่านในนิพจน์แลมบ์ดาโดยตรง เมื่อเรียกวิธีการเปรียบเทียบ () ของวัตถุมันคือการดำเนินการร่างคำสั่งในนิพจน์แลมบ์ดา
หากคำสั่งของนิพจน์แลมบ์ดาโยนข้อยกเว้นวิธีนามธรรมที่สอดคล้องกันในอินเตอร์เฟสการทำงานจะต้องโยนข้อยกเว้นมิฉะนั้นจำเป็นต้องจับข้อยกเว้นอย่างชัดเจนในนิพจน์แลมบ์ดา:
runnable r = ()-> {system.out.println ("-------"); ลอง {thread.sleep (10); } catch (interruptedException e) {// catch exception}}; callable <string> c = ()-> {system.out.println ("----------"); Thread.sleep (10); กลับ "";}; วิธีการอ้างอิง
หากพารามิเตอร์ของนิพจน์แลมบ์ดาถูกส่งผ่านเป็นพารามิเตอร์ไปยังวิธีการและเอฟเฟกต์การดำเนินการของพวกเขาจะเหมือนกันการแสดงออกของแลมบ์ดาสามารถแสดงได้โดยใช้การอ้างอิงวิธีการและสองวิธีต่อไปนี้เทียบเท่า:
(x) -> system.out.println (x) system.out :: println
ในหมู่พวกเขา System.out :: println เรียกว่าการอ้างอิงวิธี
การอ้างอิงวิธีการส่วนใหญ่มาในสามรูปแบบ:
สำหรับสองวิธีแรกพารามิเตอร์ Lambda Expression ที่สอดคล้องกันและพารามิเตอร์ Method จะเหมือนกันเช่น:
System.out :: println (x) -> system.out.println (x) คณิตศาสตร์ :: pow (x, y) -> math.pow (x, y)
สำหรับวิธีที่สามในตัวแปรนิพจน์แลมบ์ดาที่สอดคล้องกันพารามิเตอร์แรกจะใช้เป็นวัตถุวิธีการที่เรียกว่าและพารามิเตอร์อื่น ๆ จะใช้เป็นพารามิเตอร์วิธีการเช่น::
String :: comperetoignorecase (S1, S2) -> S1.Comparetoignorecase (S2) 1.5 การอ้างอิงตัวสร้าง
การอ้างอิงตัวสร้างนั้นคล้ายกับการอ้างอิงวิธีการ แต่เป็นวิธีพิเศษ: ใหม่ ตัวสร้างเฉพาะถูกกำหนดโดยสภาพแวดล้อมบริบทเช่น:
รายการ <String> labels = ... ; สตรีม <ปุ่ม> สตรีม = labels.stream (). แผนที่ (ปุ่ม :: ใหม่);
ปุ่ม :: ใหม่เทียบเท่ากับ (x) -> ปุ่ม (x) ดังนั้นตัวสร้างที่เรียกว่าคือ: ปุ่ม (x);
นอกเหนือจากการสร้างวัตถุเดียวคุณยังสามารถสร้างอาร์เรย์ของวัตถุเช่นสองเทียบเท่าต่อไปนี้:
int [] :: new (x) -> int ใหม่ [x]
ขอบเขตตัวแปร
Lambd Expressions จับตัวแปรที่มีอยู่ในขอบเขตปัจจุบันเช่น:
โมฆะสาธารณะ repeatMessage (ข้อความสตริง, จำนวน int) {runnable r = () -> {สำหรับ (int i = 0; i <count; i ++) {system.out.println (ข้อความ); Thread.yield (); - เธรดใหม่ (r) .start ();}แต่ตัวแปรเหล่านี้จะต้องเปลี่ยนไม่ได้ทำไม? ดูตัวอย่างต่อไปนี้:
int matches = 0; สำหรับ (path p: files) เธรดใหม่ (() -> {ถ้า (p มีคุณสมบัติบางอย่าง) ตรงกับ ++;}) เริ่มต้น (); // ผิดกฎหมายในการกลายพันธุ์การแข่งขันเนื่องจากตัวแปรที่ไม่แน่นอนไม่ได้เป็นแบบเธรดที่ปลอดภัยในการแสดงออกของแลมบ์ดาจึงสอดคล้องกับข้อกำหนดของคลาสชั้นในและเฉพาะตัวแปรสุดท้ายที่กำหนดไว้ภายนอกเท่านั้นที่สามารถอ้างอิงได้ในคลาสภายใน
ขอบเขตของนิพจน์แลมบ์ดานั้นเหมือนกับของบล็อกรหัสซ้อนกันดังนั้นชื่อพารามิเตอร์หรือชื่อตัวแปรในนิพจน์ Lambd ไม่สามารถขัดแย้งกับตัวแปรท้องถิ่นเช่น::
PATH FIRST = PATHS.GET ("/USR/BIN"); comparator <String> comp = (ครั้งแรก, วินาที) -> integer.Compare (first.length (), second.length ()); // ข้อผิดพลาด: ตัวแปรก่อนกำหนดไว้แล้วหากตัวแปรนี้อ้างอิงในนิพจน์แลมบ์ดาการอ้างอิงเป็นตัวแปรนี้ของวิธีการที่สร้างนิพจน์แลมบ์ดาเช่น:
แอปพลิเคชันคลาสสาธารณะ () {โมฆะสาธารณะ Dowork () {runnable runner = () -> {... ; System.out.println (this.toString ()); - - ดังนั้นที่นี่ this.toString () เรียก toString () ของวัตถุแอปพลิเคชันไม่สามารถเรียกใช้งานได้
วัตถุ
วิธีการเริ่มต้น
สามารถมีวิธีนามธรรมในอินเทอร์เฟซเท่านั้น หากมีการเพิ่มวิธีการใหม่ในอินเทอร์เฟซที่มีอยู่คลาสการใช้งานทั้งหมดของอินเตอร์เฟสจำเป็นต้องใช้วิธีนี้
Java 8 แนะนำแนวคิดของวิธีการเริ่มต้นและเพิ่มวิธีการเริ่มต้นไปยังอินเตอร์เฟสซึ่งจะไม่ทำลายกฎอินเทอร์เฟซที่มีอยู่ คลาสการใช้งานอินเตอร์เฟสสามารถเลือกที่จะแทนที่หรือสืบทอดวิธีเริ่มต้นโดยตรงเช่น:
คนอินเทอร์เฟซ {long getId (); สตริงเริ่มต้น getName () {return "John Q. สาธารณะ"; -Java อนุญาตให้มีการสืบทอดหลายครั้ง วิธีจัดการกับความขัดแย้งนี้หากวิธีการที่กำหนดไว้ในคลาสพาเรนต์ของคลาสนั้นเหมือนกับวิธีเริ่มต้นที่กำหนดไว้ในอินเทอร์เฟซหรืออินเทอร์เฟซทั้งสองของคลาสนั้นเหมือนกันวิธีจัดการกับความขัดแย้งนี้อย่างไร กฎการประมวลผลมีดังนี้:
หากวิธีการที่ขัดแย้งกันระหว่างคลาสแม่และอินเทอร์เฟซ: วิธีการในคลาสพาเรนต์จะเหนือกว่าและวิธีการในอินเทอร์เฟซจะถูกละเว้น;
หากวิธีการเริ่มต้นในสองอินเทอร์เฟซขัดแย้งคุณต้องแทนที่วิธีการเพื่อแก้ไขความขัดแย้ง
วิธีการคงที่
ก่อน Java 8 สามารถกำหนดตัวแปรคงที่เฉพาะในอินเทอร์เฟซ เริ่มต้นจาก Java 8 วิธีการคงที่สามารถเพิ่มลงในอินเทอร์เฟซเช่น
อินเทอร์เฟซเปรียบเทียบได้เพิ่มชุดของวิธีการคงที่ของ ComparingXXX เช่น:
สาธารณะคงที่ <T> ตัวเปรียบเทียบ <T> การเปรียบเทียบ (TOINTFUNCTION <? super t> keyExtractor) {objects.requirenonnull (keyextractor); return (comparator <t> & serializable) (C1, C2) -> integer.compare (keyExtractor.applyasint (C1), keyExtractor.applyasint (C2));}การใช้วิธีการคงที่นี้สองวิธีต่อไปนี้ก็เทียบเท่า:
1.
array.sort (เมือง, (ครั้งแรก, วินาที) -> integer.compare (first.length (), second.length ()));
2.
array.sort (เมือง, compomparingint (สตริง :: ความยาว));
ดังนั้นเมื่อเราออกแบบอินเทอร์เฟซของเราเองในอนาคตเราไม่จำเป็นต้องกำหนดคลาสเครื่องมือแยกต่างหากอีกต่อไป (เช่นคอลเลกชัน/คอลเลกชัน)
เพียงใช้วิธีการคงที่ในอินเทอร์เฟซ
ชั้นเรียนภายในที่ไม่ระบุชื่อ
ในโลก Java คลาสภายในที่ไม่ระบุชื่อสามารถใช้การดำเนินงานที่อาจดำเนินการได้เพียงครั้งเดียวในแอปพลิเคชัน ตัวอย่างเช่นในแอปพลิเคชัน Android จะมีการจัดการเหตุการณ์การคลิกปุ่ม คุณไม่จำเป็นต้องเขียนคลาสแยกต่างหากเพื่อจัดการกับเหตุการณ์คลิกคุณสามารถทำได้ด้วยคลาสภายในที่ไม่ระบุชื่อ:
ปุ่มปุ่ม = (ปุ่ม) findViewById (r.id.button1); button.setonclicklistener (ใหม่ onclicklistener () {@Override โมฆะสาธารณะ onClick (ดูดู) {toast.maketext (mainactivity.his, "ปุ่มคลิก", toast.length_short) ตัวอย่างแลมบ์ดา 1. แลมบ์ดาที่รัน ได้มาดูตัวอย่างหลายตัวอย่าง นี่คือตัวอย่างของ Runnable: โมฆะสาธารณะ runnableTest () {system.out.println ("=== runnableTest ==="); // unonymous runnable runnable r1 = new runnable () {@Override โมฆะสาธารณะ Run () {System.out.println ("Hello World One!"); - // lambda runnable runnable r2 = () -> system.out.println ("Hello World Two!"); // ดำเนินการสองฟังก์ชั่นการเรียกใช้ r1.run (); r2.run (); - โมฆะสาธารณะ runnableTest () {system.out.println ("=== runnableTest ==="); // unonymous runnable runnable r1 = new runnable () {@Override โมฆะสาธารณะ Run () {System.out.println ("Hello World One!"); - // lambda runnable runnable r2 = () -> system.out.println ("Hello World Two!"); // ดำเนินการสองฟังก์ชั่นการเรียกใช้ r1.run (); r2.run (); - การใช้งานหรือการส่งคืนจะไม่ถูกส่งคืน นิพจน์แลมบ์ดาที่รันได้ใช้บล็อกโค้ดเพื่อลดความซับซ้อนของรหัสห้าองค์ประกอบลงในคำสั่งเดียว บุคคลชั้นเรียนสาธารณะ {สตริงส่วนตัวที่กำหนดชื่อ; นามสกุลสตริงส่วนตัว อายุ int ส่วนตัว; เพศส่วนตัว อีเมลสตริงส่วนตัว โทรศัพท์สตริงส่วนตัว ที่อยู่สตริงส่วนตัว} บุคคลชั้นเรียนสาธารณะ {สตริงส่วนตัวที่กำหนดชื่อ; นามสกุลสตริงส่วนตัว อายุ int ส่วนตัว; เพศส่วนตัว อีเมลสตริงส่วนตัว โทรศัพท์สตริงส่วนตัว ที่อยู่สตริงส่วนตัว} ต่อไปนี้เป็นวิธีการใช้อินเทอร์เฟซเปรียบเทียบโดยใช้คลาสภายในที่ไม่ระบุชื่อและนิพจน์แลมบ์ดา: Public Class Comparatortest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {รายการ <person> personlist = person.createshortlist (); // ใช้คลาสชั้นในเพื่อใช้คอลเลกชันการเรียงลำดับ (PersonList, ตัวเปรียบเทียบใหม่ <person> () {public int Compare (บุคคล P1, บุคคล p2) {return p1.getSurname (). compereto (p2.getSurname ());}}); System.out.println ("=== นามสกุล ASC ที่เรียงลำดับ ==="); สำหรับ (Person P: PersonList) {p.printName (); } // การใช้งานโดยใช้ Lambda Expression // Accending System.out.println ("=== นามสกุล ASC ที่เรียงลำดับ ==="); Collections.sort (PersonList, (บุคคล P1, Person P2) -> P1.GetSurname (). ComparEto (P2.GetSurname ())); สำหรับ (Person P: PersonList) {p.printName (); } // desc system.out.out.println ("=== นามสกุล desc ที่เรียงลำดับ ==="); collections.sort (PersonList, (P1, P2) -> P2.GetSurname (). ComparEto (P1.GetSurname ())); สำหรับ (Person P: PersonList) {p.printName (); - Public Class Comparatortest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {รายการ <person> personlist = person.createshortlist (); // ใช้คลาสชั้นในเพื่อใช้คอลเลกชันการเรียงลำดับ (PersonList, ตัวเปรียบเทียบใหม่ <person> () {public int Compare (บุคคล P1, บุคคล p2) {return p1.getSurname (). compereto (p2.getSurname ());}}); System.out.println ("=== นามสกุล ASC ที่เรียงลำดับ ==="); สำหรับ (Person P: PersonList) {p.printName (); } // การใช้งานโดยใช้ Lambda Expression // Accending System.out.println ("=== นามสกุล ASC ที่เรียงลำดับ ==="); Collections.sort (PersonList, (บุคคล P1, Person P2) -> P1.GetSurname (). ComparEto (P2.GetSurname ())); สำหรับ (Person P: PersonList) {p.printName (); } // desc system.out.out.println ("=== นามสกุล desc ที่เรียงลำดับ ==="); collections.sort (PersonList, (P1, P2) -> P2.GetSurname (). ComparEto (P1.GetSurname ())); สำหรับ (Person P: PersonList) {p.printName (); - คุณจะเห็นได้ว่าชั้นเรียนภายในที่ไม่ระบุชื่อสามารถนำไปใช้ได้ผ่านการแสดงออกของแลมบ์ดา โปรดทราบว่านิพจน์แลมบ์ดาตัวแรกกำหนดประเภทของพารามิเตอร์เป็นบุคคล นิพจน์แลมบ์ดาที่สองละเว้นคำจำกัดความประเภท Lambda Expressions ประเภทสนับสนุนการล้มลงและหากประเภทที่ต้องการสามารถอนุมานได้ผ่านบริบทสามารถละเว้นคำจำกัดความประเภทได้ ที่นี่เนื่องจากเราใช้นิพจน์แลมบ์ดาในตัวเปรียบเทียบที่ใช้คำจำกัดความทั่วไปคอมไพเลอร์สามารถอนุมานพารามิเตอร์ทั้งสองนี้เป็นบุคคล