Delegaci Java?


194

Czy język Java ma funkcje delegowania, podobnie jak C # obsługuje delegatów?


20
@Suma Jak to może być duplikat, jeśli pytanie, o którym wspomniałeś, zostało opublikowane rok później?
Ani

1
Java 8 ma funkcję podobną do delegatów. To się nazywa lambda.
tbodt

4
@tbodt, aby być bardziej poprawnym, Java 8 ma funkcję podobną do delegatów. To się nazywa interfejsy funkcjonalne . Lambdas to jeden ze sposobów tworzenia takich delegowanych instancji (anonimowo).
nawfal

3
Poniższe odpowiedzi pochodzą z wersji wcześniejszej niż Java 8. Po opublikowaniu w języku Java 8 odpowiedzi znajdują się w tym wątku: stackoverflow.com/questions/20311779/…
Michael Lloyd Lee mlk

@nawfal, +1, ale aby być jeszcze bardziej poprawnym, Java 7 i poniżej ma już funkcję podobną do delegatów. Nazywa się to zwykłymi interfejsami.
Pacerier,

Odpowiedzi:


152

Nie, naprawdę nie.

Możesz uzyskać ten sam efekt, używając odbicia, aby uzyskać obiekty Method, które następnie możesz wywołać, a innym sposobem jest utworzenie interfejsu z pojedynczą metodą „invoke” lub „execute”, a następnie utworzenie ich instancji w celu wywołania metody jesteś zainteresowany (tj. używając anonimowej klasy wewnętrznej).

Ten artykuł może być również interesujący / przydatny: programista Java patrzy na delegatów C # (@ archive.org)


3
Rozwiązanie z invoke () jako jedyną funkcją składową interfejsu jest naprawdę fajne.
Stephane Rolland

1
Ale zobacz mój przykład tutaj, co byś zrobił, nawet w Javie 7, aby osiągnąć odpowiednik delegata C #.
ToolmakerSteve

63

Zależnie od tego, co masz na myśli, możesz osiągnąć podobny efekt (omijając metodę) za pomocą Wzorca Strategii.

Zamiast linii takiej jak ta deklarująca nazwaną sygnaturę metody:

// C#
public delegate void SomeFunction();

zadeklaruj interfejs:

// Java
public interface ISomeBehaviour {
   void SomeFunction();
}

Dla konkretnych implementacji metody zdefiniuj klasę, która implementuje zachowanie:

// Java
public class TypeABehaviour implements ISomeBehaviour {
   public void SomeFunction() {
      // TypeA behaviour
   }
}

public class TypeBBehaviour implements ISomeBehaviour {
   public void SomeFunction() {
      // TypeB behaviour
   }
}

Następnie, gdziekolwiek miałbyś SomeFunctiondelegata w C #, ISomeBehaviourzamiast tego użyj odwołania:

// C#
SomeFunction doSomething = SomeMethod;
doSomething();
doSomething = SomeOtherMethod;
doSomething();

// Java
ISomeBehaviour someBehaviour = new TypeABehaviour();
someBehaviour.SomeFunction();
someBehaviour = new TypeBBehaviour();
someBehaviour.SomeFunction();

Dzięki anonimowym klasom wewnętrznym możesz nawet uniknąć deklarowania oddzielnych nazwanych klas i prawie traktować je jak prawdziwe funkcje delegowania.

// Java
public void SomeMethod(ISomeBehaviour pSomeBehaviour) {
   ...
}

...

SomeMethod(new ISomeBehaviour() { 
   @Override
   public void SomeFunction() {
      // your implementation
   }
});

Należy tego prawdopodobnie użyć tylko wtedy, gdy implementacja jest bardzo specyficzna dla obecnego kontekstu i nie przyniosłaby korzyści z ponownego użycia.

A potem oczywiście w Javie 8 stają się one w zasadzie wyrażeniami lambda:

// Java 8
SomeMethod(() -> { /* your implementation */ });

6
+1. jest to przyzwoite obejście, a gadatliwość może zostać zrekompensowana utrzymywalnością w przyszłości.
nawfal

to jest świetne ... Obecnie pracuję nad projektem, w którym nie jestem w stanie użyć refleksji z powodu ograniczeń projektu i to obejście sprawia, że ​​praca jest wykonywana pięknie :)
Jonathan Camarena

Czy Java ma konwencję nazewnictwa I-prefix dla interfejsów? Nie widziałem tego wcześniej
Kyle Delaney

