35 votes

À quoi servent les constructeurs génériques en Java ?

Comme tout le monde le sait, vous pouvez avoir une classe générique en Java en utilisant des arguments de type :

class Foo<T> {
    T tee;
    Foo(T tee) {
        this.tee = tee;
    }
}

Mais vous pouvez aussi avoir un générique constructeurs c'est-à-dire des constructeurs qui reçoivent explicitement leurs propres arguments de type générique, par exemple :

class Bar {
    <U> Bar(U you) {
        // Why!?
    }
}

J'ai du mal à comprendre le cas d'utilisation. Qu'est-ce que cette fonctionnalité me permet de faire ?

11 votes

Peut-être dans des situations comme celle-ci : <U> Bar(U a, U b) ?

1 votes

Cet exemple n'est pas très utile. Mais les génériques vous aideraient à établir des contrats sur les arguments lorsque U devrait étendre un autre type ou être un type d'intersection. (par exemple, <U étend Serializable>)

7 votes

Les méthodes génériques sont utiles. Les constructeurs sont similaires aux méthodes. Il serait plus difficile de interdire que de les autoriser, même s'il n'y a pas de cas d'utilisation particulièrement convaincant.

23voto

Lino Points 13360

Le cas d'utilisation auquel je pense pourrait être que quelqu'un veut un objet qui hérite de 2 types. Par exemple, il implémente 2 interfaces :

public class Foo {

    public <T extends Bar & Baz> Foo(T barAndBaz){
        barAndBaz.barMethod();
        barAndBaz.bazMethod();
    }
}

Bien que je ne l'aie jamais utilisé en production.

1 votes

C'est un bon exemple. Je n'y avais pas pensé ! Bien qu'à proprement parler, vous puissiez également résoudre ce problème avec interface BarBaz extends Bar, Baz ou similaire, il s'agit probablement d'une façon plus agréable de dire la même chose. Mais c'est, de manière assez amusante, plus faiblement car il n'y a pas de type d'exécution.

0 votes

@0xbe5077ed exactement, et parce que le type d'exécution est inconnu, ou n'est même pas vraiment nécessaire, c'est plus dynamique.

19voto

Jacob G. Points 16099

Il est clair dans l'exemple que vous avez fourni que U ne joue aucun rôle dans le constructeur de la classe, puisqu'il devient effectivement un Object au moment de l'exécution :

class Bar {
    <U> Bar(U you) {
        // Why!?
    }
}

Mais supposons que je veuille que mon constructeur n'accepte que les types qui étendent une autre classe ou interface, comme indiqué ci-dessous :

class Foo<T extends Baz> {
    <U extends Bar> Foo(U u) {
        // I must be a Bar!
    }
}

Remarquez que la classe utilise déjà un type générique différent ; cela vous permet d'utiliser un type générique distinct, sans rapport avec la définition de la classe.

D'accord, je n'ai jamais utilisé quelque chose comme ça, et je ne l'ai jamais vu en service, mais c'est possible !

21 votes

Si tout ce que vous avez besoin de savoir, c'est que u est un Bar Dans ce cas, n'est-il pas préférable de faire simplement Foo(Bar u) {} ? Je suis tout à fait d'accord avec votre idée d'être générique par rapport aux descendants de Baz Cependant, un bon exemple est <T extends Comparable> pour les structures de données ordonnées par comparaison, comme les arbres de recherche

6 votes

<U extends Bar & Baz> Foo(U u) où Bar et Baz ne sont pas liés (par exemple deux interfaces, toutes deux requises par le constructeur).

0 votes

Oui, cela aurait été mieux.

15voto

John Bollinger Points 16563

Qu'est-ce que cette fonction me permet de faire ?

