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อินเทอร์เฟซ

ตัวอย่าง:

รหัส บันทึก
Timer timer = new Timer();
timer.run();

Runnable r1 = new Timer();
r1.run();

Runnable r2 = new Calendar();
r2.run();

เรียก เมธอด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)