36

Krótka historia: nie .

Wprowadzenie

Najnowsza wersja środowiska programistycznego Microsoft Visual J ++ obsługuje konstrukcję języka o nazwie delegaci lub powiązane odwołania do metod . Konstrukcja ta oraz nowe słowa kluczowe delegatei multicastwprowadzone w celu jej obsługi nie są częścią języka programowania Java TM , który jest określony w specyfikacji języka Java i zmieniony w specyfikacji klas wewnętrznych zawartych w dokumentacji oprogramowania JDKTM 1.1 .

Jest mało prawdopodobne, aby język programowania Java kiedykolwiek zawierał tę konstrukcję. Firma Sun już starannie rozważała przyjęcie go w 1996 r. W zakresie budowy i odrzucania działających prototypów. Doszliśmy do wniosku, że powiązane odniesienia do metod są niepotrzebne i szkodliwe dla języka. Ta decyzja została podjęta w porozumieniu z Borland International, który miał wcześniejsze doświadczenie z powiązanymi referencjami metod w Delphi Object Pascal.

Uważamy, że powiązane odniesienia do metod są niepotrzebne, ponieważ inna alternatywa projektowa, klasy wewnętrzne , zapewnia równą lub lepszą funkcjonalność. W szczególności klasy wewnętrzne w pełni obsługują wymagania dotyczące obsługi zdarzeń interfejsu użytkownika i zostały wykorzystane do implementacji interfejsu API interfejsu użytkownika co najmniej tak kompleksowego, jak klasy Windows Foundation.

Uważamy, że powiązane odniesienia do metod są szkodliwe, ponieważ szkodzą prostocie języka programowania Java i wszechobecnemu obiektowemu charakterowi interfejsów API. Odwołania do powiązanych metod wprowadzają również nieprawidłowości w składni języka i regułach określania zakresu. Wreszcie, osłabiają one inwestycje w technologie maszyn wirtualnych, ponieważ maszyny wirtualne muszą efektywnie obsługiwać dodatkowe i różnorodne typy referencji i metody łączenia.


Jak napisano w tym, co połączył Patrick , zamiast tego chcesz użyć klas wewnętrznych.
SCdF,

16
Świetny artykuł. UWIELBIAM ich definicję „prosty”: Java jest zaprojektowana tak, aby była prostym językiem, ponieważ w „Java musi być prosta do pisania kompilatora / maszyny wirtualnej dla”, ignorując „Java musi być łatwa do pisania / czytania przez osobę” . Wiele wyjaśnia.
Juozas Kontvainis

5
Myślę, że SUN popełnił wielki błąd. Po prostu nie przekonał ich funkcjonalny paradygmat i to wszystko.
Stephane Rolland

7
@Juozas: Python został wyraźnie zaprojektowany tak, aby był łatwy do pisania / czytania przez osobę i implementuje funkcje / delegaty lambda .
Stephane Rolland

5
Zamieniono na link archive.org. To naprawdę głupie, Oracle.
Patrick

18

Czytałeś to :

Delegaci są użyteczną konstrukcją w systemach opartych na zdarzeniach. Zasadniczo Delegaci to obiekty, które kodują wysłanie metody do określonego obiektu. Ten dokument pokazuje, w jaki sposób wewnętrzne klasy Java zapewniają bardziej ogólne rozwiązanie takich problemów.

Co to jest delegat? Naprawdę jest bardzo podobny do wskaźnika do funkcji składowej stosowanej w C ++. Ale delegat zawiera obiekt docelowy wraz z metodą, która ma zostać wywołana. Idealnie byłoby miło móc powiedzieć:

obj.registerHandler (ano.methodOne);

.. i że metoda methodOne będzie wywoływana ano, gdy zostanie odebrane jakieś określone zdarzenie.

To właśnie osiąga struktura Delegata.

Klasy wewnętrzne Java

Argumentowano, że Java zapewnia tę funkcjonalność poprzez anonimowe klasy wewnętrzne, a zatem nie potrzebuje dodatkowej konstrukcji Delegata.

obj.registerHandler(new Handler() {
        public void handleIt(Event ev) {
            methodOne(ev);
        }
      } );

Na pierwszy rzut oka wydaje się to poprawne, ale jednocześnie uciążliwe. Ponieważ w wielu przykładach przetwarzania zdarzeń prostota składni Delegatów jest bardzo atrakcyjna.

Generał Handler

