Délégués Java?


194

Le langage Java possède-t-il des fonctionnalités de délégué, similaires à la façon dont C # prend en charge les délégués?


20
@Suma Comment cela peut-il être un double si la question que vous avez mentionnée a été publiée un an après celle-ci?
Ani

1
Java 8 a une fonctionnalité similaire à celle des délégués. Ça s'appelle des lambdas.
tbodt

4
@tbodt pour être plus correct, Java 8 a une fonctionnalité similaire à celle des délégués. Cela s'appelle des interfaces fonctionnelles . Les Lambdas sont un moyen de créer de telles instances de délégué (de manière anonyme).
nawfal

3
Les réponses ci-dessous proviennent de Pré-Java 8. Après Java 8 voir les réponses dans ce fil: stackoverflow.com/questions/20311779/…
Michael Lloyd Lee mlk

@nawfal, +1, mais pour être encore plus correct, Java 7 et versions antérieures ont déjà une fonctionnalité similaire à celle des délégués. Cela s'appelle des interfaces simples.
Pacerier

Réponses:


152

Non, pas vraiment.

Vous pouvez obtenir le même effet en utilisant la réflexion pour obtenir des objets Method que vous pouvez ensuite invoquer, et l'autre manière consiste à créer une interface avec une seule méthode «invoke» ou «execute», puis les instancier pour appeler la méthode votre intérêt (c'est-à-dire l'utilisation d'une classe interne anonyme).

Vous pourriez également trouver cet article intéressant / utile: un programmeur Java examine les délégués C # (@ archive.org)


3
La solution avec invoke () comme seule fonction membre d'une interface est vraiment sympa.
Stephane Rolland

1
Mais voyez mon exemple ici de ce que l'on ferait, même en Java 7, pour accomplir l'équivalent d'un délégué C #.
ToolmakerSteve

63

