ตัวชี้ฟังก์ชั่นใน Java


148

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


1
ฉันแค่อยากรู้ว่าทำไมคุณถึงต้องการสิ่งนี้ที่ผู้ฟังหรือ OOP สร้างอื่น ๆ จะทำงานเดียวกันในขณะที่รักษาธรรมชาติของวัตถุ ฉันหมายถึงฉันเข้าใจถึงความต้องการฟังก์ชั่นที่นำเสนอโดยแนวคิดเพียงว่ามันสามารถทำได้ด้วยวัตถุธรรมดา .. เว้นแต่แน่นอนฉันไม่มีอะไรบางอย่างดังนั้นฉันถามคำถามนี้! :-)
Newtopian

2
Java 8 มีการแสดงออกแลมบ์ดา: docs.oracle.com/javase/tutorial/java/javaOO/…คุณอาจต้องการตรวจสอบสิ่งนั้น ไม่ได้เป็นตัวชี้ฟังก์ชั่น แต่อาจจะยังใช้งานได้มากกว่า
FrustratedWithFormsDesigner

6
การอ้างอิงเมธอด Java 8 เป็นสิ่งที่คุณต้องการอย่างแท้จริง
Steven

8
การอ้างอิงเมธอด Java 8 เป็นสิ่งที่คุณต้องการอย่างแท้จริง เป็นความหมายเช่นเดียวกับการสร้างแลมบ์ดาthis::myMethod paramA, paramB -> this.myMethod(paramA, paramB)
Steven

คำตอบ:


126

Java idiom สำหรับฟังก์ชั่นคล้ายตัวชี้ฟังก์ชั่นเป็นคลาสที่ไม่ระบุชื่อซึ่งใช้อินเตอร์เฟสเช่น

Collections.sort(list, new Comparator<MyClass>(){
    public int compare(MyClass a, MyClass b)
    {
        // compare objects
    }
});

อัปเดต:ข้างต้นเป็นสิ่งจำเป็นในเวอร์ชัน Java ก่อนหน้า Java 8 ตอนนี้เรามีทางเลือกที่ดีกว่ามากคือ lambdas:

list.sort((a, b) -> a.isGreaterThan(b));

และวิธีการอ้างอิง:

list.sort(MyClass::isGreaterThan);

2
รูปแบบการออกแบบกลยุทธ์ ไม่น่าเกลียดเหมือนตัวชี้ฟังก์ชัน C หรือ C ++ เมื่อคุณต้องการให้ฟังก์ชันใช้ข้อมูลที่ไม่ใช่โกลบอลบางอย่างที่ไม่ได้จัดเตรียมไว้เป็นอาร์กิวเมนต์ของฟังก์ชัน
Raedwald

75
@Raedwald C ++ มี functors และ C ++ 0x มี closures และ lambdas ที่สร้างจากด้านบนของ functors มันยังมีstd::bindซึ่งผูกพารามิเตอร์กับฟังก์ชั่นและส่งกลับวัตถุ callable ฉันไม่สามารถปกป้อง C ในบริเวณเหล่านี้ แต่ c ++ จริงๆคือดีกว่า Java สำหรับเรื่องนี้
zneak

21
@zneak, @Raedwald: คุณสามารถทำได้ใน C โดยส่งต่อตัวชี้ไปยังข้อมูลผู้ใช้ (เช่น: struct) (และสามารถแค็ปซูลกับระดับการเรียกกลับทางอ้อมแต่ละครั้ง) มันค่อนข้างสะอาด
ไบรซ์

25
ใช่ฉันจะนำพอยน์เตอร์ฟังก์ชั่น C ไปใช้กับคลาส wrapper crufty ทุกวัน
BT

3
Java 8 มีไวยากรณ์ที่ดีกว่าสำหรับเรื่องนี้
Thorbjørn Ravn Andersen

65

คุณสามารถทดแทนตัวชี้ฟังก์ชันด้วยอินเทอร์เฟซ ให้บอกว่าคุณต้องการเรียกใช้คอลเลกชันและทำบางสิ่งบางอย่างกับแต่ละองค์ประกอบ

public interface IFunction {
  public void execute(Object o);
}

นี่คืออินเทอร์เฟซที่เราสามารถส่งผ่านไปยังบางคนพูดว่า CollectionUtils2.doFunc (คอลเล็กชัน c, IFunction f)

public static void doFunc(Collection c, IFunction f) {
   for (Object o : c) {
      f.execute(o);
   }
}

ตัวอย่างเช่นเรามีชุดของตัวเลขและคุณต้องการเพิ่ม 1 ในทุกองค์ประกอบ

CollectionUtils2.doFunc(List numbers, new IFunction() {
    public void execute(Object o) {
       Integer anInt = (Integer) o;
       anInt++;
    }
});

42

คุณสามารถใช้การสะท้อนเพื่อทำมัน

ส่งผ่านเป็นพารามิเตอร์วัตถุและชื่อวิธีการ (เป็นสตริง) แล้วเรียกใช้วิธีการ ตัวอย่างเช่น:

Object methodCaller(Object theObject, String methodName) {
   return theObject.getClass().getMethod(methodName).invoke(theObject);
   // Catch the exceptions
}

จากนั้นใช้เป็นใน:

String theDescription = methodCaller(object1, "toString");
Class theClass = methodCaller(object2, "getClass");