Jeśli jednak programowanie oparte na zdarzeniach jest stosowane w bardziej wszechstronny sposób, powiedzmy na przykład jako część ogólnego asynchronicznego środowiska programistycznego, istnieje większe ryzyko.

W takiej ogólnej sytuacji nie wystarczy podać tylko metodę docelową i instancję obiektu docelowego. Zasadniczo mogą być wymagane inne parametry, które są określane w kontekście, gdy rejestrator obsługi zdarzeń jest zarejestrowany.

W tej bardziej ogólnej sytuacji podejście java może zapewnić bardzo eleganckie rozwiązanie, szczególnie w połączeniu z użyciem zmiennych końcowych:

void processState(final T1 p1, final T2 dispatch) { 
  final int a1 = someCalculation();

  m_obj.registerHandler(new Handler() {
    public void handleIt(Event ev) {
     dispatch.methodOne(a1, ev, p1);
    }
  } );
}

finał * finał * finał

Masz swoją uwagę?

Zauważ, że końcowe zmienne są dostępne z poziomu anonimowych definicji metod klas. Pamiętaj, aby dokładnie przestudiować ten kod, aby zrozumieć konsekwencje. Jest to potencjalnie bardzo potężna technika. Na przykład można go wykorzystać z dobrym skutkiem podczas rejestrowania programów obsługi w MiniDOM i w bardziej ogólnych sytuacjach.

Natomiast konstrukcja Delegata nie zapewnia rozwiązania tego bardziej ogólnego wymogu i jako taka powinna zostać odrzucona jako idiom, na którym można oprzeć projekty.



5

Nie, ale można je sfałszować przy użyciu serwerów proxy i refleksji:

  public static class TestClass {
      public String knockKnock() {
          return "who's there?";
      }
  }

  private final TestClass testInstance = new TestClass();

  @Test public void
  can_delegate_a_single_method_interface_to_an_instance() throws Exception {
      Delegator<TestClass, Callable<String>> knockKnockDelegator = Delegator.ofMethod("knockKnock")
                                                                   .of(TestClass.class)
                                                                   .to(Callable.class);
      Callable<String> callable = knockKnockDelegator.delegateTo(testInstance);
      assertThat(callable.call(), is("who's there?"));
  }

Zaletą tego idiomu jest to, że możesz sprawdzić, czy istnieje metoda delegowana do, i że ma ona wymagany podpis w punkcie, w którym tworzysz delegującego (chociaż nie w czasie kompilacji, niestety, chociaż wtyczka FindBugs może pomoc tutaj), a następnie bezpiecznie użyj go do delegowania do różnych instancji.

Zobacz kod karg na github, aby uzyskać więcej testów i implementacji .


2

Zaimplementowałem obsługę wywołania zwrotnego / delegowania w Javie za pomocą refleksji. Szczegóły i działające źródło są dostępne na mojej stronie internetowej .

Jak to działa

Istnieje klasa zasadnicza o nazwie Callback z zagnieżdżoną klasą o nazwie WithParms. Interfejs API, który wymaga wywołania zwrotnego, przyjmie obiekt Callback jako parametr i, jeśli to konieczne, utworzy Callback.WithParms jako zmienną metody. Ponieważ wiele aplikacji tego obiektu będzie rekurencyjnych, działa to bardzo czysto.

Ponieważ wydajność wciąż ma dla mnie wysoki priorytet, nie chciałem być zmuszony do tworzenia tablicy obiektów typu flip-out do przechowywania parametrów dla każdego wywołania - w końcu w dużej strukturze danych mogą być tysiące elementów, a podczas przetwarzania wiadomości scenariusz może zakończyć się przetwarzaniem tysięcy struktur danych na sekundę.

Aby być bezpiecznym dla wątków, tablica parametrów musi istnieć jednoznacznie dla każdego wywołania metody API, a dla wydajności należy używać tego samego dla każdego wywołania wywołania zwrotnego; Potrzebowałem drugiego obiektu, którego utworzenie byłoby tanie, aby połączyć wywołanie zwrotne z tablicą parametrów do wywołania. Jednak w niektórych scenariuszach wywoływacz miałby już tablicę parametrów z innych powodów. Z tych dwóch powodów tablica parametrów nie należy do obiektu wywołania zwrotnego. Również wybór wywołania (przekazanie parametrów jako tablicy lub jako pojedynczych obiektów) należy do interfejsu API za pomocą wywołania zwrotnego, umożliwiającego użycie dowolnego wywołania, które najlepiej odpowiada jego wewnętrznemu działaniu.