En fonction de ce que vous voulez dire précisément, vous pouvez obtenir un effet similaire (faire le tour d'une méthode) en utilisant le modèle de stratégie.

Au lieu d'une ligne comme celle-ci déclarant une signature de méthode nommée:

// C#
public delegate void SomeFunction();

déclarer une interface:

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

Pour les implémentations concrètes de la méthode, définissez une classe qui implémente le comportement:

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

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

Ensuite, partout où vous auriez eu un SomeFunctiondélégué en C #, utilisez ISomeBehaviourplutôt une référence:

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

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

Avec les classes internes anonymes, vous pouvez même éviter de déclarer des classes nommées distinctes et les traiter presque comme de vraies fonctions de délégué.

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

...

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

Cela ne devrait probablement être utilisé que lorsque l'implémentation est très spécifique au contexte actuel et ne gagnerait pas à être réutilisée.

Et puis bien sûr en Java 8, celles-ci deviennent essentiellement des expressions lambda:

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

6
+1. il s'agit d'une solution de contournement décente, et la verbosité peut être compensée par la maintenabilité à l'avenir.
nawfal

c'est génial ... Je travaille actuellement sur un projet où je ne peux pas utiliser la réflexion en raison des contraintes du projet et cette solution de rechange fait le travail à merveille :)
Jonathan Camarena

Java a-t-il une convention de dénomination de préfixe I pour les interfaces? Je n'avais encore jamais vu ça
Kyle Delaney

36

Petite histoire: non .

introduction

La dernière version de l'environnement de développement Microsoft Visual J ++ prend en charge une construction de langage appelée délégués ou références de méthode liée . Cette construction, ainsi que les nouveaux mots clés delegateet multicastintroduits pour la prendre en charge, ne font pas partie du langage de programmation Java TM , qui est spécifié par la spécification du langage Java et modifié par la spécification des classes internes incluse dans la documentation du logiciel JDKTM 1.1 .

Il est peu probable que le langage de programmation Java inclue un jour cette construction. Sun a déjà soigneusement envisagé de l'adopter en 1996, au point de construire et de jeter des prototypes fonctionnels. Notre conclusion était que les références de méthode liées sont inutiles et nuisibles au langage. Cette décision a été prise en consultation avec Borland International, qui avait une expérience antérieure avec les références de méthode liée dans Delphi Object Pascal.

Nous pensons que les références de méthode liées ne sont pas nécessaires car une autre alternative de conception, les classes internes , fournit des fonctionnalités égales ou supérieures. En particulier, les classes internes prennent entièrement en charge les exigences de la gestion des événements d'interface utilisateur et ont été utilisées pour implémenter une API d'interface utilisateur au moins aussi complète que les classes Windows Foundation.

Nous pensons que les références de méthodes liées sont nuisibles car elles nuisent à la simplicité du langage de programmation Java et au caractère omniprésent des objets des API. Les références de méthode liées introduisent également des irrégularités dans la syntaxe du langage et les règles de portée. Enfin, ils diluent l'investissement dans les technologies de VM car les VM doivent gérer efficacement des types de références supplémentaires et disparates et une liaison de méthode.


Comme il est dit dans ce lien lié à Patrick, vous souhaitez plutôt utiliser des classes internes.
SCdF

16
Excellent article. J'AIME leur définition de "simple": Java est un langage simple comme dans "Java doit être simple pour écrire le compilateur / VM" tout en ignorant "Java doit être simple pour écrire / lire par une personne" . Explique beaucoup.
Juozas Kontvainis

5
Je pense juste que SUN a fait une grosse erreur. Ils n'ont tout simplement pas été convaincus par le paradigme fonctionnel, et c'est tout.
Stéphane Rolland

7
@Juozas: Python est explicitement conçu pour être simple à écrire / lire par une personne et il implémente des fonctions / délégués lambda .
Stéphane Rolland

5
Remplacé par un lien archive.org. De plus, c'est vraiment stupide, Oracle.
Patrick

18

Avez-vous lu ceci :

Les délégués sont une construction utile dans les systèmes basés sur des événements. Les délégués sont essentiellement des objets qui codent une répartition de méthode sur un objet spécifié. Ce document montre comment les classes internes Java fournissent une solution plus générique à ces problèmes.

Qu'est-ce qu'un délégué? Vraiment, il est très similaire à un pointeur sur une fonction membre tel qu'utilisé en C ++. Mais un délégué contient l'objet cible avec la méthode à invoquer. Idéalement, ce serait bien de pouvoir dire:

obj.registerHandler (ano.methodOne);

..et que la méthode methodOne serait appelée ano lorsqu'un événement spécifique serait reçu.

C'est ce que permet la structure des délégués.

Classes internes Java

Il a été avancé que Java fournit cette fonctionnalité via des classes internes anonymes et n'a donc pas besoin de la construction Delegate supplémentaire.

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

À première vue, cela semble correct mais en même temps gênant. Parce que pour de nombreux exemples de traitement d'événements, la simplicité de la syntaxe des délégués est très attrayante.

Gestionnaire général

Cependant, si la programmation basée sur les événements est utilisée d'une manière plus omniprésente, par exemple, dans le cadre d'un environnement de programmation asynchrone général, les enjeux sont plus importants.

Dans une telle situation générale, il ne suffit pas d'inclure uniquement la méthode cible et l'instance d'objet cible. En général, il peut y avoir d'autres paramètres requis, qui sont déterminés dans le contexte lorsque le gestionnaire d'événements est enregistré.

Dans cette situation plus générale, l'approche java peut fournir une solution très élégante, en particulier lorsqu'elle est combinée avec l'utilisation de variables finales:

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

Vous avez votre attention?

Notez que les variables finales sont accessibles à partir des définitions de méthode de classe anonyme. Assurez-vous d'étudier attentivement ce code pour comprendre les ramifications. C'est potentiellement une technique très puissante. Par exemple, il peut être utilisé à bon escient lors de l'enregistrement de gestionnaires dans MiniDOM et dans des situations plus générales.

En revanche, la conception Délégué ne fournit pas de solution à cette exigence plus générale et, en tant que telle, doit être rejetée en tant qu'idiome sur lequel les conceptions peuvent être basées.



5

Non, mais ils peuvent être simulés à l'aide de procurations et de réflexion:

  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 bonne chose à propos de cet idiome est que vous pouvez vérifier que la méthode déléguée existe et a la signature requise, au moment où vous créez le délégant (bien que pas au moment de la compilation, malheureusement, bien qu'un plug-in FindBugs puisse aide ici), puis utilisez-le en toute sécurité pour déléguer à diverses instances.

Voir le code karg sur github pour plus de tests et d' implémentation .


2

J'ai implémenté le support de rappel / délégué en Java en utilisant la réflexion. Les détails et la source de travail sont disponibles sur mon site Web .

Comment ça fonctionne

Il existe une classe principale nommée Callback avec une classe imbriquée nommée WithParms. L'API qui a besoin du rappel prendra un objet Callback comme paramètre et, si nécessaire, créera un Callback.WithParms comme variable de méthode. Étant donné qu'un grand nombre des applications de cet objet seront récursives, cela fonctionne très proprement.

Les performances étant toujours une priorité pour moi, je ne voulais pas être obligé de créer un tableau d'objets jetables pour contenir les paramètres de chaque appel - après tout, dans une grande structure de données, il pourrait y avoir des milliers d'éléments et dans un traitement de message scénario, nous pourrions finir par traiter des milliers de structures de données par seconde.

Pour être sûr pour les threads, le tableau de paramètres doit exister de façon unique pour chaque appel de la méthode API, et pour plus d'efficacité, le même doit être utilisé pour chaque appel du rappel; J'avais besoin d'un deuxième objet qui serait bon marché à créer afin de lier le rappel avec un tableau de paramètres pour l'invocation. Mais, dans certains scénarios, l'invocateur aurait déjà un tableau de paramètres pour d'autres raisons. Pour ces deux raisons, le tableau de paramètres n'appartient pas à l'objet Callback. Le choix de l'invocation (en passant les paramètres sous forme de tableau ou d'objets individuels) appartient également à l'API en utilisant le rappel lui permettant d'utiliser celle qui convient le mieux à son fonctionnement interne.

La classe imbriquée WithParms est alors facultative et remplit deux fonctions: elle contient le tableau d'objets de paramètres requis pour les appels de rappel et fournit 10 méthodes invoke () surchargées (avec de 1 à 10 paramètres) qui chargent le tableau de paramètres, puis invoquer la cible de rappel.

Ce qui suit est un exemple utilisant un rappel pour traiter les fichiers dans une arborescence de répertoires. Il s'agit d'une passe de validation initiale qui compte uniquement les fichiers à traiter et garantit qu'aucun ne dépasse une taille maximale prédéterminée. Dans ce cas, nous créons simplement le rappel en ligne avec l'appel de l'API. Cependant, nous reflétons la méthode cible comme une valeur statique afin que la réflexion ne soit pas effectuée à chaque fois.

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

Cet exemple illustre la beauté de cette approche - la logique spécifique à l'application est abstraite dans le rappel, et la corvée de parcourir récursivement une arborescence de répertoires est bien cachée dans une méthode utilitaire statique entièrement réutilisable. Et nous n'avons pas à payer à plusieurs reprises le prix de la définition et de la mise en œuvre d'une interface pour chaque nouvelle utilisation. Bien sûr, l'argument en faveur de une interface est qu'elle est beaucoup plus explicite sur ce qu'il faut implémenter (elle est appliquée, pas simplement documentée) - mais en pratique, je n'ai pas trouvé que ce soit un problème pour obtenir la bonne définition de rappel.

Définir et implémenter une interface n'est pas si mal (sauf si vous distribuez des applets, comme moi, où éviter de créer des classes supplémentaires compte vraiment), mais où cela brille vraiment lorsque vous avez plusieurs rappels dans une seule classe. Non seulement est obligé de les pousser chacun dans une classe interne distincte ajoutée dans l'application déployée, mais c'est carrément fastidieux à programmer et tout ce code de plaque de chaudière est vraiment juste du «bruit».


1

Oui et non, mais le modèle de délégué en Java pourrait être pensé de cette façon. Ce didacticiel vidéo traite de l'échange de données entre des fragments d'activité et présente une grande essence de modèle sorta délégué à l'aide d'interfaces.

Interface Java


1

Il n'a pas de delegatemot clé explicite comme C #, mais vous pouvez obtenir un résultat similaire en Java 8 en utilisant une interface fonctionnelle (c'est-à-dire n'importe quelle interface avec exactement une méthode) et 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();
}

