ใช้แลมบ์ดานิพจน์เมื่อใดก็ตามที่เป็นไปได้ในการฝึกหัด Java


52

ฉันเพิ่งเข้าใจแลมบ์ดานิพจน์ที่นำมาใช้ในจาวา 8 ฉันพบว่าเมื่อใดก็ตามที่ฉันใช้อินเตอร์เฟซที่ใช้งานได้ฉันมักจะใช้นิพจน์แลมบ์ดาแทนการสร้างคลาสที่ใช้อินเทอร์เฟซ

นี่ถือว่าเป็นการปฏิบัติที่ดีหรือไม่? หรือสถานการณ์ของพวกเขาที่ใช้แลมบ์ดาสำหรับอินเทอร์เฟซการใช้งานไม่เหมาะสมหรือไม่


122
กับคำถาม"จะใช้เทคนิค X เมื่อใดก็ตามที่เป็นไปได้การปฏิบัติที่ดี" เพียงคำตอบที่ถูกต้องคือ"ไม่ใช้เทคนิค X ไม่เมื่อใดก็ตามที่เป็นไปได้ แต่เมื่อใดก็ตามที่เหมาะสม"
Doc Brown

ทางเลือกของการใช้แลมบ์ดา (ซึ่งไม่ระบุชื่อ) กับการใช้งานประเภทอื่น ๆ (เช่นการอ้างอิงเมธอด) เป็นคำถามที่น่าสนใจ ฉันมีโอกาสได้พบกับ Josh Bloch เมื่อสองสามวันก่อนและนี่คือหนึ่งในประเด็นที่เขาพูดถึง จากสิ่งที่เขาบอกว่าฉันเข้าใจว่าเขาวางแผนที่จะเพิ่มรายการใหม่ไปยังEffective Javaรุ่นถัดไปที่เกี่ยวข้องกับคำถามนี้
Daniel Pryden

@DocBrown นั่นควรเป็นคำตอบไม่ใช่ความคิดเห็น
gnasher729

1
@ gnasher729: ไม่แน่นอน
Doc Brown เมื่อ

คำตอบ:


116

มีหลายเกณฑ์ที่ควรพิจารณาว่าคุณไม่ได้ใช้แลมบ์ดา:

  • ขนาดแลมบ์ดายิ่งใหญ่ขึ้นเท่าไหร่ก็ยิ่งยากที่จะทำตามตรรกะรอบ ๆ
  • การทำซ้ำการสร้างฟังก์ชั่นที่ตั้งชื่อสำหรับตรรกะซ้ำ ๆ นั้นดีกว่าแม้ว่ามันจะโอเคที่จะทำซ้ำ lambdas ง่าย ๆ ที่กระจายออกไป
  • การตั้งชื่อหากคุณนึกชื่อ semantic ได้คุณควรใช้มันแทนเพราะมันจะช่วยเพิ่มความชัดเจนให้กับโค้ดของคุณ ฉันไม่ได้พูดชื่อเหมือนpriceIsOver100กัน x -> x.price > 100มีความชัดเจนเหมือนกับชื่อนั้น ฉันหมายถึงชื่อเช่นisEligibleVoterนั้นแทนที่รายการเงื่อนไขที่ยาว
  • รังของแลมบ์ดาซ้อนอยู่ในรังอ่านยากจริงๆ

อย่าไปลงน้ำ จำไว้ว่าซอฟต์แวร์นั้นเปลี่ยนแปลงได้ง่าย เมื่อสงสัยให้เขียนทั้งสองวิธีแล้วดูว่าอ่านง่ายกว่ากันไหม


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

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

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

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

3
หากมีข้อสงสัยให้เขียนทั้งสองวิธีแล้วถามคนอื่นที่เก็บรหัสซึ่งอ่านง่ายกว่า
corsiKa

14

มันขึ้นอยู่กับ. เมื่อใดก็ตามที่คุณพบว่าตัวเองใช้แลมบ์ดาเดียวกันในที่ต่าง ๆ คุณควรพิจารณาใช้คลาสที่ใช้อินเทอร์เฟซ แต่ถ้าคุณจะใช้คลาสภายในที่ไม่ระบุชื่อมิฉะนั้นฉันคิดว่าแลมบ์ดานั้นดีกว่ามาก


