Java-Delegierte?


194

Verfügt die Java-Sprache über Delegatenfunktionen, ähnlich wie C # Delegaten unterstützt?


20
@Suma Wie kann dies ein Duplikat sein, wenn die von Ihnen erwähnte Frage ein Jahr nach dieser gestellt wurde?
Ani

1
Java 8 hat eine ähnliche Funktion wie Delegierte. Es heißt Lambdas.
tbodt

4
@tbodt um genauer zu sein, Java 8 hat eine Funktion, die der von Delegierten sehr ähnlich ist. Es heißt funktionale Schnittstellen . Lambdas sind eine Möglichkeit, solche Delegateninstanzen (anonym) zu erstellen.
Nawfal

3
Die folgenden Antworten stammen aus der Zeit vor Java 8. Nach Java 8 finden Sie die Antworten in diesem Thread: stackoverflow.com/questions/20311779/…
Michael Lloyd Lee mlk

@nawfal, +1, aber um noch genauer zu sein, Java 7 und niedriger hat bereits eine Funktion, die der von Delegierten sehr ähnlich ist. Es heißt einfache Schnittstellen.
Pacerier

Antworten:


152

Nicht wirklich, nein.

Möglicherweise können Sie denselben Effekt erzielen, indem Sie mithilfe von Reflection Methodenobjekte abrufen, die Sie dann aufrufen können. Die andere Möglichkeit besteht darin, eine Schnittstelle mit einer einzelnen Methode zum Aufrufen oder Ausführen zu erstellen und diese dann zum Aufrufen der Methode zu instanziieren Ihr Interesse an (dh mit einer anonymen inneren Klasse).

Vielleicht finden Sie diesen Artikel auch interessant / nützlich: Ein Java-Programmierer betrachtet C # -Delegierte (@ archive.org)


3
Die Lösung mit invoke () als einziger Mitgliedsfunktion einer Schnittstelle ist wirklich nett.
Stephane Rolland

1
Aber siehe mein Beispiel hier von dem, was man tun würde, auch in Java 7, das entspricht einen C # Delegaten zu erreichen.
ToolmakerSteve

63

Je nachdem, was Sie genau meinen, können Sie mithilfe des Strategiemusters einen ähnlichen Effekt erzielen (eine Methode weitergeben).

Anstelle einer Zeile wie dieser, die eine benannte Methodensignatur deklariert:

// C#
public delegate void SomeFunction();

Deklarieren Sie eine Schnittstelle:

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

Definieren Sie für konkrete Implementierungen der Methode eine Klasse, die das Verhalten implementiert:

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

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

SomeFunctionVerwenden Sie ISomeBehaviourstattdessen überall dort, wo Sie einen Delegaten in C # gehabt hätten , eine Referenz:

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

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

Mit anonymen inneren Klassen können Sie sogar vermeiden, separate benannte Klassen zu deklarieren, und sie fast wie echte Delegatenfunktionen behandeln.

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

...

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

Dies sollte wahrscheinlich nur verwendet werden, wenn die Implementierung sehr spezifisch für den aktuellen Kontext ist und nicht von einer Wiederverwendung profitieren würde.

Und dann werden diese in Java 8 natürlich zu Lambda-Ausdrücken:

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

6
+1. Dies ist eine anständige Problemumgehung, und die Ausführlichkeit kann in Zukunft durch Wartbarkeit ausgeglichen werden.
Nawfal

Das ist großartig ... Derzeit arbeite ich an einem Projekt, bei dem ich aufgrund von Projektbeschränkungen keine Reflexion verwenden kann und diese Problemumgehung die Arbeit wunderbar erledigt :)
Jonathan Camarena

Hat Java eine I-Präfix-Namenskonvention für Schnittstellen? Ich habe das noch nie gesehen
Kyle Delaney

36

Kurzgeschichte: nein .

Einführung

Die neueste Version der Microsoft Visual J ++ - Entwicklungsumgebung unterstützt ein Sprachkonstrukt, das als Delegaten oder gebundene Methodenreferenzen bezeichnet wird . Dieses Konstrukt, und die neuen Keywords delegateund multicastführten sie zu unterstützen, sind nicht Teil der Java TM Programmiersprache, die von der angegeben ist Java Language Specification und geändert durch die inneren Klassen Spezifikation , die in der Dokumentation für die JDKTM 1.1 Software .