Klasa zagnieżdżona WithParms jest zatem opcjonalna i służy dwóm celom, zawiera tablicę obiektów parametrów potrzebną do wywołań zwrotnych i zapewnia 10 przeciążonych metod invoke () (od 1 do 10 parametrów), które ładują tablicę parametrów, a następnie wywołać cel wywołania zwrotnego.

Poniżej znajduje się przykład użycia wywołania zwrotnego do przetworzenia plików w drzewie katalogów. Jest to wstępny przebieg sprawdzania poprawności, który po prostu zlicza pliki do przetworzenia i zapewnia, że ​​żaden nie przekroczy określonego z góry maksymalnego rozmiaru. W tym przypadku po prostu tworzymy wywołanie zwrotne w połączeniu z wywołaniem interfejsu API. Odbieramy jednak metodę docelową jako wartość statyczną, aby odbicie nie było wykonywane za każdym razem.

static private final Method             COUNT =Callback.getMethod(Xxx.class,"callback_count",true,File.class,File.class);

...

IoUtil.processDirectory(root,new Callback(this,COUNT),selector);

...

private void callback_count(File dir, File fil) {
    if(fil!=null) {                                                                             // file is null for processing a directory
        fileTotal++;
        if(fil.length()>fileSizeLimit) {
            throw new Abort("Failed","File size exceeds maximum of "+TextUtil.formatNumber(fileSizeLimit)+" bytes: "+fil);
            }
        }
    progress("Counting",dir,fileTotal);
    }

IoUtil.processDirectory ():

/**
 * Process a directory using callbacks.  To interrupt, the callback must throw an (unchecked) exception.
 * Subdirectories are processed only if the selector is null or selects the directories, and are done
 * after the files in any given directory.  When the callback is invoked for a directory, the file
 * argument is null;
 * <p>
 * The callback signature is:
 * <pre>    void callback(File dir, File ent);</pre>
 * <p>
 * @return          The number of files processed.
 */
static public int processDirectory(File dir, Callback cbk, FileSelector sel) {
    return _processDirectory(dir,new Callback.WithParms(cbk,2),sel);
    }

static private int _processDirectory(File dir, Callback.WithParms cbk, FileSelector sel) {
    int                                 cnt=0;

    if(!dir.isDirectory()) {
        if(sel==null || sel.accept(dir)) { cbk.invoke(dir.getParent(),dir); cnt++; }
        }
    else {
        cbk.invoke(dir,(Object[])null);

        File[] lst=(sel==null ? dir.listFiles() : dir.listFiles(sel));
        if(lst!=null) {
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(!ent.isDirectory()) {
                    cbk.invoke(dir,ent);
                    lst[xa]=null;
                    cnt++;
                    }
                }
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(ent!=null) { cnt+=_processDirectory(ent,cbk,sel); }
                }
            }
        }
    return cnt;
    }

Ten przykład ilustruje piękno tego podejścia - logika specyficzna dla aplikacji jest abstrakcyjna w wywołaniu zwrotnym, a hałaśliwość rekurencyjnego chodzenia po drzewie katalogów jest ładnie schowana w całkowicie statycznej metodzie użyteczności wielokrotnego użytku. I nie musimy wielokrotnie płacić ceny za zdefiniowanie i wdrożenie interfejsu dla każdego nowego zastosowania. Oczywiście argument za interfejsem jest to, że jest ono o wiele bardziej wyraźne na temat tego, co należy wdrożyć (jest wymuszone, a nie tylko udokumentowane) - ale w praktyce nie stwierdziłem, że jest problem z prawidłową definicją wywołania zwrotnego.

Definiowanie i implementacja interfejsu nie jest wcale taka zła (chyba, że ​​dystrybuujesz aplety, tak jak ja, gdzie unikanie tworzenia dodatkowych klas naprawdę ma znaczenie), ale to naprawdę świeci, gdy masz wiele wywołań zwrotnych w jednej klasie. Nie tylko jest zmuszany do popchnięcia ich do osobnej klasy wewnętrznej dodanej narzutu we wdrożonej aplikacji, ale programowanie jest wręcz żmudne, a cały kod płyty kotłowej jest po prostu „hałasem”.


1

Tak i nie, ale można by pomyśleć o takim schemacie delegowania w Javie. Ten samouczek wideo dotyczy wymiany danych między fragmentami aktywności i ma wielką istotę delegowania sortowania wzorca za pomocą interfejsów.

