Hàm con trỏ trong Java


148

Đây có thể là một cái gì đó phổ biến và tầm thường, nhưng tôi dường như gặp khó khăn khi tìm một câu trả lời cụ thể. Trong C # có một khái niệm về các đại biểu, liên quan mạnh mẽ đến ý tưởng về các con trỏ hàm từ C ++. Có một chức năng tương tự trong Java? Cho rằng con trỏ có phần vắng mặt, cách tốt nhất về điều này là gì? Và để rõ ràng, chúng ta đang nói về lớp học đầu tiên ở đây.


1
Tôi chỉ tò mò về lý do tại sao bạn muốn điều này trong đó người nghe hoặc cấu trúc OOP khác sẽ làm nhiệm vụ tương tự trong khi vẫn giữ được bản chất đối tượng của họ. Ý tôi là tôi hiểu sự cần thiết của chức năng được cung cấp bởi khái niệm chỉ có thể đạt được với đối tượng đơn giản .. trừ khi, tất nhiên tôi đang thiếu một cái gì đó, do đó tôi hỏi câu hỏi này! :-)
Newtopian

2
Java 8 có các biểu thức lambda: docs.oracle.com/javase/tutorial/java/javaOO/ khăn Bạn có thể muốn kiểm tra xem. Không hẳn là một con trỏ hàm, nhưng vẫn có thể được sử dụng nhiều hơn.
Thất vọngWithFormsDesigner

6
Tham chiếu phương thức Java 8 chính xác là những gì bạn đang yêu cầu.
Steven

8
Tham chiếu phương thức Java 8 chính xác là những gì bạn đang yêu cầu. this::myMethodvề mặt ngữ nghĩa giống như tạo ra lambda paramA, paramB -> this.myMethod(paramA, paramB).
Steven

Câu trả lời:


126

Thành ngữ Java cho chức năng giống như con trỏ hàm là một lớp ẩn danh thực hiện giao diện, ví dụ:

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

Cập nhật: những điều trên là cần thiết trong các phiên bản Java trước Java 8. Bây giờ chúng ta có các lựa chọn thay thế đẹp hơn nhiều, cụ thể là lambdas:

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

và tham chiếu phương thức:

list.sort(MyClass::isGreaterThan);

2
Mẫu thiết kế Chiến lược. Không xấu như con trỏ hàm C hoặc C ++ khi bạn muốn hàm sử dụng một số dữ liệu không toàn cầu không được cung cấp làm đối số cho hàm.
Raedwald

75
@Raedwald C ++ có functor và C ++ 0x có các bao đóng và lambdas được xây dựng trên đỉnh functor. Nó thậm chí còn có std::bind, liên kết các tham số với các hàm và trả về một đối tượng có thể gọi được. Tôi không thể bảo vệ C bằng các căn cứ, nhưng C ++ thực sự tốt hơn so với Java cho việc này.
zneak

21
@zneak, @Raedwald: Bạn có thể thực hiện điều này bằng C bằng cách chuyển dọc một con trỏ tới dữ liệu người dùng (ví dụ: struct). (và có thể gói gọn nó với mỗi cấp độ gọi lại mới) Nó thực sự khá sạch sẽ.
brice

25
Vâng, tôi thực sự sẽ đưa con trỏ hàm C vào các lớp bao bọc thô sơ bất cứ ngày nào
BT

3
Java 8 có một cú pháp tốt hơn cho việc này.
Thorbjørn Ravn Andersen

65

Bạn có thể thay thế một con trỏ hàm bằng một giao diện. Hãy nói rằng bạn muốn chạy qua một bộ sưu tập và làm một cái gì đó với từng yếu tố.

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

Đây là giao diện mà chúng ta có thể chuyển cho một số người nói CollectionUtils2.doFunc (Bộ sưu tập c, Hàm số f).

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

Ví dụ, chúng tôi có một bộ sưu tập các số và bạn muốn thêm 1 cho mọi phần tử.

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

42

Bạn có thể sử dụng sự phản chiếu để làm điều đó.

Truyền tham số làm đối tượng và tên phương thức (dưới dạng chuỗi) và sau đó gọi phương thức. Ví dụ:

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

Và sau đó sử dụng nó như trong:

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

Tất nhiên, kiểm tra tất cả các ngoại lệ và thêm các diễn viên cần thiết.


7
Sự phản ánh không phải là câu trả lời đúng cho bất cứ điều gì ngoại trừ "Làm thế nào để tôi viết mã java tinh ranh chậm": D
Jacob

5
Nó có thể "xấu" và chậm, nhưng tôi tin rằng sự phản chiếu cho phép thực hiện những thứ mà giải pháp dựa trên giao diện trong câu trả lời được chấp nhận không thể làm được (trừ khi tôi nhầm), cụ thể là gọi các phương thức khác nhau của cùng một đối tượng trong cùng một mã phần.
Eusebius

@zoquete .. Tôi đã xem qua bài đăng này và có một nghi ngờ .. vì vậy trong cách tiếp cận của bạn, nếu tôi truy cập vào biến "theDes mô tả", hàm sẽ được gọi mỗi và mỗi khi biến được truy cập ??
karthik27