6
อย่าลืมความเป็นไปได้ของการใช้วิธีการอ้างอิง! Oracle ได้เพิ่ม (และกำลังเพิ่มมากขึ้นใน Java 9) วิธีการคงที่และวิธีการเริ่มต้นทั่วสถานที่ด้วยเหตุผลที่ว่า
Jörg W Mittag

13

ฉันสนับสนุนคำตอบของ Karl Bielefeldt แต่ต้องการให้เพิ่มเติมสั้น ๆ

  • การดีบัก การต่อสู้ของ IDE บางอย่างด้วยขอบเขตภายในแลมบ์ดาและต่อสู้เพื่อแสดงตัวแปรสมาชิกภายในบริบทของแลมบ์ดา ในขณะที่หวังว่าสถานการณ์นี้จะเปลี่ยนบรรทัดมันอาจเป็นเรื่องน่ารำคาญที่จะรักษารหัสของคนอื่นเมื่อมันถูกทิ้งให้เป็นลูกแกะ

เป็นความจริงอย่างแน่นอนของ. NET ไม่ทำให้ฉันหลีกเลี่ยง lambdas แต่ฉันไม่รู้สึกผิดเลยถ้าฉันทำอย่างอื่นแทน
user1172763

3
หากเป็นกรณีนี้คุณกำลังใช้ IDE ผิด ทั้ง IntelliJ และ Netbeans ทำได้ค่อนข้างดีในพื้นที่นั้น
David Foerster

1
@ user1172763 ใน Visual Studio หากคุณต้องการดูตัวแปรสมาชิกฉันพบว่าคุณสามารถเดินทางไปที่สแต็คการโทรจนกว่าคุณจะได้รับบริบทของแลมบ์ดา
Zev Spitz

6

การเข้าถึงตัวแปรท้องถิ่นของขอบเขตการปิดล้อม

คำตอบที่ยอมรับโดย Karl Bielefeldtนั้นถูกต้อง ฉันสามารถเพิ่มความแตกต่างได้อีกหนึ่ง:

  • ขอบเขต

รหัสแลมบ์ดาที่ซ้อนอยู่ภายในเมธอดในคลาสสามารถเข้าถึงตัวแปรที่มีประสิทธิภาพขั้นสุดท้ายที่พบภายในเมธอดและคลาสนั้น

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

หากต้องการอ้างถึงบทแนะนำ Java (เน้นที่เหมือง):

เช่นเดียวกับคลาสที่ไม่ระบุชื่อคลาสนิพจน์แลมบ์ดาสามารถจับตัวแปรได้ พวกเขามีการเข้าถึงตัวแปรโลคัลของขอบเขตการล้อมรอบเดียวกัน อย่างไรก็ตามแตกต่างจากคลาสท้องถิ่นและไม่ระบุชื่อนิพจน์แลมบ์ดาไม่มีปัญหาเรื่องเงา (ดู Shadowing สำหรับข้อมูลเพิ่มเติม) การแสดงออกของแลมบ์ดานั้นอยู่ในขอบเขตที่ จำกัด ซึ่งหมายความว่าพวกเขาจะไม่สืบทอดชื่อใด ๆ จาก supertype หรือแนะนำการกำหนดขอบเขตระดับใหม่ การประกาศในการแสดงออกแลมบ์ดาจะถูกตีความเช่นเดียวกับที่พวกเขาอยู่ในสภาพแวดล้อมที่ล้อมรอบ

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

ดู:


4

นี่อาจเป็นการหยิบยก แต่สำหรับจุดที่ยอดเยี่ยมอื่น ๆ ที่ทำในคำตอบอื่น ๆ ฉันจะเพิ่ม:

ชอบวิธีอ้างอิงเมื่อเป็นไปได้ เปรียบเทียบ:

employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);

กับ

employees.stream()
         .map(employee -> employee.getName())
         .forEach(employeeName -> System.out.println(employeeName));

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


0

จากคำตอบของ Matt McHenry อาจมีความสัมพันธ์ระหว่างแลมบ์ดาและการอ้างอิงเมธอดซึ่งแลมบ์ดาสามารถเขียนใหม่เป็นชุดการอ้างอิงวิธีการได้ ตัวอย่างเช่น:

employees.stream()
         .forEach(employeeName -> System.out.println(employee.getName()));

VS

employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.