Java8 Lambdas เทียบกับคลาส Anonymous


111

เนื่องจาก Java8 ได้รับการเผยแพร่เมื่อไม่นานมานี้และนิพจน์แลมบ์ดาใหม่เอี่ยมนั้นดูดีมากฉันสงสัยว่านี่หมายถึงการตายของคลาส Anonymous ที่เราคุ้นเคยหรือไม่

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

Collections.sort(personList, new Comparator<Person>(){
  public int compare(Person p1, Person p2){
    return p1.firstName.compareTo(p2.firstName);
  }
});

ตอนนี้สามารถทำได้โดยใช้ Lambdas:

Collections.sort(personList, (Person p1, Person p2) -> p1.firstName.compareTo(p2.firstName));

และดูกระชับอย่างน่าประหลาด. คำถามของฉันคือมีเหตุผลอะไรที่จะใช้คลาสเหล่านั้นใน Java8 แทน Lambdas ต่อไปหรือไม่?

แก้ไข

คำถามเดียวกัน แต่ในทางตรงกันข้ามประโยชน์ของการใช้ Lambdas แทนคลาส Anonymous คืออะไรเนื่องจาก Lambdas สามารถใช้ได้กับอินเทอร์เฟซวิธีการเดียวเท่านั้นคุณลักษณะใหม่นี้เป็นเพียงทางลัดที่ใช้ในบางกรณีเท่านั้นหรือมีประโยชน์จริงหรือไม่


5
แน่นอนสำหรับคลาสที่ไม่ระบุตัวตนทั้งหมดที่มีวิธีการที่มีผลข้างเคียง
tobias_k

11
เพียงแค่ให้ข้อมูลของคุณคุณยังสามารถสร้างเปรียบเทียบเป็น: Comparator.comparing(Person::getFirstName)ถ้าจะเป็นวิธีการที่จะกลับมาgetFirstName() firstName
skiwi

1
หรือคลาสที่ไม่ระบุชื่อด้วยหลายวิธีหรือ ...
Mark Rotteveel

1
ฉันกำลังล่อลวงไปลงคะแนนเสียงให้ใกล้ที่สุดเท่าที่กว้างเกินไปโดยเฉพาะอย่างยิ่งเนื่องจากคำถามเพิ่มเติมหลังจากแก้ไข
Mark Rotteveel

1
บทความเชิงลึกที่น่าสนใจในหัวข้อนี้: infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

คำตอบ:


108

คลาสภายในที่ไม่ระบุชื่อ (AIC) สามารถใช้เพื่อสร้างคลาสย่อยของคลาสนามธรรมหรือคลาสคอนกรีต AIC ยังสามารถนำเสนออินเทอร์เฟซที่เป็นรูปธรรมรวมถึงการเพิ่มสถานะ (ฟิลด์) ตัวอย่างของ AIC สามารถอ้างถึงได้โดยใช้thisในเนื้อความของวิธีการดังนั้นจึงสามารถเรียกใช้วิธีการเพิ่มเติมได้สถานะของมันสามารถกลายพันธุ์ได้เมื่อเวลาผ่านไป ฯลฯ สิ่งเหล่านี้ไม่มีผลกับ lambdas

ฉันเดาว่าการใช้งานส่วนใหญ่ของ AIC คือเพื่อให้มีการใช้งานฟังก์ชันเดียวแบบไร้สัญชาติและสามารถแทนที่ด้วยนิพจน์แลมบ์ดาได้ แต่มีการใช้ AIC อื่น ๆ ที่ไม่สามารถใช้ lambdas ได้ AIC อยู่ที่นี่

อัปเดต

ความแตกต่างอีกอย่างระหว่างนิพจน์ AIC และแลมบ์ดาคือ AIC แนะนำขอบเขตใหม่ นั่นคือชื่อได้รับการแก้ไขจากซูเปอร์คลาสและอินเทอร์เฟซของ AIC และสามารถเงาชื่อที่เกิดขึ้นในสภาพแวดล้อมการปิดคำศัพท์ สำหรับ lambdas ชื่อทั้งหมดได้รับการแก้ไขเป็นศัพท์


