Delegati Java?


194

Il linguaggio Java ha funzionalità di delegato, simile a come C # ha il supporto per i delegati?


20
@Suma Come può essere un duplicato se la domanda che hai menzionato è stata pubblicata un anno dopo questa?
Ani,

1
Java 8 ha una funzione abbastanza simile ai delegati. Si chiama lambdas.
martedì

4
@tbodt per essere più corretti, Java 8 ha una funzione abbastanza simile ai delegati. Si chiama interfacce funzionali . Le lambda sono un modo per creare tali istanze delegate (in forma anonima).
nawfal,

3
Le risposte che seguono provengono da Pre-Java 8. Pubblica Java 8 vedi le risposte in questa discussione: stackoverflow.com/questions/20311779/…
Michael Lloyd Lee mlk

@nawfal, +1, ma per essere ancora più corretti, Java 7 e versioni precedenti hanno già una funzione abbastanza simile ai delegati. Si chiama interfacce semplici.
Pacerier,

Risposte:


152

Non proprio no.

Potresti essere in grado di ottenere lo stesso effetto usando reflection per ottenere oggetti Method che puoi quindi invocare, e l'altro modo è creare un'interfaccia con un singolo metodo 'invoke' o 'execute', e quindi istanziarli per chiamare il metodo sei interessato a (cioè utilizzando una classe interna anonima).

Potresti anche trovare questo articolo interessante / utile: un programmatore Java guarda i delegati C # (@ archive.org)


3
La soluzione con invoke () come unica funzione membro di un'interfaccia è davvero bella.
Stephane Rolland,

1
Ma vedi qui il mio esempio di cosa si farebbe, anche in Java 7, per ottenere l'equivalente di un delegato C #.
ToolmakerSteve

63

A seconda di cosa intendi esattamente, puoi ottenere un effetto simile (passando per un metodo) usando il modello di strategia.

Invece di una riga come questa che dichiara una firma del metodo denominata:

// C#
public delegate void SomeFunction();

dichiarare un'interfaccia:

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

Per implementazioni concrete del metodo, definire una classe che implementa il comportamento:

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

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

Quindi, ovunque avresti avuto un SomeFunctiondelegato in C #, usa ISomeBehaviourinvece un riferimento:

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

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

Con le classi interne anonime, puoi persino evitare di dichiarare classi nominate separate e trattarle quasi come vere funzioni delegate.

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

...

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

Questo dovrebbe probabilmente essere usato solo quando l'implementazione è molto specifica per il contesto attuale e non trarrebbe beneficio dal riutilizzo.

E poi ovviamente in Java 8, queste diventano in sostanza espressioni lambda:

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

6
+1. questa è una soluzione decente e la verbosità potrebbe essere compensata dalla manutenibilità in futuro.
nawfal,

questo è fantastico ... Attualmente sto lavorando a un progetto in cui non sono in grado di usare la riflessione a causa di vincoli del progetto e questa soluzione alternativa porta a termine il lavoro magnificamente :)
Jonathan Camarena

Java ha una convenzione di denominazione I-prefix per le interfacce? Non l'ho mai visto prima
Kyle Delaney,

36

Racconto: no .

introduzione

La versione più recente dell'ambiente di sviluppo Microsoft Visual J ++ supporta un costrutto di linguaggio chiamato delegati o riferimenti a metodi associati . Questo costrutto, e le nuove parole chiave delegatee multicastintrodotte per supportarlo, non fanno parte del linguaggio di programmazione Java TM , che è specificato dalla specifica del linguaggio Java e modificato dalla specifica delle classi interne inclusa nella documentazione per il software JDKTM 1.1 .

È improbabile che il linguaggio di programmazione Java includa mai questo costrutto. Sun ha già attentamente considerato l'adozione nel 1996, al punto di costruire e scartare prototipi funzionanti. La nostra conclusione è stata che i riferimenti ai metodi vincolati sono superflui e dannosi per la lingua. Questa decisione è stata presa in consultazione con Borland International, che aveva precedenti esperienze con riferimenti a metodi rilegati in Delphi Object Pascal.

Riteniamo che i riferimenti ai metodi associati non siano necessari perché un'altra alternativa di progettazione, le classi interne , fornisce funzionalità uguali o superiori. In particolare, le classi interne supportano completamente i requisiti della gestione degli eventi dell'interfaccia utente e sono state utilizzate per implementare un'API dell'interfaccia utente almeno tanto completa quanto le Classi di Windows Foundation.

Riteniamo che i riferimenti ai metodi associati siano dannosi perché riducono la semplicità del linguaggio di programmazione Java e il carattere pervasivamente orientato agli oggetti delle API. I riferimenti al metodo associato introducono anche irregolarità nella sintassi del linguaggio e nelle regole di ambito. Infine, diluiscono l'investimento nelle tecnologie VM perché le VM sono necessarie per gestire in modo efficiente tipi di riferimenti e collegamenti aggiuntivi e disparati.


