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คลาส |
คุณสามารถกำหนดการอ้างอิงอ็อบเจกต์ให้กับตัวแปรประเภทใดก็ได้ ตราบใดที่ประเภทนั้นเป็นหนึ่งในคลาสบรรพบุรุษของออบเจ็กต์ สำหรับ คลาส Timerand 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();
}
}
StringLengthComparatorชั้นเรียน
หากต้องการเปรียบเทียบความยาวของสตริง เพียงลบความยาวหนึ่งออกจากอีกอันหนึ่ง
รหัสที่สมบูรณ์สำหรับโปรแกรมที่เรียงลำดับสตริงตามความยาวจะมีลักษณะดังนี้:
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();
}
}
StringLengthComparatorระดับ
สีเดียวกันใช้เพื่อระบุบล็อกโค้ดที่เหมือนกันในสองกรณีที่ต่างกัน ความแตกต่างนั้นค่อนข้างเล็กในทางปฏิบัติ
เมื่อคอมไพเลอร์พบบล็อกแรกของโค้ด ก็จะสร้างโค้ดบล็อกที่สองที่สอดคล้องกันและให้ชื่อแบบสุ่มแก่คลาส
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