1
Lambdas สามารถมีสถานะ ในเรื่องนี้ฉันไม่เห็นความแตกต่างระหว่าง Lambdas และ AIC
nosid

1
@nosid AICs เช่นเดียวกับอินสแตนซ์ของคลาสใด ๆ สามารถเก็บสถานะในฟิลด์และสถานะนี้สามารถเข้าถึงได้ (และอาจเปลี่ยนแปลงได้โดย) วิธีการใด ๆ ของคลาส สถานะนี้มีอยู่จนกว่าวัตถุจะเป็น GC กล่าวคือมีขอบเขตไม่ จำกัด ดังนั้นจึงสามารถคงอยู่ได้ตลอดการเรียกใช้เมธอด สถานะเดียวที่มี lambdas ขอบเขตไม่ จำกัด ถูกจับในเวลาที่พบแลมบ์ดา สถานะนี้ไม่เปลี่ยนรูป ตัวแปรโลคัลภายในแลมบ์ดานั้นไม่แน่นอน แต่จะมีอยู่ในขณะที่กำลังดำเนินการเรียกแลมบ์ดาเท่านั้น
Stuart Marks

1
@nosid Ah แฮ็คอาร์เรย์องค์ประกอบเดียว อย่าพยายามใช้ตัวนับของคุณจากหลายเธรด หากคุณกำลังจะจัดสรรบางสิ่งบนฮีปและจับมันในแลมบ์ดาคุณอาจใช้ AIC และเพิ่มฟิลด์ที่คุณสามารถกลายพันธุ์ได้โดยตรง การใช้แลมบ์ดาวิธีนี้สามารถใช้งานได้ แต่ทำไมต้องกังวลเมื่อคุณสามารถใช้วัตถุจริง?
Stuart Marks

2
AIC จะสร้างไฟล์แบบนี้A$1.classแต่ Lambda จะไม่ทำ ฉันสามารถเพิ่มสิ่งนี้ในความแตกต่างได้หรือไม่?
Asif Mushtaq

2
@UnKnown ส่วนใหญ่เป็นข้อกังวลในการดำเนินการ; มันไม่มีผลกับโปรแกรมหนึ่งที่มี AICs vs lambdas ซึ่งเป็นคำถามส่วนใหญ่เกี่ยวกับ LambdaClass$$Lambda$1/1078694789โปรดทราบว่าการแสดงออกแลมบ์ดาไม่สร้างชั้นเรียนที่มีชื่อเหมือน อย่างไรก็ตามคลาสนี้สร้างขึ้นทันทีโดยแลมบ์ดา metafactory ไม่ใช่โดยjavacดังนั้นจึงไม่มี.classไฟล์ที่เกี่ยวข้อง อย่างไรก็ตามนี่เป็นข้อกังวลในการนำไปใช้อีกครั้ง
Stuart Marks

60

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

ดังนั้นเราไม่สามารถเพิกเฉยต่อคลาสที่ไม่ระบุตัวตนได้ และเพียงแค่ FYI sort()วิธีการของคุณสามารถง่ายขึ้นโดยข้ามการประกาศประเภทสำหรับp1และp2:

Collections.sort(personList, (p1, p2) -> p1.firstName.compareTo(p2.firstName));

คุณยังสามารถใช้การอ้างอิงวิธีการได้ที่นี่ คุณเพิ่มcompareByFirstName()วิธีการในPersonชั้นเรียนและใช้:

Collections.sort(personList, Person::compareByFirstName);

หรือเพิ่ม getter สำหรับfirstNameรับวิธีComparatorจากโดยตรงComparator.comparing():

Collections.sort(personList, Comparator.comparing(Person::getFirstName));