Es ist unwahrscheinlich, dass die Java-Programmiersprache dieses Konstrukt jemals enthält. Sun hat bereits 1996 sorgfältig überlegt, es zu übernehmen, um funktionierende Prototypen zu bauen und zu verwerfen. Unsere Schlussfolgerung war, dass gebundene Methodenreferenzen unnötig und für die Sprache schädlich sind. Diese Entscheidung wurde in Absprache mit Borland International getroffen, das bereits Erfahrungen mit gebundenen Methodenreferenzen in Delphi Object Pascal gesammelt hatte.

Wir glauben , gebundene Methode Referenzen sind nicht notwendig , da eine andere Design - Alternative, innere Klassen , gleiche oder bessere Funktionalität bereitstellt. Insbesondere unterstützen innere Klassen die Anforderungen der Ereignisbehandlung auf der Benutzeroberfläche vollständig und wurden verwendet, um eine Benutzeroberflächen-API zu implementieren, die mindestens so umfassend ist wie die Windows Foundation-Klassen.

Wir glauben, dass gebundene Methodenreferenzen schädlich sind, da sie die Einfachheit der Java-Programmiersprache und den allgegenwärtigen objektorientierten Charakter der APIs beeinträchtigen. Gebundene Methodenreferenzen führen auch zu Unregelmäßigkeiten in der Sprachsyntax und den Gültigkeitsbereichsregeln. Schließlich verwässern sie die Investition in VM-Technologien, da VMs zusätzliche und unterschiedliche Arten von Referenzen und Methodenverknüpfungen effizient verarbeiten müssen.


Wie in Patrick verlinkt , möchten Sie stattdessen innere Klassen verwenden.
SCdF

16
Großartiger Artikel. Ich LIEBE ihre Definition von "einfach": Java ist eine einfache Sprache wie in "Java muss einfach zu schreiben sein Compiler / VM für", während ignoriert wird "Java muss einfach zu schreiben / von einer Person zu lesen sein". . Erklärt viel.
Juozas Kontvainis

5
Ich denke nur, dass SUN einen großen Fehler gemacht hat. Das funktionale Paradigma hat sie einfach nicht überzeugt, und das ist alles.
Stephane Rolland

7
@Juozas: Python ist explizit so gestaltet, dass es von einer Person einfach geschrieben / gelesen werden kann, und es implementiert Lambda-Funktionen / Delegaten .
Stephane Rolland

5
Ersetzt durch einen archive.org-Link. Das ist auch wirklich dumm, Oracle.
Patrick

18

Haben Sie lesen diese :

Delegaten sind ein nützliches Konstrukt in ereignisbasierten Systemen. Im Wesentlichen sind Delegaten Objekte, die einen Methodenversand für ein bestimmtes Objekt codieren. Dieses Dokument zeigt, wie Java-Innenklassen eine allgemeinere Lösung für solche Probleme bieten.

Was ist ein Delegierter? Wirklich ist es einem Zeiger auf die Mitgliedsfunktion, wie sie in C ++ verwendet wird, sehr ähnlich. Ein Delegat enthält jedoch das Zielobjekt zusammen mit der aufzurufenden Methode. Im Idealfall wäre es schön sagen zu können:

obj.registerHandler (ano.methodOne);

..und dass die Methode methodOne ano aufgerufen wird, wenn ein bestimmtes Ereignis empfangen wird.

Dies erreicht die Delegiertenstruktur.

Java Inner Classes

Es wurde argumentiert, dass Java diese Funktionalität über anonyme innere Klassen bereitstellt und daher das zusätzliche Delegate-Konstrukt nicht benötigt.

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

Auf den ersten Blick scheint dies richtig, aber gleichzeitig ein Ärgernis zu sein. Da für viele Beispiele für die Ereignisverarbeitung die Einfachheit der Delegates-Syntax sehr attraktiv ist.

General Handler

Wenn die ereignisbasierte Programmierung jedoch umfassender eingesetzt wird, beispielsweise als Teil einer allgemeinen asynchronen Programmierumgebung, steht mehr auf dem Spiel.

In einer solchen allgemeinen Situation reicht es nicht aus, nur die Zielmethode und die Zielobjektinstanz einzuschließen. Im Allgemeinen sind möglicherweise andere Parameter erforderlich, die im Kontext festgelegt werden, wenn der Ereignishandler registriert wird.

In dieser allgemeineren Situation kann der Java-Ansatz eine sehr elegante Lösung bieten, insbesondere wenn er mit der Verwendung endgültiger Variablen kombiniert wird:

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

final * final * final

Hast du deine Aufmerksamkeit?