Come dice in ciò che Patrick ha collegato , vuoi usare invece le classi interne.
SCdF,

16
Ottimo articolo ADORO la loro definizione di "semplice": Java è progettato per essere un linguaggio semplice come in "Java deve essere semplice per scrivere compilatore / VM per" mentre ignora "Java deve essere semplice da scrivere / leggere da una persona" . Spiega molto.
Juozas Kontvainis,

5
Penso solo che SUN abbia fatto un grande errore. Semplicemente non sono stati convinti dal paradigma funzionale, e questo è tutto.
Stephane Rolland,

7
@Juozas: Python è esplicitamente creato per essere semplice da scrivere / leggere da una persona e implementa funzioni / delegati lambda .
Stephane Rolland,

5
Sostituito con un collegamento archive.org. Inoltre, è davvero stupido, Oracle.
Patrick,

18

Hai letto questo :

I delegati sono un costrutto utile nei sistemi basati su eventi. I delegati sono essenzialmente oggetti che codificano un metodo di invio su un oggetto specificato. Questo documento mostra come le classi interne Java forniscono una soluzione più generica a tali problemi.

Che cos'è un delegato? In realtà è molto simile a un puntatore alla funzione membro utilizzata in C ++. Ma un delegato contiene l'oggetto target insieme al metodo da invocare. Idealmente sarebbe bello poter dire:

obj.registerHandler (ano.methodOne);

..e che il metodo methodOne sarebbe stato chiamato ano quando è stato ricevuto un evento specifico.

Questo è ciò che la struttura del delegato ottiene.

Classi interne di Java

È stato sostenuto che Java fornisce questa funzionalità tramite classi interne anonime e quindi non necessita del costrutto delegato aggiuntivo.

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

A prima vista questo sembra corretto ma allo stesso tempo un fastidio. Perché per molti esempi di elaborazione degli eventi la semplicità della sintassi dei delegati è molto interessante.

Gestore generale

Tuttavia, se la programmazione basata su eventi viene utilizzata in modo più pervasivo, ad esempio, come parte di un ambiente di programmazione asincrono generale, è in gioco qualcosa di più.

In una situazione così generale, non è sufficiente includere solo il metodo target e l'istanza dell'oggetto target. In generale, potrebbero essere necessari altri parametri, che sono determinati nel contesto in cui il gestore eventi è registrato.

In questa situazione più generale, l'approccio java può fornire una soluzione molto elegante, in particolare se combinato con l'uso delle variabili finali:

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

Hai la tua attenzione?

Si noti che le variabili finali sono accessibili dalle definizioni del metodo di classe anonima. Assicurati di studiare attentamente questo codice per comprendere le conseguenze. Questa è potenzialmente una tecnica molto potente. Ad esempio, può essere utilizzato con buoni risultati quando si registrano i gestori in MiniDOM e in situazioni più generali.

Al contrario, il costrutto delegato non fornisce una soluzione per questo requisito più generale e come tale dovrebbe essere respinto come un linguaggio su cui i progetti possono essere basati.



5

No, ma sono falsi usando proxy e riflessione:

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

La cosa bella di questo idioma è che puoi verificare che il metodo delegato esista e abbia la firma richiesta nel punto in cui crei il delegatore (anche se non in fase di compilazione, sfortunatamente, sebbene un plug-in FindBugs potrebbe aiuto qui), quindi utilizzalo in modo sicuro per delegare a varie istanze.

Vedi il codice karg su github per ulteriori test e implementazione .


2

Ho implementato il supporto callback / delegate in Java usando la reflection. Dettagli e fonte di lavoro sono disponibili sul mio sito web .

Come funziona

Esiste una classe di principio chiamata Callback con una classe nidificata denominata WithParms. L'API che richiede il callback prenderà un oggetto Callback come parametro e, se necessario, creerà un Callback.WithParms come variabile di metodo. Dal momento che molte delle applicazioni di questo oggetto saranno ricorsive, questo funziona in modo molto pulito.

Con le prestazioni ancora una priorità per me, non volevo che mi venisse richiesto di creare un array di oggetti usa e getta per contenere i parametri per ogni chiamata - dopotutto in una grande struttura di dati potrebbero esserci migliaia di elementi e in un'elaborazione dei messaggi scenario potremmo finire per elaborare migliaia di strutture di dati al secondo.

Per essere thread-safe, l'array di parametri deve esistere in modo univoco per ogni invocazione del metodo API e per efficienza lo stesso deve essere utilizzato per ogni invocazione del callback; Avevo bisogno di un secondo oggetto che sarebbe stato economico da creare per legare il callback con un array di parametri per l'invocazione. Ma, in alcuni scenari, il chiamante avrebbe già un array di parametri per altri motivi. Per questi due motivi, l'array di parametri non appartiene all'oggetto Callback. Anche la scelta dell'invocazione (passando i parametri come un array o come singoli oggetti) appartiene all'API usando il callback che le consente di usare qualunque invocazione sia più adatta al suo funzionamento interno.

