ฉันจะเขียนฟังก์ชันนิรนามใน Java ได้อย่างไร?


89

เป็นไปได้หรือไม่?


6
โปรดทราบว่าขณะนี้สามารถทำได้ใน Java 8 - ดูคำตอบเกี่ยวกับ Lambda Expressions โดย Mark Rotteveel ด้านล่าง
Josiah Yoder

คำตอบ:


81

หากคุณหมายถึงฟังก์ชันที่ไม่ระบุชื่อและกำลังใช้ Java เวอร์ชันก่อน Java 8ดังนั้นในคำว่า no ( อ่านเกี่ยวกับนิพจน์แลมบ์ดาหากคุณใช้ Java 8+ )

อย่างไรก็ตามคุณสามารถใช้อินเทอร์เฟซที่มีฟังก์ชันดังนี้:

Comparator<String> c = new Comparator<String>() {
    int compare(String s, String s2) { ... }
};

และคุณสามารถใช้สิ่งนี้กับคลาสภายในเพื่อรับฟังก์ชั่นที่แทบไม่ระบุตัวตน :)


6
ยัง. ใน Java 7 จะเป็นไปได้: stackoverflow.com/questions/233579/closures-in-java-7
Ilya Boyandin

2
ในระหว่างที่รอ JDK7 เมธอดที่ไม่ระบุตัวตนสามารถจำลองได้ในบริบท OO โดยใช้en.wikipedia.org/wiki/Command_pattern
gpampara

1
ปิดไม่ได้ทำให้เป็น Java 7
Thorbjørn Ravn Andersen

5
ฉันคิดว่าคุณควรแก้ไขคำตอบของคุณเนื่องจากเรามีฟังก์ชันนิรนามกับ Java 8
Node.JS

45

นี่คือตัวอย่างของคลาสภายในที่ไม่ระบุชื่อ

System.out.println(new Object() {
    @Override public String toString() {
        return "Hello world!";
    }
}); // prints "Hello world!"

นี้ไม่ได้เป็นประโยชน์อย่างมากในขณะที่มันเป็น แต่มันแสดงให้เห็นถึงวิธีการสร้างตัวอย่างของระดับชั้นที่ไม่ระบุชื่อที่extends Objectและ@OverrideของtoString()วิธีการ

ดูสิ่งนี้ด้วย


คลาสภายในที่ไม่ระบุตัวตนมีประโยชน์มากเมื่อคุณต้องการใช้งานinterfaceซึ่งอาจไม่สามารถนำกลับมาใช้ใหม่ได้อย่างมาก (ดังนั้นจึงไม่คุ้มค่าที่จะปรับโครงสร้างเป็นคลาสที่มีชื่อของตัวเอง) ตัวอย่างคำแนะนำใช้แบบกำหนดเองjava.util.Comparator<T>สำหรับการเรียงลำดับ

นี่คือตัวอย่างของวิธีการที่คุณสามารถจัดเรียงหนึ่งขึ้นอยู่กับString[]String.length()

import java.util.*;
//...

String[] arr = { "xxx", "cd", "ab", "z" };
Arrays.sort(arr, new Comparator<String>() {
    @Override public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }           
});
System.out.println(Arrays.toString(arr));
// prints "[z, cd, ab, xxx]"

สังเกตเคล็ดลับการเปรียบเทียบโดยการลบที่ใช้ที่นี่ ควรกล่าวว่าเทคนิคนี้ใช้งานไม่ได้โดยทั่วไป: ใช้ได้เฉพาะเมื่อคุณสามารถรับประกันได้ว่าจะไม่ล้น (เช่นกรณีที่มีStringความยาว)

ดูสิ่งนี้ด้วย


5
เหตุการณ์อื่น ๆ ส่วนใหญ่สามารถพบได้ในEventListenerการใช้งาน (ย่อย) ในแอปพลิเคชัน Swing โดยเฉลี่ย
BalusC

@BalusC: เพิ่มลิงก์ไปยังคำถาม "ใช้อย่างไร"
polygenelubricants

@BalusC: stackoverflow เพิ่งเพิ่มLinkedแถบด้านข้างดังนั้นฉันจึงพยายามอย่างเต็มที่เพื่อใช้ประโยชน์จากมัน
polygenelubricants

12

ด้วยการแนะนำ lambda expression ใน Java 8 ตอนนี้คุณสามารถมีเมธอดที่ไม่ระบุตัวตนได้แล้ว

สมมติว่าฉันมีชั้นเรียนAlphaและต้องการกรองAlphaเงื่อนไขเฉพาะ ในการดำเนินการนี้คุณสามารถใช้ไฟล์Predicate<Alpha>. นี่คืออินเทอร์เฟซที่ใช้งานได้ซึ่งมีวิธีการtestที่ยอมรับAlphaและส่งคืนไฟล์boolean.

สมมติว่าวิธีการกรองมีลายเซ็นนี้:

List<Alpha> filter(Predicate<Alpha> filterPredicate)

ด้วยโซลูชันคลาสนิรนามแบบเก่าคุณจะต้องทำสิ่งต่อไปนี้:

filter(new Predicate<Alpha>() {
   boolean test(Alpha alpha) {
      return alpha.centauri > 1;
   }
});

ด้วย Java 8 lambdas คุณสามารถทำได้:

filter(alpha -> alpha.centauri > 1);

สำหรับข้อมูลเพิ่มเติมโปรดดูบทแนะนำ Lambda Expressions


2
การอ้างอิงวิธีการยังมีประโยชน์ เช่น sort (String :: CompareToIgnoreCase
Josiah Yoder

9

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

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

private static String pretty(Node node) {
    return "Node: " + new Object() {
        String print(Node cur) {
            return cur.isTerminal() ?
                cur.name() :
                ("("+print(cur.left())+":"+print(cur.right())+")");
        }
    }.print(node);
}

(ฉันเดิมเขียนนี้ใช้nodeมากกว่าcurในprintวิธี. บอกว่าไม่มีการจับ "โดยปริยายfinal" ชาวบ้าน? )


nodeควรประกาศไว้finalที่นี่
BalusC

@BalusC ดีจับ. curอันที่จริงความผิดพลาดของฉันไม่ได้กับการใช้งาน
Tom Hawtin - แท

@ ทอม: +1 เทคนิคดีๆ! มีการใช้งานจริงทุกที่หรือไม่? ชื่อของรูปแบบเฉพาะนี้หรือไม่?
polygenelubricants

@polygenelubricants ไม่เท่าที่ฉันรู้ ต้นทุนวัตถุพิเศษทั้งหมด! (และชั้นเรียน) เช่นเดียวกันกับสำนวนรั้งสองชั้น คนที่คิดถูกดูเหมือนจะไม่สนใจสำนวน Execute Around
Tom Hawtin - แท

@polygenelubricants จริงๆแล้วฉันดูเหมือนอัลกอริธึมแบบวนซ้ำ (ในตัว) ไม่มาก โดยเฉพาะอย่างยิ่งสิ่งที่ไม่สามารถเรียกใช้ซ้ำได้ (หรือทำได้อย่างง่ายดาย) และไม่สามารถใช้งานได้โดยการเรียกวิธีสาธารณะ (โปรดสังเกตว่าไม่เกี่ยวข้องเล็กน้อย"Node" +เพื่อสร้างวิธีที่สองที่จำเป็น) / ฉันไม่มีชื่อ. บางทีฉันอาจสร้างคำถาม "การสำรวจความคิดเห็น" (CW) เพื่อตั้งชื่อและให้คะแนนนั้นลดลง
Tom Hawtin - แท

1

ใช่ถ้าคุณใช้ java ล่าสุดซึ่งเป็นเวอร์ชัน 8 Java8 ทำให้สามารถกำหนดฟังก์ชันที่ไม่ระบุชื่อซึ่งเป็นไปไม่ได้ในเวอร์ชันก่อนหน้า

มายกตัวอย่างจากjava docsเพื่อทำความรู้จักว่าเราสามารถประกาศฟังก์ชัน anonymous class ได้อย่างไร

ตัวอย่างต่อไปนี้ HelloWorldAnonymousClasses ใช้คลาสที่ไม่ระบุชื่อในคำสั่งเริ่มต้นของตัวแปรโลคัล frenchGreeting และ spanishGreeting แต่ใช้คลาสโลคัลสำหรับการเริ่มต้นตัวแปร englishGreeting:

public class HelloWorldAnonymousClasses {

    interface HelloWorld {
        public void greet();
        public void greetSomeone(String someone);
    }

    public void sayHello() {

        class EnglishGreeting implements HelloWorld {
            String name = "world";
            public void greet() {
                greetSomeone("world");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hello " + name);
            }
        }

        HelloWorld englishGreeting = new EnglishGreeting();

        HelloWorld frenchGreeting = new HelloWorld() {
            String name = "tout le monde";
            public void greet() {
                greetSomeone("tout le monde");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Salut " + name);
            }
        };

        HelloWorld spanishGreeting = new HelloWorld() {
            String name = "mundo";
            public void greet() {
                greetSomeone("mundo");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hola, " + name);
            }
        };
        englishGreeting.greet();
        frenchGreeting.greetSomeone("Fred");
        spanishGreeting.greet();
    }

    public static void main(String... args) {
        HelloWorldAnonymousClasses myApp =
            new HelloWorldAnonymousClasses();
        myApp.sayHello();
    }            
}

ไวยากรณ์ของคลาสที่ไม่ระบุชื่อ

พิจารณาการสร้างอินสแตนซ์ของวัตถุ FrenchGreeting:

    HelloWorld frenchGreeting = new HelloWorld() {
        String name = "tout le monde";
        public void greet() {
            greetSomeone("tout le monde");
        }
        public void greetSomeone(String someone) {
            name = someone;
            System.out.println("Salut " + name);
        }
    };

นิพจน์คลาสที่ไม่ระบุชื่อประกอบด้วยสิ่งต่อไปนี้:

  • ตัวnewดำเนินการ
  • ชื่อของอินเทอร์เฟซที่จะใช้หรือคลาสที่จะขยาย ในตัวอย่างนี้คลาสที่ไม่ระบุชื่อกำลังใช้อินเตอร์เฟส HelloWorld

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

  • ร่างกายซึ่งเป็นเนื้อหาการประกาศคลาส โดยเฉพาะอย่างยิ่งในเนื้อความอนุญาตให้มีการประกาศวิธีการ แต่ไม่สามารถใช้คำสั่งได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.