Chaque nouvel objet de type SingleFuncdoit être implémenté printMe(), il est donc sûr de le passer à une autre méthode (par exemple delegate(SingleFunc)) pour appeler la printMe()méthode.


0

Bien qu'il ne soit nulle part aussi propre, mais vous pouvez implémenter quelque chose comme des délégués C # en utilisant un proxy Java .


2
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien de référence. Les réponses de lien uniquement peuvent devenir invalides si la page liée change.
StackFlowed

2
@StackFlowed Supprimez le lien, et qu'obtenez-vous? Un poste avec des informations. Votez pour garder.
Scimonster

1
@Scimonster que se passerait-il si le lien n'était plus valide?
StackFlowed

@StackFlowed Il fournit toujours une réponse - utilisez un proxy Java.
Scimonster du

alors il pourrait être laissé comme commentaire?
StackFlowed

0

Non, mais il a un comportement similaire en interne.

En C #, les délégués sont utilisés pour créer un point d'entrée séparé et ils fonctionnent un peu comme un pointeur de fonction.

En java, il n'y a rien en tant que pointeur de fonction (en apparence) mais en interne Java doit faire la même chose pour atteindre ces objectifs.

Par exemple, la création de threads en Java nécessite une classe étendant Thread ou implémentant Runnable, car une variable d'objet de classe peut être utilisée comme pointeur d'emplacement mémoire.



0

Le code décrit offre de nombreux avantages des délégués C #. Les méthodes, statiques ou dynamiques, peuvent être traitées de manière uniforme. La complexité de l'appel des méthodes par réflexion est réduite et le code est réutilisable, dans le sens où il ne nécessite aucune classe supplémentaire dans le code utilisateur. Notez que nous appelons une autre version pratique d'invoke, où une méthode avec un paramètre peut être appelée sans créer de tableau d'objets.Code Java ci-dessous:

  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 n'a pas de délégués et en est fier :). D'après ce que j'ai lu ici, j'ai trouvé essentiellement 2 façons de truquer les délégués: 1. la réflexion; 2. classe intérieure

Les reflets sont slooooow! La classe interne ne couvre pas le cas d'utilisation le plus simple: fonction de tri. Je ne veux pas entrer dans les détails, mais la solution avec la classe interne consiste essentiellement à créer une classe wrapper pour un tableau d'entiers à trier par ordre croissant et une classe pour un tableau d'entiers à trier par ordre décroissant.


Qu'est-ce que la classe interne a à voir avec le tri? Et qu'est-ce que le tri a à voir avec la question?
nawfal
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.