Wskaźniki funkcji w Javie

148

Może to być coś powszechnego i trywialnego, ale wydaje mi się, że mam problem ze znalezieniem konkretnej odpowiedzi. W C # istnieje koncepcja delegatów, która jest silnie związana z ideą wskaźników funkcji z C ++. Czy w Javie jest podobna funkcjonalność? Biorąc pod uwagę, że wskaźniki są nieco nieobecne, jaki jest najlepszy sposób na to? Żeby było jasne, mówimy tutaj o pierwszej klasie.

dreadwail
źródło
1
Jestem po prostu ciekawy, dlaczego chcesz, aby słuchacze lub inny konstrukt OOP wykonywałby to samo zadanie, zachowując swoją przedmiotową naturę. Chodzi mi o to, że rozumiem potrzebę funkcjonalności oferowanej przez koncepcję tylko po to, aby można ją było osiągnąć zwykłym przedmiotem .. chyba, że ​​czegoś mi brakuje, stąd zadaję to pytanie! :-)
Newtopian
2
Java 8 ma wyrażenia lambda: docs.oracle.com/javase/tutorial/java/javaOO/… Możesz to sprawdzić. Nie jest to wskaźnik funkcji, ale może być bardziej przydatny.
FrustratedWithFormsDesigner
6
Odwołania do metod Java 8 są dokładnie tym, o co prosisz.
Steven
8
Odwołania do metod Java 8 są dokładnie tym, o co prosisz. this::myMethodjest semantycznie taka sama jak tworzenie lambdy paramA, paramB -> this.myMethod(paramA, paramB).
Steven

Odpowiedzi:

126

Idiom Java dla funkcjonalności podobnej do wskaźnika funkcji to anonimowa klasa implementująca interfejs, np

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

Aktualizacja: powyższe jest konieczne w wersjach Java przed Java 8. Teraz mamy dużo ładniejsze alternatywy, a mianowicie lambdy:

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

i odniesienia do metod:

list.sort(MyClass::isGreaterThan);
Michael Borgwardt
źródło
2
Wzorzec projektowania strategii. Nie tak brzydki jak wskaźnik funkcji w C lub C ++, gdy chcesz, aby funkcja używała pewnych nieglobalnych danych, które nie są dostarczane jako argument funkcji.
Raedwald
75
@Raedwald C ++ ma funktory, a C ++ 0x ma domknięcia i lambdy zbudowane na podstawie funktorów. Ma nawet std::bind, co wiąże parametry z funkcjami i zwraca wywoływalny obiekt. Nie mogę bronić C na tej podstawie, ale C ++ jest naprawdę lepszy niż Java.
zneak
21
@zneak, @Raedwald: Możesz to zrobić w C, przekazując wskaźnik do danych użytkownika (np .: struct). (i może hermetyzować go z każdym nowym poziomem pośrednictwa wywołań zwrotnych) W rzeczywistości jest dość czysty.
brice
25
Tak, właściwie wezmę wskaźniki funkcji C na zajęcia crufty wrapper każdego dnia
BT
3
Java 8 ma lepszą składnię do tego.
Thorbjørn Ravn Andersen
65

Możesz zastąpić wskaźnik funkcji interfejsem. Powiedzmy, że chcesz przejrzeć kolekcję i zrobić coś z każdym elementem.

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

To jest interfejs, który moglibyśmy przekazać niektórym, powiedzmy, CollectionUtils2.doFunc (Collection c, IFunction f).

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

Jako przykład załóżmy, że mamy zbiór liczb i chciałbyś dodać 1 do każdego elementu.

CollectionUtils2.doFunc(List numbers, new IFunction() {
    public void execute(Object o) {
       Integer anInt = (Integer) o;
       anInt++;
    }
});
Björn Raupach
źródło
42

Możesz użyć do tego refleksji.

Jako parametr przekaż obiekt i nazwę metody (jako ciąg), a następnie wywołaj metodę. Na przykład:

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

A potem użyj go jak w:

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

Oczywiście sprawdź wszystkie wyjątki i dodaj potrzebne rzuty.

zoquete
źródło
7
Refleksja nie jest właściwą odpowiedzią na nic poza „Jak napisać powolny, podejrzany kod w języku Java”: D
Jacob
5
To może być "brzydkie" i powolne, ale uważam, że refleksja pozwala robić rzeczy, których rozwiązanie oparte na interfejsie w zaakceptowanej odpowiedzi nie może zrobić (chyba że się mylę), mianowicie wywoływać różne metody tego samego obiektu w tym samym kodzie część.
Euzebiusz
@zoquete .. Przeglądałem ten post i miałem wątpliwości .. więc w twoim podejściu, jeśli uzyskam dostęp do zmiennej "theDescription", funkcja będzie wywoływana za każdym razem, gdy zmienna będzie dostępna?
karthik27
20