4
ฉันรู้ แต่ฉันชอบแบบยาวในแง่ของความสามารถในการอ่านเพราะมิฉะนั้นอาจทำให้สับสนได้ว่าตัวแปรเหล่านี้มาจากไหน
Amin Abu-Taleb

@ AminAbu-Taleb จะงงไปทำไม. นั่นคือไวยากรณ์ Lambda ที่ถูกต้อง ประเภทคืออะไรก็ตามที่อนุมาน อย่างไรก็ตามมันเป็นทางเลือกส่วนตัว คุณสามารถระบุประเภทที่ชัดเจนได้ ไม่มีปัญหา
Rohit Jain

1
เรียนไม่ประสงค์ออกนามสามารถข้อเขียนโดยตรงกับใหม่: มีอีกความแตกต่างที่ลึกซึ้งระหว่าง lambdas และการเรียนที่ไม่ระบุชื่อเป็นJava 8 ประเภทคำอธิบายประกอบnew @MyTypeAnnotation SomeInterface(){};เป็นเช่น นี่เป็นไปไม่ได้สำหรับนิพจน์แลมบ์ดา ดูรายละเอียดคำถามของฉันที่นี่: Annotating อินเตอร์เฟซการทำงานของนิพจน์แลมบ์ดา
Balder

36

ประสิทธิภาพ Lambda พร้อมคลาส Anonymous

เมื่อเปิดแอปพลิเคชันแต่ละไฟล์คลาสจะต้องโหลดและตรวจสอบ

คลาสที่ไม่ระบุชื่อถูกประมวลผลโดยคอมไพเลอร์เป็นประเภทย่อยใหม่สำหรับคลาสหรืออินเทอร์เฟซที่กำหนดดังนั้นจะมีการสร้างไฟล์คลาสใหม่สำหรับแต่ละคลาส

Lambdas แตกต่างกันที่การสร้าง bytecode ซึ่งมีประสิทธิภาพมากกว่าใช้คำสั่งที่เรียกใช้แบบไดนามิกที่มาพร้อมกับ JDK7

สำหรับ Lambdas คำสั่งนี้ใช้เพื่อหน่วงเวลาการแปลนิพจน์ lambda ใน bytecode จนถึงรันไทม์ (คำสั่งจะถูกเรียกเป็นครั้งแรกเท่านั้น)

ดังนั้นนิพจน์ Lambda จะกลายเป็นวิธีการแบบคงที่ (มีความแตกต่างเล็กน้อยกับ stateles และ statefull case พวกเขาจะได้รับการแก้ไขผ่านข้อโต้แย้งของเมธอดที่สร้างขึ้น)


แลมบ์ดาแต่ละตัวต้องการคลาสใหม่เช่นกัน แต่มันถูกสร้างขึ้นที่รันไทม์ดังนั้นในแง่นี้แลมบ์ดาจึงไม่มีประสิทธิภาพมากกว่าคลาสที่ไม่ระบุชื่อ lambdas จะถูกสร้างขึ้นผ่านทางinvokedynamicซึ่งโดยทั่วไปช้ากว่าที่invokespecialใช้ในการสร้างอินสแตนซ์ใหม่ของการเรียนที่ไม่ระบุชื่อ ดังนั้นในแง่นี้ lambdas ก็ช้าลงเช่นกัน (อย่างไรก็ตาม JVM สามารถเพิ่มประสิทธิภาพการinvokedynamicโทรได้เกือบตลอดเวลา)
ZhekaKozlov

3
@AndreiTomashpolskiy 1. กรุณาสุภาพ 2. อ่านความคิดเห็นนี้โดยวิศวกรคอมไพเลอร์: habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov

@ZhekaKozlov คุณไม่จำเป็นต้องเป็นวิศวกรคอมไพเลอร์เพื่ออ่านซอร์สโค้ด JRE และใช้ javap / debugger สิ่งที่คุณขาดหายไปคือการสร้างคลาส Wrapper สำหรับวิธีแลมบ์ดานั้นทำในหน่วยความจำทั้งหมดและไม่มีค่าใช้จ่ายใด ๆ ในขณะที่การสร้างอินสแตนซ์ AIC เกี่ยวข้องกับการแก้ไขและโหลดทรัพยากรคลาสที่เกี่ยวข้อง (ซึ่งหมายถึงการเรียกระบบ I / O) ดังนั้นinvokedynamicด้วยการสร้างคลาสเฉพาะกิจจึงเห็นได้ชัดอย่างรวดเร็วเมื่อเทียบกับคลาสที่ไม่ระบุตัวตนที่คอมไพล์
อังเดร Tomashpolskiy

@AndreiTomashpolskiy I / O ไม่จำเป็นต้องช้า
เสมอไป

14

มีความแตกต่างดังต่อไปนี้:

1) ไวยากรณ์

นิพจน์ Lambda ดูเรียบร้อยเมื่อเทียบกับ Anonymous Inner Class (AIC)

public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("in run");
        }
    };

    Thread t = new Thread(r);
    t.start(); 
}

//syntax of lambda expression 
public static void main(String[] args) {
    Runnable r = ()->{System.out.println("in run");};
    Thread t = new Thread(r);
    t.start();
}

2) ขอบเขต

คลาสภายในที่ไม่ระบุชื่อคือคลาสซึ่งหมายความว่ามีขอบเขตสำหรับตัวแปรที่กำหนดไว้ภายในคลาสภายใน

ในขณะที่การแสดงออกของแลมบ์ดาไม่ใช่ขอบเขตของตัวมันเอง แต่เป็นส่วนหนึ่งของขอบเขตการปิดล้อม

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

//AIC
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int cnt = 5;    
                System.out.println("in run" + cnt);
            }
        };

        Thread t = new Thread(r);
        t.start();
    }

//Lambda
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = ()->{
            int cnt = 5; //compilation error
            System.out.println("in run"+cnt);};
        Thread t = new Thread(r);
        t.start();
    }

3) ประสิทธิภาพ

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

** ฉันตระหนักดีว่าประเด็นนี้ไม่เป็นความจริงทั้งหมด โปรดดูคำถามต่อไปนี้สำหรับรายละเอียด Lambda เทียบกับประสิทธิภาพคลาสภายในที่ไม่ระบุชื่อ: ลดภาระใน ClassLoader?


3

Lambda ใน java 8 ได้รับการแนะนำสำหรับการเขียนโปรแกรมเชิงฟังก์ชัน คุณสามารถหลีกเลี่ยงรหัสสำเร็จรูปได้ที่ไหน ฉันเจอบทความที่น่าสนใจเกี่ยวกับแลมด้า

http://radar.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

ขอแนะนำให้ใช้ฟังก์ชันแลมด้าสำหรับลอจิกแบบง่าย หากใช้ตรรกะที่ซับซ้อนโดยใช้ lambdas จะเป็นค่าใช้จ่ายในการดีบักโค้ดในกรณีที่มีปัญหา


0

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


-1
  • ไวยากรณ์แลมบ์ดาไม่จำเป็นต้องเขียนโค้ดที่ชัดเจนซึ่ง java สามารถอนุมานได้
  • โดยการใช้invoke dynamic, แลมบ์ดาไม่ได้แปลงกลับไปเรียนที่ไม่ระบุชื่อในระหว่างการรวบรวมเวลา (Java ไม่ต้องไปผ่านวัตถุที่สร้างเพียงดูแลเกี่ยวกับลายเซ็นของวิธีการที่สามารถผูกกับวิธีการโดยไม่ต้องสร้างวัตถุ
  • แลมด้าให้ความสำคัญกับสิ่งที่เราต้องการทำแทนสิ่งที่เราต้องทำก่อนที่จะทำได้
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.