Beachten Sie, dass auf die endgültigen Variablen innerhalb der anonymen Klassenmethodendefinitionen zugegriffen werden kann. Lesen Sie diesen Code sorgfältig durch, um die Auswirkungen zu verstehen. Dies ist möglicherweise eine sehr leistungsfähige Technik. Beispielsweise kann es effektiv bei der Registrierung von Handlern in MiniDOM und in allgemeineren Situationen verwendet werden.

Im Gegensatz dazu bietet das Delegate-Konstrukt keine Lösung für diese allgemeinere Anforderung und sollte als solche als Redewendung abgelehnt werden, auf der Entwürfe basieren können.


13

Ich weiß, dass dieser Beitrag alt ist, aber Java 8 hat Lambdas hinzugefügt und das Konzept einer funktionalen Schnittstelle, bei der es sich um eine beliebige Schnittstelle mit nur einer Methode handelt. Zusammen bieten diese C # -Delegierten ähnliche Funktionen. Weitere Informationen finden Sie hier oder googeln Sie einfach Java Lambdas. http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html


5

Nein, aber sie können mit Proxies und Reflexionen gefälscht werden:

  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?"));
  }

Das Schöne an dieser Redewendung ist, dass Sie an dem Punkt, an dem Sie den Delegator erstellen, überprüfen können, ob die delegierte Methode vorhanden ist und über die erforderliche Signatur verfügt (obwohl dies leider nicht zur Kompilierungszeit geschieht, obwohl ein FindBugs-Plug-In dies möglicherweise tut Hilfe hier), dann verwenden Sie es sicher, um an verschiedene Instanzen zu delegieren.

Weitere Tests und Implementierungen finden Sie im Karg-Code auf Github .


2

Ich habe Callback / Delegate-Unterstützung in Java mithilfe von Reflection implementiert. Details und Arbeitsquelle finden Sie auf meiner Website .

Wie es funktioniert

Es gibt eine Hauptklasse namens Callback mit einer verschachtelten Klasse namens WithParms. Die API, die den Rückruf benötigt, verwendet ein Callback-Objekt als Parameter und erstellt bei Bedarf einen Callback.WithParms als Methodenvariable. Da sehr viele Anwendungen dieses Objekts rekursiv sind, funktioniert dies sehr sauber.

Da die Leistung für mich immer noch eine hohe Priorität hat, wollte ich nicht verpflichtet sein, ein Wegwerfobjektarray zu erstellen, das die Parameter für jeden Aufruf enthält - schließlich kann es in einer großen Datenstruktur Tausende von Elementen geben und in einer Nachrichtenverarbeitung In diesem Szenario könnten wir am Ende Tausende von Datenstrukturen pro Sekunde verarbeiten.

Um threadsicher zu sein, muss das Parameterarray für jeden Aufruf der API-Methode eindeutig vorhanden sein, und aus Effizienzgründen sollte für jeden Aufruf des Rückrufs dasselbe verwendet werden. Ich brauchte ein zweites Objekt, dessen Erstellung billig wäre, um den Rückruf mit einem Parameterarray zum Aufrufen zu binden. In einigen Szenarien verfügt der Aufrufer jedoch aus anderen Gründen bereits über ein Parameterarray. Aus diesen beiden Gründen gehört das Parameterarray nicht zum Callback-Objekt. Auch die Wahl des Aufrufs (Übergabe der Parameter als Array oder als einzelne Objekte) liegt in den Händen der API, die den Rückruf verwendet, um den Aufruf zu verwenden, der für das Innenleben am besten geeignet ist.

Die verschachtelte WithParms-Klasse ist also optional und dient zwei Zwecken: Sie enthält das für die Rückrufaufrufe erforderliche Parameterobjektarray und bietet 10 überladene invoke () -Methoden (mit 1 bis 10 Parametern), die das Parameterarray laden und dann Rufen Sie das Rückrufziel auf.

Es folgt ein Beispiel für die Verwendung eines Rückrufs zur Verarbeitung der Dateien in einem Verzeichnisbaum. Dies ist ein erster Validierungsdurchlauf, bei dem nur die zu verarbeitenden Dateien gezählt werden und sichergestellt wird, dass keine eine vorgegebene maximale Größe überschreitet. In diesem Fall erstellen wir den Rückruf nur inline mit dem API-Aufruf. Wir reflektieren die Zielmethode jedoch als statischen Wert, sodass die Reflexion nicht jedes Mal erfolgt.

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