Nie, funkcje nie są obiektami pierwszej klasy w Javie. Możesz zrobić to samo, implementując klasę handlera - w ten sposób zaimplementowane są wywołania zwrotne w Swingu itp.

Istnieją jednak propozycje zamknięć (oficjalna nazwa tego, o czym mówisz) w przyszłych wersjach java - Javaworld ma ciekawy artykuł.

jwoolard
źródło
15

To przywodzi na myśl Egzekucję Steve'a Yegge'a w Królestwie Rzeczowników . Zasadniczo stwierdza się, że Java potrzebuje obiektu dla każdej akcji i dlatego nie ma jednostek „tylko czasownikowych”, takich jak wskaźniki funkcji.

Yuval F.
źródło
8

Aby osiągnąć podobną funkcjonalność, możesz użyć anonimowych klas wewnętrznych.

Gdybyś miał zdefiniować interfejs Foo:

interface Foo {
    Object myFunc(Object arg);
}

Utwórz metodę, barktóra otrzyma `` wskaźnik funkcji '' jako argument:

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

Na koniec wywołaj metodę w następujący sposób:

bar(new Foo() {
    public Object myFunc(Object arg) {
        // Function code.
    }
}
Kingamajick
źródło
7

Java8 wprowadziła lambdy i odwołania do metod . Więc jeśli twoja funkcja pasuje do funkcjonalnego interfejsu (możesz stworzyć własny), możesz w tym przypadku użyć odwołania do metody.

Java udostępnia zestaw wspólnych interfejsów funkcjonalnych . mając na uwadze, że możesz wykonać następujące czynności:

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
   }
}
Alex
źródło
czy istnieje koncepcja alias: np. public List<T> collect(T t) = Collections::collect
javadba
@javadba, o ile nie wiem.
Alex
5

W Javie nie ma czegoś takiego. Będziesz musiał opakować swoją funkcję w jakiś obiekt i przekazać referencję do tego obiektu, aby przekazać referencję do metody na tym obiekcie.

Składniowo można to w pewnym stopniu złagodzić, stosując klasy anonimowe zdefiniowane lokalnie lub klasy anonimowe zdefiniowane jako zmienne składowe klasy.

Przykład:

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");
        }
    }
}
VoidPointer
źródło
3

Zaimplementowałem obsługę callback / delegate w Javie przy użyciu refleksji. Szczegóły i działające źródło są dostępne na mojej stronie internetowej .

Jak to działa

Mamy klasę główną o nazwie Callback z zagnieżdżoną klasą o nazwie WithParms. API, które potrzebuje wywołania zwrotnego, przyjmie obiekt Callback jako parametr i, jeśli to konieczne, utworzy Callback.WithParms jako zmienną metody. Ponieważ wiele zastosowań tego obiektu będzie rekurencyjnych, działa to bardzo czysto.

Ponieważ wydajność nadal ma dla mnie wysoki priorytet, nie chciałem być zmuszony do tworzenia jednorazowej tablicy obiektów do przechowywania parametrów dla każdego wywołania - w końcu w dużej strukturze danych mogą znajdować się tysiące elementów, a przetwarzanie wiadomości W scenariuszu możemy skończyć przetwarzaniem tysięcy struktur danych na sekundę.

Aby zapewnić ochronę wątków, tablica parametrów musi istnieć unikalnie dla każdego wywołania metody API, a dla wydajności powinna być używana taka sama dla każdego wywołania wywołania zwrotnego; Potrzebowałem drugiego obiektu, który byłby tani do utworzenia, aby powiązać wywołanie zwrotne z tablicą parametrów do wywołania. Ale w niektórych scenariuszach wywołujący miałby już tablicę parametrów z innych powodów. Z tych dwóch powodów tablica parametrów nie należała do obiektu Callback. Również wybór wywołania (przekazanie parametrów jako tablica lub jako pojedyncze obiekty) należy do API przy użyciu wywołania zwrotnego, umożliwiającego użycie dowolnego wywołania, które najlepiej pasuje do jego wewnętrznego działania.

Zagnieżdżona klasa WithParms jest zatem opcjonalna i służy dwóm celom: zawiera tablicę obiektów parametrów potrzebnych do wywołań zwrotnych oraz udostępnia 10 przeciążonych metod invoke () (z 1 do 10 parametrami), które ładują tablicę parametrów, a następnie wywołać cel wywołania zwrotnego.

Lawrence Dol
źródło
-1

W porównaniu z większością osób tutaj jestem nowy w Javie, ale ponieważ nie widziałem podobnej sugestii, mam inną alternatywę do zasugerowania. Nie jestem pewien, czy to dobra praktyka, czy nie, a nawet zasugerowałem wcześniej i po prostu tego nie rozumiem. Po prostu to lubię, ponieważ uważam, że jest samoopisowe.

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

następnie w zależności od aplikacji przypisz

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

itp.

i zadzwoń

 functionpointer.execute();
ozgeneral
źródło