Il y a au moins trois deux choses qu'il vous permet de faire et que vous ne pourriez pas faire autrement :

  1. exprimer des relations entre les types d'arguments, par exemple :

    class Bar {
        <T> Bar(T object, Class<T> type) {
            // 'type' must represent a class to which 'object' is assignable,
            // albeit not necessarily 'object''s exact class.
            // ...
        }
    }
  2. <br />

  3. Comme @Lino l'a observé en premier lieu, elle permet d'exprimer que les arguments doivent être compatibles avec une combinaison de deux ou plusieurs types non liés (ce qui peut avoir un sens lorsque tous les types sauf un au plus sont des types d'interface). Voir la réponse de Lino pour un exemple.

1 votes

L'exemple 2 ne se contente-t-il pas de déduire T como Object pour des types non liés, laissant ainsi passer n'importe quoi ?

0 votes

@user2357112, si vous vous appuyez sur l'inférence de type et que vous n'avez rien d'autre pour la contraindre, alors oui, Object peut être le type déduit dans le cas 2. La mise en place d'une borne inférieure est plus intéressante lorsqu'elle s'accompagne d'autres contraintes.

0 votes

Le deuxième cas n'est pas compilable, vous ne pouvez pas utiliser une borne inférieure de cette façon.

10voto

keuleJ Points 977

En fait, ce constructeur

class Bar {
    <U> Bar(U you) {
        // Why!?
    }
}

est comme un méthode générique . Cela aurait beaucoup plus de sens si vous aviez plusieurs arguments de constructeur comme ceci :

class Bar {
    <U> Bar(U you, List<U> me) {
        // Why!?
    }
} 

Vous pourriez alors appliquer la contrainte selon laquelle ils ont le même temps avec le compilateur. Sans faire de U un générique pour toute la classe.

4 votes

Le compilateur pourrait décider d'insérer Object as U pour résoudre le problème, auquel cas vous pouvez toujours passer un objet de type int et un objet de type string.

4 votes

Mais notez que si vous ne spécifiez pas explicitement l'argument de type lorsque vous invoquez le constructeur de l'exemple, Java peut déduire U être Object Il n'y a donc aucune restriction.

0 votes

Je comprends qu'il s'agit d'une méthode générique. est une méthode générique portant un nom particulier, <init> . Mais pour une méthode générique, il s'agit généralement d'une méthode de type static dans la pratique, car pour l'essentiel, les méthodes d'instance peuvent généralement faire ce dont elles ont besoin en utilisant le caractère "générique" dont elles "héritent" de la classe. Je me demandais ce qu'il était possible de faire avec un constructeur générique, étant donné que les constructeurs initialisent principalement les éléments suivants classe et il est difficile d'imaginer des scénarios où l'on mettrait en place un système d'alerte. classe basé sur une dimension générique qui n'est pas disponible pour la classe...

8voto

Makoto Points 23751

Parce que ce type générique non lié s'efface pour Object cela reviendrait à passer Object dans votre constructeur :

public class Foo {
    Object o;

    public Foo(Object o) {
        this.o = o;
    }
}

...mais comme passer un blanc Object à moins que vous ne fassiez quelque chose astucieux mais cela n'a guère de valeur pratique.

Vous voyez des avantages et des gains si vous passez en lié génériques à la place, ce qui signifie que vous pouvez réellement avoir des garanties autour du type qui vous intéresse.

public class Foo<T extends Collection<?>> {
    T collection;
    public Foo(T collection) {
        this.collection = collection;
    }
}

En fait, il s'agit plus d'une question de flexibilité que de quelque chose de révolutionnaire. Si vous voulez avoir la flexibilité de passer dans une catégorie spécifique de types, alors vous avez le pouvoir de le faire ici. Si vous Ne le fais pas. alors il n'y a pas de problème avec les classes standard. Il est simplement là pour votre commodité, et puisque l'effacement de type est toujours une chose, un générique non lié est le même (et a la même utilité) que passer Object .

2 votes

L'OP semble se concentrer sur le constructeur générique de sa classe Bar ayant présenté la classe générique Foo principalement pour le contraste. Bien que tout dans cette réponse semble correct, j'ai du mal à voir comment elle répond à la question du constructeur générique.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X