20

Không, các hàm không phải là đối tượng hạng nhất trong java. Bạn có thể làm điều tương tự bằng cách triển khai một lớp xử lý - đây là cách gọi lại được thực hiện trong Swing, v.v.

Tuy nhiên, có những đề xuất về việc đóng cửa (tên chính thức cho những gì bạn đang nói đến) trong các phiên bản tương lai của java - Javaworld có một bài viết thú vị.


15

Điều này gợi đến sự thực thi của Steve Yegge trong Vương quốc danh từ . Về cơ bản, nó nói rằng Java cần một đối tượng cho mọi hành động và do đó không có các thực thể "chỉ động từ" như các con trỏ hàm.


8

Để đạt được chức năng tương tự, bạn có thể sử dụng các lớp bên trong ẩn danh.

Nếu bạn định định một giao diện Foo:

interface Foo {
    Object myFunc(Object arg);
}

Tạo một phương thức barsẽ nhận được một 'con trỏ hàm' làm đối số:

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

Cuối cùng gọi phương thức như sau:

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

7

Java8 đã giới thiệu lambdas và tham chiếu phương thức . Vì vậy, nếu chức năng của bạn phù hợp với giao diện chức năng (bạn có thể tự tạo), bạn có thể sử dụng tham chiếu phương thức trong trường hợp này.

Java cung cấp một tập hợp các giao diện chức năng phổ biến . trong khi bạn có thể làm như sau:

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
   }
}

là có khái niệm về một alias: ví dụ public List<T> collect(T t) = Collections::collect
javadba

@javadba theo như tôi biết thì không.
Alex

5

Không có điều đó trong Java. Bạn sẽ cần phải bọc hàm của bạn vào một đối tượng nào đó và chuyển tham chiếu đến đối tượng đó để truyền tham chiếu đến phương thức trên đối tượng đó.

Về mặt cú pháp, điều này có thể được giảm bớt đến một mức độ nhất định bằng cách sử dụng các lớp ẩn danh được định nghĩa tại chỗ hoặc các lớp ẩn danh được định nghĩa là các biến thành viên của lớp.

Thí dụ:

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

Tôi đã triển khai hỗ trợ gọi lại / ủy nhiệm trong Java bằng cách sử dụng sự phản chiếu. Thông tin chi tiết và nguồn làm việc có sẵn trên trang web của tôi .

Làm thế nào nó hoạt động

Chúng tôi có một lớp nguyên tắc có tên Callback với một lớp lồng nhau có tên WithParms. API cần gọi lại sẽ lấy đối tượng Gọi lại làm tham số và, nếu cần, hãy tạo Callback.WithParms làm biến phương thức. Vì rất nhiều ứng dụng của đối tượng này sẽ được đệ quy, nên nó hoạt động rất sạch sẽ.

Với hiệu năng vẫn là ưu tiên cao đối với tôi, tôi không muốn được yêu cầu tạo một mảng đối tượng vứt đi để giữ các tham số cho mỗi lần gọi - sau tất cả trong một cấu trúc dữ liệu lớn có thể có hàng ngàn phần tử và trong quá trình xử lý tin nhắn kịch bản cuối cùng chúng ta có thể xử lý hàng ngàn cấu trúc dữ liệu.

Để đảm bảo an toàn cho chủ đề, mảng tham số cần tồn tại duy nhất cho mỗi lần gọi phương thức API và để sử dụng hiệu quả, cùng một cách sử dụng cho mỗi lần gọi lại; Tôi cần một đối tượng thứ hai sẽ rẻ để tạo ra để liên kết cuộc gọi lại với một mảng tham số để gọi. Nhưng, trong một số tình huống, invoker sẽ có một mảng tham số vì những lý do khác. Vì hai lý do này, mảng tham số không thuộc về đối tượng Gọi lại. Ngoài ra, sự lựa chọn của việc gọi (truyền các tham số dưới dạng một mảng hoặc dưới dạng các đối tượng riêng lẻ) thuộc về API bằng cách sử dụng hàm gọi lại cho phép nó sử dụng bất kỳ lời gọi nào phù hợp nhất với hoạt động bên trong của nó.

Lớp WithParms lồng nhau, sau đó, là tùy chọn và phục vụ hai mục đích, nó chứa mảng đối tượng tham số cần thiết cho các lệnh gọi lại và nó cung cấp 10 phương thức invoke () bị quá tải (với từ 1 đến 10 tham số) để tải mảng tham số và sau đó tải mảng tham số gọi mục tiêu gọi lại.



-1

Liên quan đến hầu hết mọi người ở đây, tôi chưa quen với java nhưng vì tôi chưa thấy một đề nghị tương tự nên tôi có một cách khác để đề xuất. Tôi không chắc liệu đó có phải là một thực hành tốt hay không, hoặc thậm chí được đề xuất trước đó và tôi chỉ không nhận được nó. Tôi chỉ thích nó vì tôi nghĩ nó tự mô tả.

 /*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;

Sau đó tùy thuộc vào ứng dụng, chỉ định

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

Vân vân.

và gọi bằng

 functionpointer.execute();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.