La classe nidificata WithParms, quindi, è facoltativa e ha due scopi, contiene l'array di oggetti parametro necessario per le chiamate di callback e fornisce 10 metodi invoke () sovraccarichi (con da 1 a 10 parametri) che caricano l'array di parametri e quindi invocare il target di callback.

Quello che segue è un esempio che utilizza un callback per elaborare i file in un albero di directory. Questo è un passaggio di convalida iniziale che conta solo i file da elaborare e garantisce che nessuno superi una dimensione massima predeterminata. In questo caso, creiamo semplicemente il callback in linea con l'invocazione dell'API. Tuttavia, riflettiamo il metodo target come valore statico in modo che la riflessione non venga eseguita ogni volta.

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

Questo esempio illustra la bellezza di questo approccio: la logica specifica dell'applicazione viene sottratta al callback e la fatica di ricorrere in modo ricorsivo a un albero di directory viene nascosta piacevolmente in un metodo di utilità statica completamente riutilizzabile. E non dobbiamo pagare ripetutamente il prezzo di definizione e implementazione di un'interfaccia per ogni nuovo utilizzo. Certo, l'argomento per un'interfaccia è che è molto più esplicito su cosa implementare (è forzato, non semplicemente documentato) - ma in pratica non ho trovato un problema per ottenere la definizione di callback corretta.

Definire e implementare un'interfaccia non è poi così male (a meno che tu non stia distribuendo applet, come lo sono io, dove evitare di creare classi extra conta davvero), ma dove questo brilla davvero è quando hai più callback in una singola classe. Non solo è costretto a spingerli ciascuno in una classe interna separata aggiunta nell'applicazione distribuita, ma è decisamente noioso da programmare e tutto quel codice di caldaia è davvero solo "rumore".


1

Sì e No, ma si potrebbe pensare a questo modello delegato in Java. Questo tutorial video tratta dello scambio di dati tra frammenti di attività e ha una grande essenza di modelli di deleghe che utilizzano interfacce.

Interfaccia Java


1

Non ha una delegateparola chiave esplicita come C #, ma puoi ottenere simili in Java 8 usando un'interfaccia funzionale (cioè qualsiasi interfaccia con esattamente un metodo) e 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();
}

Ogni nuovo oggetto di tipo SingleFuncdeve essere implementato printMe(), quindi è sicuro passarlo a un altro metodo (ad es. delegate(SingleFunc)) Per chiamarlo printMe().


0

Anche se non è affatto pulito, ma potresti implementare qualcosa come i delegati C # usando un proxy Java .


2
Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il collegamento come riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia.
StackFlowed

2
@StackFlowed Rimuovi il link e cosa ottieni? Un post con informazioni. Vota per continuare.
Scimonster,

1
@Scimonster cosa accadrebbe se il link non fosse più valido?
StackFlowed

@StackFlowed Fornisce comunque una risposta: utilizzare un proxy Java.
Scimonster,

allora potrebbe essere lasciato come commento?
StackFlowed

0

No, ma ha un comportamento simile, internamente.

In C # i delegati vengono utilizzati per creare un punto di ingresso separato e funzionano in modo molto simile a un puntatore a funzione.

In java non esiste nulla come puntatore a funzione (a prima vista) ma internamente Java deve fare la stessa cosa per raggiungere questi obiettivi.

Ad esempio, la creazione di thread in Java richiede una classe che estende Thread o implementa Runnable, poiché una variabile oggetto classe può essere utilizzata un puntatore di posizione di memoria.



0

Il codice descritto offre molti dei vantaggi dei delegati C #. I metodi, statici o dinamici, possono essere trattati in modo uniforme. La complessità nei metodi di chiamata tramite reflection è ridotta e il codice è riutilizzabile, nel senso che non richiede classi aggiuntive nel codice utente. Nota che stiamo chiamando una versione alternativa di invoke, in cui è possibile chiamare un metodo con un parametro senza creare un array di oggetti. Codice Java di seguito:

  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 non ha delegati ed è orgoglioso di questo :). Da quello che ho letto qui ho trovato in sostanza 2 modi per falsificare i delegati: 1. riflessione; 2. classe interna

I riflessi sono slooooow! La classe interna non copre il caso d'uso più semplice: la funzione di ordinamento. Non voglio entrare nei dettagli, ma la soluzione con la classe interna è fondamentalmente quella di creare una classe wrapper per un array di numeri interi da ordinare in ordine crescente e una classe per un array di numeri interi da ordinare in ordine decrescente.


Cosa c'entra la classe interna con l'ordinamento? E cosa c'entra l'ordinamento con la domanda?
nawfal,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.