Dieses Beispiel zeigt die Schönheit dieses Ansatzes: Die anwendungsspezifische Logik wird in den Rückruf abstrahiert, und die Plackerei, einen Verzeichnisbaum rekursiv zu durchlaufen, ist in einer vollständig wiederverwendbaren statischen Dienstprogrammmethode versteckt. Und wir müssen nicht wiederholt den Preis für die Definition und Implementierung einer Schnittstelle für jede neue Verwendung bezahlen. Natürlich das Argument für eine Schnittstelle ist dass es viel expliziter ist, was implementiert werden soll (erzwungen, nicht einfach dokumentiert) - aber in der Praxis habe ich es nicht als Problem empfunden, die Rückrufdefinition richtig zu machen.

Das Definieren und Implementieren einer Schnittstelle ist nicht wirklich so schlecht (es sei denn, Sie verteilen wie ich Applets, bei denen das Vermeiden des Erstellens zusätzlicher Klassen tatsächlich wichtig ist), aber dies ist wirklich der Fall, wenn Sie mehrere Rückrufe in einer einzelnen Klasse haben. Es ist nicht nur gezwungen, sie jeweils in eine separate innere Klasse zu verschieben, was den Overhead in der bereitgestellten Anwendung erhöht, sondern es ist geradezu mühsam zu programmieren, und der gesamte Code auf der Kesselplatte ist wirklich nur "Rauschen".


1

Ja und Nein, aber das Delegatenmuster in Java könnte so gedacht werden. In diesem Video-Tutorial geht es um den Datenaustausch zwischen Aktivitätsfragmenten. Das Delegat-Sorta-Muster wird über Schnittstellen delegiert.

Java-Schnittstelle


1

Es hat kein explizites delegateSchlüsselwort als C #, aber Sie können in Java 8 ähnliche Ergebnisse erzielen, indem Sie eine funktionale Schnittstelle (dh eine beliebige Schnittstelle mit genau einer Methode) und Lambda verwenden:

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();
}

Jedes neue Objekt vom Typ SingleFuncmuss implementiert werden printMe(), sodass es sicher an eine andere Methode (z. B. delegate(SingleFunc)) übergeben werden kann, um die printMe()Methode aufzurufen .


0

Es ist zwar bei weitem nicht so sauber, aber Sie könnten so etwas wie C # -Delegierte mit einem Java- Proxy implementieren .


2
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert.
StackFlowed

2
@StackFlowed Entfernen Sie den Link, und was bekommen Sie? Ein Beitrag mit Informationen. Stimmen Sie ab, um zu behalten.
Scimonster

1
@ Scimonster was würde passieren, wenn der Link nicht mehr gültig ist?
StackFlowed

@StackFlowed Es gibt immer noch eine Antwort - verwenden Sie einen Java-Proxy.
Scimonster

dann könnte es als Kommentar hinterlassen werden?
StackFlowed

0

Nein, aber es hat intern ein ähnliches Verhalten.

In C # werden Delegaten verwendet, um einen separaten Einstiegspunkt zu erstellen, und sie funktionieren ähnlich wie ein Funktionszeiger.

In Java gibt es keinen Funktionszeiger (im oberen Bereich), aber intern muss Java dasselbe tun, um diese Ziele zu erreichen.

Zum Erstellen von Threads in Java ist beispielsweise eine Klasse erforderlich, die Thread erweitert oder Runnable implementiert, da eine Klassenobjektvariable als Speicherortzeiger verwendet werden kann.



0

Der beschriebene Code bietet viele der Vorteile von C # -Delegierten. Statische oder dynamische Methoden können einheitlich behandelt werden. Die Komplexität beim Aufrufen von Methoden durch Reflektion wird reduziert und der Code ist wiederverwendbar, da keine zusätzlichen Klassen im Benutzercode erforderlich sind. Beachten Sie, dass wir eine alternative Convenience-Version von invoke aufrufen, bei der eine Methode mit einem Parameter aufgerufen werden kann, ohne ein Objektarray zu erstellen. Java-Code unten:

  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 hat keine Delegierten und ist stolz darauf :). Nach dem, was ich hier gelesen habe, habe ich im Wesentlichen zwei Möglichkeiten gefunden, Delegierte zu fälschen: 1. Reflexion; 2. innere Klasse

Reflexionen sind langsam! Die innere Klasse behandelt nicht den einfachsten Anwendungsfall: die Sortierfunktion. Sie möchten nicht auf Details eingehen, aber die Lösung mit der inneren Klasse besteht im Wesentlichen darin, eine Wrapper-Klasse für ein Array von Ganzzahlen zu erstellen, die in aufsteigender Reihenfolge sortiert werden sollen, und eine Klasse für ein Array von Ganzzahlen, die in absteigender Reihenfolge sortiert werden sollen.


Was hat die innere Klasse mit Sortieren zu tun? Und was hat Sortieren mit der Frage zu tun?
Nawfal
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.