แน่นอนตรวจสอบข้อยกเว้นทั้งหมดและเพิ่ม casts ที่จำเป็น


7
Reflection ไม่ใช่คำตอบที่ถูกต้องสำหรับทุกสิ่งยกเว้น "ฉันจะเขียนโค้ดจาวาช้าได้อย่างไร": D
Jacob

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

@zoquete .. ฉันจะผ่านโพสต์นี้และมีข้อสงสัย .. ดังนั้นในวิธีการของคุณถ้าฉันเข้าถึงตัวแปร "theDescription" ฟังก์ชั่นจะถูกเรียกว่าทุกครั้งที่มีการเข้าถึงตัวแปรหรือไม่?
karthik27

20

ไม่ฟังก์ชั่นไม่ใช่วัตถุชั้นหนึ่งใน java คุณสามารถทำสิ่งเดียวกันได้โดยการใช้คลาสตัวจัดการ - นี่คือวิธีที่ callbacks ถูกนำไปใช้ใน Swing เป็นต้น

อย่างไรก็ตามมีข้อเสนอสำหรับการปิด (ชื่ออย่างเป็นทางการสำหรับสิ่งที่คุณกำลังพูดถึง) ในรุ่นอนาคตของจาวา - Javaworldมีบทความที่น่าสนใจ


15

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


8

เพื่อให้บรรลุการทำงานที่คล้ายกันคุณสามารถใช้คลาสภายในที่ไม่ระบุชื่อ

หากคุณต้องการกำหนดอินเทอร์เฟซFoo:

interface Foo {
    Object myFunc(Object arg);
}

สร้างวิธีการbarซึ่งจะได้รับ 'function pointer' เป็นอาร์กิวเมนต์:

public void bar(Foo foo) {
    // .....
    Object object = foo.myFunc(argValue);
    // .....
}

สุดท้ายเรียกเมธอดดังต่อไปนี้:

bar(new Foo() {
    public Object myFunc(Object arg) {
        // Function code.
    }
}

7

Java8 ได้แนะนำ lambdas และอ้างอิงวิธี ดังนั้นหากฟังก์ชั่นของคุณตรงกับส่วนต่อประสานการทำงาน (คุณสามารถสร้างของคุณเอง) คุณสามารถใช้วิธีการอ้างอิงในกรณีนี้

Java ให้ชุดของการเชื่อมต่อการทำงานร่วมกัน ในขณะที่คุณสามารถทำสิ่งต่อไปนี้:

public class Test {
   public void test1(Integer i) {}
   public void test2(Integer i) {}
   public void consumer(Consumer<Integer> a) {
     a.accept(10);
   }
   public void provideConsumer() {
     consumer(this::test1);   // method reference
     consumer(x -> test2(x)); // lambda
   }
}

มีแนวคิดของaliasเช่น: public List<T> collect(T t) = Collections::collect
javadba

@ javadba เท่าที่ฉันไม่รู้
Alex

5

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

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

ตัวอย่าง:

class MyComponent extends JPanel {
    private JButton button;
    public MyComponent() {
        button = new JButton("click me");
        button.addActionListener(buttonAction);
        add(button);
    }

    private ActionListener buttonAction = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            // handle the event...
            // note how the handler instance can access 
            // members of the surrounding class
            button.setText("you clicked me");
        }
    }
}

3

ฉันใช้งานการสนับสนุนการโทรกลับ / ผู้รับมอบสิทธิ์ใน Java โดยใช้การสะท้อนกลับ รายละเอียดและแหล่งที่มาทำงานอยู่ที่มีอยู่บนเว็บไซต์ของฉัน

มันทำงานอย่างไร

เรามีคลาสหลักการชื่อ Callback พร้อมคลาสซ้อนชื่อ WithParms API ที่ต้องการการเรียกกลับจะใช้วัตถุโทรกลับเป็นพารามิเตอร์และถ้าจำเป็นให้สร้าง Callback.WithParms เป็นตัวแปรวิธี เนื่องจากแอพพลิเคชั่นมากมายของวัตถุนี้จะถูกเรียกซ้ำการทำงานนี้อย่างหมดจด

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

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

คลาสที่ซ้อนกัน WithParms นั้นเป็นทางเลือกและให้บริการสองวัตถุประสงค์มันมีอาร์เรย์วัตถุพารามิเตอร์ที่จำเป็นสำหรับการเรียกกลับและมันมี 10 วิธีการเรียกใช้ที่โอเวอร์โหลด () จาก 10 ถึง 10 พารามิเตอร์ซึ่งโหลดอาร์เรย์พารามิเตอร์จากนั้น เรียกใช้เป้าหมายการโทรกลับ



-1

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

 /*Just to merge functions in a common name*/
 public class CustomFunction{ 
 public CustomFunction(){}
 }

 /*Actual functions*/
 public class Function1 extends CustomFunction{
 public Function1(){}
 public void execute(){...something here...}
 }

 public class Function2 extends CustomFunction{
 public Function2(){}
 public void execute(){...something here...}
 }

 .....
 /*in Main class*/
 CustomFunction functionpointer = null;

จากนั้นขึ้นอยู่กับแอปพลิเคชันกำหนด

 functionpointer = new Function1();
 functionpointer = new Function2();

เป็นต้น

และโทรโดย

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