Interfejs Java


1

Nie ma jawnego delegatesłowa kluczowego jako C #, ale można osiągnąć podobne w Javie 8, używając interfejsu funkcjonalnego (tj. Dowolnego interfejsu z dokładnie jedną metodą) i lambda:

private interface SingleFunc {
    void printMe();
}

public static void main(String[] args) {
    SingleFunc sf = () -> {
        System.out.println("Hello, I am a simple single func.");
    };
    SingleFunc sfComplex = () -> {
        System.out.println("Hello, I am a COMPLEX single func.");
    };
    delegate(sf);
    delegate(sfComplex);
}

private static void delegate(SingleFunc f) {
    f.printMe();
}

Każdy nowy obiekt typu SingleFuncmusi zostać zaimplementowany printMe(), więc można bezpiecznie przekazać go do innej metody (np. delegate(SingleFunc)) W celu wywołania printMe()metody.


0

Chociaż nie jest to tak czyste, ale można zaimplementować coś w rodzaju delegatów C # przy użyciu serwera proxy Java .


2
Chociaż ten link może odpowiedzieć na pytanie, lepiej dołączyć tutaj istotne części odpowiedzi i podać link w celach informacyjnych. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie.
StackFlowed

2
@StackFlowed Zdejmij link i co otrzymasz? Post z informacją. Głosuj, aby zachować.
Scimonster,

1
@ Scimonster, co by się stało, gdyby link już nie był ważny?
StackFlowed

@StackFlowed Nadal zapewnia odpowiedź - użyj serwera proxy Java.
Scimonster

to może być pozostawiony jako komentarz?
StackFlowed

0

Nie, ale wewnętrznie ma podobne zachowanie.

W języku C # delegaty są używane do tworzenia osobnego punktu wejścia i działają podobnie jak wskaźnik funkcji.

W Javie nie ma czegoś takiego jak wskaźnik funkcji (w wyglądzie górnym), ale wewnętrznie Java musi zrobić to samo, aby osiągnąć te cele.

Na przykład tworzenie wątków w Javie wymaga klasy rozszerzającej wątek lub implementującej Runnable, ponieważ zmiennej obiektu klasy można użyć wskaźnika lokalizacji pamięci.



0

Opisany kod oferuje wiele zalet delegatów C #. Metody, zarówno statyczne, jak i dynamiczne, można traktować w jednolity sposób. Złożoność wywoływania metod poprzez refleksję jest zmniejszona, a kod nadaje się do wielokrotnego użytku, co oznacza, że ​​nie wymaga żadnych dodatkowych klas w kodzie użytkownika. Uwaga: nazywamy alternatywną wygodną wersję invoke, w której można wywołać metodę z jednym parametrem bez tworzenia tablicy obiektów. Kod Java poniżej:

  class Class1 {
        public void show(String s) { System.out.println(s); }
    }

    class Class2 {
        public void display(String s) { System.out.println(s); }
    }

    // allows static method as well
    class Class3 {
        public static void staticDisplay(String s) { System.out.println(s); }
    }

    public class TestDelegate  {
        public static final Class[] OUTPUT_ARGS = { String.class };
        public final Delegator DO_SHOW = new Delegator(OUTPUT_ARGS,Void.TYPE);

        public void main(String[] args)  {
            Delegate[] items = new Delegate[3];

            items[0] = DO_SHOW .build(new Class1(),"show,);
            items[1] = DO_SHOW.build (new Class2(),"display");
            items[2] = DO_SHOW.build(Class3.class, "staticDisplay");

            for(int i = 0; i < items.length; i++) {
                items[i].invoke("Hello World");
            }
        }
    }

-11

Java nie ma delegatów i jest z tego dumna :). Z tego, co tu przeczytałem, znalazłem w istocie 2 sposoby na sfałszowanie delegatów: 1. odbicie; 2. klasa wewnętrzna

Refleksje są powolne! Klasa wewnętrzna nie obejmuje najprostszego przypadku użycia: funkcji sortowania. Nie chcę wchodzić w szczegóły, ale rozwiązaniem z klasą wewnętrzną jest w zasadzie utworzenie klasy opakowania dla tablicy liczb całkowitych do sortowania w kolejności rosnącej i klasy dla tablicy liczb całkowitych do sortowania w kolejności malejącej.


Co klasa wewnętrzna ma wspólnego z sortowaniem? A co ma związek z pytaniem?
nawfal
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.