1. อินเตอร์เฟส
เพื่อให้เข้าใจว่าฟังก์ชันแลมบ์ดาคืออะไร ก่อนอื่นคุณต้องเข้าใจว่าอินเทอร์เฟซคืออะไร ดังนั้นเรามานึกถึงประเด็นหลัก
อินเทอร์เฟซคือการเปลี่ยนแปลงของแนวคิดของคลาส สมมติว่าเป็นคลาสที่ถูกตัดทอนอย่างมาก อินเตอร์เฟสไม่สามารถมีตัวแปรของตัวเองได้ (ยกเว้นคลาสคงที่) ซึ่งแตกต่างจากคลาส คุณไม่สามารถสร้างวัตถุที่มีประเภทเป็นอินเทอร์เฟซ:
- คุณไม่สามารถประกาศตัวแปรของคลาสได้
- คุณไม่สามารถสร้างวัตถุ
ตัวอย่าง:
interface Runnable
{
void run();
}
การใช้อินเทอร์เฟซ
เหตุใดจึงจำเป็นต้องมีอินเทอร์เฟซ ส่วนต่อประสานใช้ร่วมกับการสืบทอดเท่านั้น อินเทอร์เฟซ เดียวกันสามารถรับช่วงต่อจากคลาสต่างๆ หรืออย่างที่กล่าวไป — คลาสใช้อินเทอร์เฟซ
ถ้าคลาสใช้อินเตอร์เฟส มันจะต้องอิมพลีเมนต์เมธอดที่ประกาศไว้แต่ไม่ได้ใช้งานโดยอินเตอร์เฟส ตัวอย่าง:
interface Runnable
{
void run();
}
class Timer implements Runnable
{
void run()
{
System.out.println(LocalTime.now());
}
}
class Calendar implements Runnable
{
void run()
{
var date = LocalDate.now();
System.out.println("Today: " + date.getDayOfWeek());
}
}
คลาสTimer
ใช้Runnable
อินเทอร์เฟซดังนั้นจึงต้องประกาศเมธอดทั้งหมดที่อยู่ในRunnable
อินเทอร์เฟซและนำไปใช้ภายในตัวเอง นั่นคือเขียนโค้ดในเนื้อหาเมธอด เช่นเดียวกับCalendar
ชั้นเรียน
แต่ตอนนี้Runnable
ตัวแปรสามารถจัดเก็บการอ้างอิงไปยังวัตถุที่ใช้Runnable
อินเทอร์เฟซ
ตัวอย่าง:
รหัส | บันทึก |
---|---|
|
เรียก เมธอด run() ในTimer คลาส เรียกเมธอด run() ในTimer คลาส เรียกเมธอด run() ในCalendar คลาส |
คุณสามารถกำหนดการอ้างอิงอ็อบเจกต์ให้กับตัวแปรประเภทใดก็ได้ ตราบใดที่ประเภทนั้นเป็นหนึ่งในคลาสบรรพบุรุษของออบเจ็กต์ สำหรับ คลาส Timer
and Calendar
มีสองประเภทดังกล่าว: Object
และRunnable
หากคุณกำหนดการอ้างอิงวัตถุให้กับObject
ตัวแปร คุณสามารถเรียกใช้เมธอดที่ประกาศในObject
คลาส เท่านั้น และถ้าคุณกำหนดการอ้างอิงวัตถุให้กับRunnable
ตัวแปร คุณสามารถเรียกใช้เมธอดของRunnable
ประเภทได้
ตัวอย่างที่ 2:
ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());
for (Runnable element: list)
element.run();
รหัสนี้จะใช้งานได้เนื่องจาก วัตถุ Timer
และCalendar
มีวิธีการทำงานที่ทำงานได้ดีอย่างสมบูรณ์ ดังนั้นการโทรหาพวกเขาจึงไม่ใช่ปัญหา หากเราเพิ่งเพิ่มเมธอด run() ให้กับทั้งสองคลาส เราจะไม่สามารถเรียกมันด้วยวิธีง่ายๆ เช่นนั้นได้
โดยพื้นฐานแล้วRunnable
อินเทอร์เฟซจะใช้เป็นสถานที่ในการเรียกใช้เมธอดเท่านั้น
2. การเรียงลำดับ
ไปสู่สิ่งที่เป็นประโยชน์มากขึ้น ตัวอย่างเช่น ลองดูที่การเรียงลำดับสตริง
ในการจัดเรียงชุดของสตริงตามตัวอักษร Java มีวิธีการที่ยอดเยี่ยมที่เรียกว่าCollections.sort(collection);
เมธอดแบบสแตติกนี้เรียงลำดับคอลเล็กชันที่ส่งผ่าน และในกระบวนการจัดเรียง จะทำการเปรียบเทียบองค์ประกอบแบบคู่เพื่อให้เข้าใจว่าควรสลับองค์ประกอบหรือไม่
ระหว่างการเรียงลำดับ การเปรียบเทียบเหล่านี้ดำเนินการโดยใช้compareTo
เมธอด () ซึ่งคลาสมาตรฐานทั้งหมดมี: Integer
, String
, ...
วิธีการเปรียบเทียบถึง() ของคลาสจำนวนเต็มจะเปรียบเทียบค่าของตัวเลขสองตัว ในขณะที่วิธีการเปรียบเทียบถึง() ของคลาสสตริงจะดูที่ลำดับตัวอักษรของสตริง
ดังนั้นชุดของตัวเลขจะเรียงลำดับจากน้อยไปมาก ในขณะที่ชุดของสตริงจะเรียงตามตัวอักษร
อัลกอริทึมการเรียงลำดับทางเลือก
แต่ถ้าเราต้องการจัดเรียงสตริงที่ไม่ใช่ตัวอักษร แต่เรียงตามความยาวล่ะ แล้วถ้าเราต้องการเรียงลำดับตัวเลขจากมากไปหาน้อยล่ะ? คุณจะทำอย่างไรในกรณีนี้?
ในการจัดการกับสถานการณ์ดังกล่าวCollections
คลาสมีsort()
เมธอดอื่นที่มีพารามิเตอร์สองตัว:
Collections.sort(collection, comparator);
โดยที่ตัวเปรียบเทียบคือวัตถุพิเศษที่รู้วิธีเปรียบเทียบวัตถุในคอลเลกชันระหว่างการดำเนินการเรียงลำดับ คำว่า comparator มาจากคำในภาษาอังกฤษว่าcomparatorซึ่งมาจากการเปรียบเทียบซึ่งหมายถึง "การเปรียบเทียบ"
แล้ววัตถุพิเศษนี้คืออะไร?
Comparator
อินเตอร์เฟซ
มันง่ายมาก ประเภทของsort()
พารามิเตอร์ตัวที่สองของเมธอดคือComparator<T>
โดยที่ T คือพารามิเตอร์ประเภทที่ระบุประเภทขององค์ประกอบในคอลเลกชันและComparator
เป็นอินเทอร์เฟซที่มีเมธอดเดียวint compare(T obj1, T obj2);
กล่าวอีกนัยหนึ่ง วัตถุเปรียบเทียบคือวัตถุใดๆ ของคลาสใดๆ ที่ใช้อินเทอร์เฟซตัวเปรียบเทียบ อินเทอร์เฟซตัวเปรียบเทียบดูเรียบง่ายมาก:
public interface Comparator<Type>
{
public int compare(Type obj1, Type obj2);
}
วิธีcompare()
การเปรียบเทียบสองข้อโต้แย้งที่ส่งผ่านไปยังมัน
หากเมธอดส่งคืนค่าลบ นั่นหมายความobj1 < obj2
ว่า หากเมธอดส่งคืนค่าจำนวนบวก แสดงobj1 > obj2
ว่า หากเมธอดคืนค่า 0 แสดงobj1 == obj2
ว่า
นี่คือตัวอย่างของวัตถุเปรียบเทียบที่เปรียบเทียบสตริงตามความยาว:
public class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
หากต้องการเปรียบเทียบความยาวของสตริง เพียงลบความยาวหนึ่งออกจากอีกอันหนึ่ง
รหัสที่สมบูรณ์สำหรับโปรแกรมที่เรียงลำดับสตริงตามความยาวจะมีลักษณะดังนี้:
public class Solution
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Hello", "how's", "life?");
Collections.sort(list, new StringLengthComparator());
}
}
class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
3. น้ำตาลวากยสัมพันธ์
คุณคิดว่าโค้ดนี้สามารถเขียนให้กระชับกว่านี้ได้ไหม? โดยทั่วไป มีเพียงบรรทัดเดียวที่มีข้อมูลที่เป็นประโยชน์obj1.length() - obj2.length();
—
แต่รหัสไม่สามารถอยู่นอกเมธอดได้ ดังนั้นเราต้องเพิ่มเมธอดcompare()
และเพื่อจัดเก็บเมธอด เราต้องเพิ่มคลาสใหม่StringLengthComparator
— และเราต้องระบุประเภทของตัวแปรด้วย... ทุกอย่างถูกต้อง
แต่มีวิธีทำให้รหัสนี้สั้นลง เรามีน้ำตาลสังเคราะห์สำหรับคุณ สองช้อน!
ชั้นในนิรนาม
คุณสามารถเขียนโค้ดตัวเปรียบเทียบได้ภายในmain()
เมธอด และคอมไพเลอร์จะจัดการส่วนที่เหลือเอง ตัวอย่าง:
public class Solution
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Hello", "how's", "life?");
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Collections.sort(list, comparator);
}
}
คุณสามารถสร้างวัตถุที่ใช้Comparator
อินเทอร์เฟซโดยไม่ต้องสร้างคลาสอย่างชัดเจน! คอมไพเลอร์จะสร้างโดยอัตโนมัติและตั้งชื่อชั่วคราว ลองเปรียบเทียบ:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Comparator<String> comparator = new StringLengthComparator();
class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
สีเดียวกันใช้เพื่อระบุบล็อกโค้ดที่เหมือนกันในสองกรณีที่ต่างกัน ความแตกต่างนั้นค่อนข้างเล็กในทางปฏิบัติ
เมื่อคอมไพเลอร์พบบล็อกแรกของโค้ด ก็จะสร้างโค้ดบล็อกที่สองที่สอดคล้องกันและให้ชื่อแบบสุ่มแก่คลาส
4. การแสดงออกของแลมบ์ดาในภาษาจาวา
สมมติว่าคุณตัดสินใจใช้คลาสภายในที่ไม่ระบุชื่อในโค้ดของคุณ ในกรณีนี้ คุณจะมีบล็อกโค้ดดังนี้:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
ที่นี่เรารวมการประกาศตัวแปรเข้ากับการสร้างคลาสนิรนาม แต่มีวิธีทำให้รหัสนี้สั้นลง ตัวอย่างเช่น:
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
จำเป็นต้องใช้เครื่องหมายอัฒภาคเนื่องจากที่นี่เราไม่ได้มีเพียงการประกาศคลาสโดยปริยายเท่านั้น แต่ยังรวมถึงการสร้างตัวแปรด้วย
สัญกรณ์เช่นนี้เรียกว่าแลมบ์ดานิพจน์
หากคอมไพลเลอร์พบสัญลักษณ์แบบนี้ในโค้ดของคุณ คอมไพลเลอร์จะสร้างโค้ดเวอร์ชันละเอียด (ด้วยคลาสภายในที่ไม่ระบุตัวตน)
โปรดทราบว่าเมื่อเขียนนิพจน์แลมบ์ดา เราไม่ได้เพียงละเว้นชื่อของคลาส แต่ยังรวมถึงชื่อของเมธอด ด้วยComparator<String>
int compare()
การคอมไพล์จะไม่มีปัญหาในการกำหนดเมธอดเนื่องจากนิพจน์แลมบ์ดาสามารถเขียนได้ สำหรับอินเท อร์เฟซที่มีเมธอดเดียวเท่านั้น ยังไงก็ตาม มีวิธีหลีกเลี่ยงกฎนี้ แต่คุณจะได้เรียนรู้เกี่ยวกับสิ่งนั้นเมื่อคุณเริ่มศึกษา OOP ในเชิงลึกมากขึ้น (เรากำลังพูดถึงวิธีการเริ่มต้น)
มาดูโค้ดเวอร์ชันละเอียดอีกครั้ง แต่เราจะทำให้ส่วนที่ละเว้นเป็นสีเทาเมื่อเขียนนิพจน์แลมบ์ดา:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
ดูเหมือนว่าจะไม่มีสิ่งสำคัญใดถูกมองข้ามไป หากComparator
อินเทอร์เฟซมีเพียงวิธีเดียวcompare()
คอมไพเลอร์สามารถกู้คืนโค้ดที่เป็นสีเทาจากโค้ดที่เหลือได้ทั้งหมด
การเรียงลำดับ
อย่างไรก็ตาม ตอนนี้เราสามารถเขียนรหัสการเรียงลำดับได้ดังนี้:
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);
หรือแม้แต่เช่นนี้:
Collections.sort(list, (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
}
);
comparator
เราก็แทนที่ ตัวแปรด้วยค่าที่กำหนดให้กับcomparator
ตัวแปรทันที
อนุมานประเภท
แต่นั่นไม่ใช่ทั้งหมด โค้ดในตัวอย่างเหล่านี้สามารถเขียนให้กระชับยิ่งขึ้น ประการแรก คอมไพลเลอร์สามารถกำหนดได้ด้วยตัวมันเองว่าตัวแปรobj1
และobj2
คือ Strings
และประการที่สอง วงเล็บปีกกาและคำสั่ง return ยังสามารถละเว้นได้หากคุณมีเพียงคำสั่งเดียวในโค้ดวิธีการ
เวอร์ชันย่อจะเป็นดังนี้:
Comparator<String> comparator = (obj1, obj2) ->
obj1.length() – obj2.length();
Collections.sort(list, comparator);
และถ้าแทนที่จะใช้comparator
ตัวแปร เราใช้ค่าของมันทันที เราจะได้เวอร์ชันต่อไปนี้:
Collections.sort(list, (obj1, obj2) -> obj1.length() — obj2.length() );
คุณคิดอย่างไรกับสิ่งนั้น? โค้ดเพียงบรรทัดเดียวที่ไม่มีข้อมูลที่ฟุ่มเฟือย — มีเพียงตัวแปรและโค้ดเท่านั้น ไม่มีทางทำให้มันสั้นลง! หรือมี?
5. วิธีการทำงาน
อันที่จริงแล้ว โค้ดสามารถเขียนให้กระชับกว่านี้ได้อีก แต่เพิ่มเติมในภายหลัง
คุณสามารถเขียนนิพจน์แลมบ์ดาโดยที่คุณจะใช้ประเภทอินเทอร์เฟซด้วยวิธีเดียว
ตัวอย่างเช่น ในโค้ดคุณสามารถเขียนนิพจน์แลมบ์ดาได้เนื่องจากลายเซ็นของเมธอดเป็นดังนี้:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());
sort()
sort(Collection<T> colls, Comparator<T> comp)
เมื่อเราส่งArrayList<String>
คอลเล็กชันเป็นอาร์กิวเมนต์แรกไปยังวิธีการเรียงลำดับ คอมไพเลอร์สามารถระบุได้ว่าประเภทของอาร์กิวเมนต์ที่สองคือ และจากนี้สรุปได้ว่าอินเทอร์เฟซนี้มีวิธีการ เดียว อย่างอื่นเป็นเทคนิคComparator<String>
int compare(String obj1, String obj2)
GO TO FULL VERSION