IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Java Discussion :

covariance entre génériques


Sujet :

Langage Java

  1. #1
    Membre éprouvé
    Inscrit en
    Août 2010
    Messages
    1 124
    Détails du profil
    Informations forums :
    Inscription : Août 2010
    Messages : 1 124
    Points : 1 277
    Points
    1 277
    Par défaut covariance entre génériques
    Bonjour,

    Je ne comprends pas pourquoi le compilateur Java n'assure pas la conversion suivante.

    En déclarant format() avec <K,V> (ligne commentée), je comprends que l'information générique sur K et V ne soit pas disponible dans TextOutputFormat.class.
    Par contre, avec la déclaration Class<? extends InputFormat<?,?>> (ligne non commentée), tout se passe comme si TextOutputFormat et TextOutputFormat<?,?> n'étaient pas covariants.

    Pourriez-vous m'expliquer ?
    Merci d'avance.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    A<Text,NullWritable> a = ...;
    a.format(TextOutputFormat.class); // PROBLEME :  method is not applicable
    a.format((Class<? extends InputFormat<Text,NullWritable>>) TextOutputFormat.class); // ok
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    Interface A<K,V> {
        /** A setter for attribute format */
        // public void format(Class<? extends InputFormat<K,V>> format);
        public void format(Class<? extends InputFormat<?,?>> format);
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    java.lang.Object
      extended by org.apache.hadoop.mapreduce.OutputFormat<K,V>
          extended by org.apache.hadoop.mapreduce.lib.output.FileOutputFormat<K,V>
              extended by org.apache.hadoop.mapreduce.lib.output.TextOutputFormat<K,V>

  2. #2
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Salut,


    Je n'arrive pas à reproduire ton problème.
    Quelle est ta version de Java ?
    Comment est déclaré la classe TextOutputFormat ?


    a++

  3. #3
    Membre éprouvé
    Inscrit en
    Août 2010
    Messages
    1 124
    Détails du profil
    Informations forums :
    Inscription : Août 2010
    Messages : 1 124
    Points : 1 277
    Points
    1 277
    Par défaut
    Merci adiguba,

    Java 1..8.0_102

    Ci dessous un exemple simplifié
    A <=> Interface A<K,V>
    B <=> OutputFormat<K,V>
    C <=> TextOutputFormat<K,V>

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    public class PipoTest {
        public static class B<K,V> {}
        public static class C<K,V> extends B<K,V> {}
        public static class D extends B<String,String> {}
     
        public static class A<K,V> {
            public void foo(Class<? extends B<?,?>> arg) {}
        }
        public static void main(String[] clargs) {
            A<String,String> a = new A<>();
            a.foo(C.class);
            // The method foo(Class<? extends PipoTest.B<?,?>>) in the type PipoTest.A<String,String> is not applicable for the arguments (Class<PipoTest.C>)
     
            a.foo( (Class<? extends C<String,String>>) C.class); // valid
            a.foo(D.class); // valid
        }
     
    }

  4. #4
    Membre éprouvé
    Inscrit en
    Août 2010
    Messages
    1 124
    Détails du profil
    Informations forums :
    Inscription : Août 2010
    Messages : 1 124
    Points : 1 277
    Points
    1 277
    Par défaut
    En fait, même ceci ne marche pas :

  5. #5
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Ok je pensais que TextOutputFormat était défini avec un type précis (comme "D" dans ton exemple).


    Même ce code n'est pas valide :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
            a.foo( (Class<? extends C<String,String>>) C.class); // valid
    Je suppose que tu compiles avec eclipse (qui ne signale qu'un warning), mais javac sort directement une erreur : incompatible types: Class<C> cannot be converted to Class<? extends C<String,String>>



    Le "truc" c'est que B.class est de type Class<B> et non pas Class<B<?,?>>, et il y a bien une différence entre les deux et ils ne sont pas compatible même s'ils peuvent sembler similaire :
    • Class<B<?,?>> correspond à une classe B dont on ne connait pas les types paramétrés.
    • Class<B> correspond au raw-type, c'est à dire sans aucun type paramétré.



    Donc dans ton cas il faudrait plutôt déclarer la méthode comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    public void foo(Class<? extends B> arg) {}
    Mais... c'est un usage "unsafe" qui génèrera un warning.



    D'où ma nouvelle question : à quoi cela doit-il te servir ?
    Que va tu faire de cet objet Class ?


    a++

  6. #6
    Membre éprouvé
    Inscrit en
    Août 2010
    Messages
    1 124
    Détails du profil
    Informations forums :
    Inscription : Août 2010
    Messages : 1 124
    Points : 1 277
    Points
    1 277
    Par défaut
    Merci bcp pour l'explication !

    Cela me sert à vérifier que les paramètres génériques du B que je vais assigner à A<K,V> sont bien cohérents avec K et V.
    En particulier si une autre méthode de B renvoie des K et des V, je veux être sur de les manipuler en tant que K et V dans la classe A.

    Maintenant, je suis en train de réaliser que les setters auxquels je communique ces classes sont declarés avec des raw types.
    Mais quand bien même, n'est ce pas la bonne approche pour effectuer les vérifications dont je parle au compile-time ?

  7. #7
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Mais concrètement : comment tu te sert de l'argument "Class" de la méthode foo() ?

    Parce que si tu utilises des raw-types ou des <?> tu ne vérifies rien du tout.
    De même que si tu utilises un type au lieu d'une instance : le fait de passer un OutputFormat.class ne te permet pas d'assurer quoi que ce soit.


    a++

  8. #8
    Membre éprouvé
    Inscrit en
    Août 2010
    Messages
    1 124
    Détails du profil
    Informations forums :
    Inscription : Août 2010
    Messages : 1 124
    Points : 1 277
    Points
    1 277
    Par défaut
    Concrètement, j'envoie ces classes à des setters du framework Hadoop. Ce framework va instancier ces différentes classes sur différentes JVM.
    Il s'avère que ces setter sont déclarés avec le raw type, ce qui fait qu'on a parfois au runtime des erreurs du genre "l'objet n'est pas du type attendu".
    C'est pour éviter ces erreurs au runtime que j'essaie de typer mes arguments avec ces bounds (ni raw ni wildcard), de sorte que ce soit vérifié au compile-time.

    le fait de passer un OutputFormat.class ne te permet pas d'assurer quoi que ce soit.
    Efffectivement, je voudrais passer "OutputFormat<String,String>.class", qui n'a pas de sens (à moins que je me mette à extraire au runtime les paramètres génériques de la classe...).
    Du coup il me semble que je n'arriverai pas à garantir les types au compile-time...

  9. #9
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Malheureusement tu ne peux pas vraiment mélanger reflection, Generics et covariance et avoir en même temps un compile typesafe...


    Je ne connais pas trop Hadoop, mais je suppose qu'elle utilise la reflection pour créer des instances.
    Pour moi le problème vient de là : il aurait été préférable d'utiliser une factory à la place.



    A la rigueur si tu englobes l'appel à Hadoop, tu peux utiliser une factory dans ton code, et l'utiliser pour créer une instance dont tu récupèreras la classe :
    Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        public static class A<K,V> {
     
            // public void foo(Class<? extends B<?,?>> arg) {}
     
            public void foo(Supplier<B<? extends K,V>> factory) {
            	// factory.get() crée une nouvelle instance :
            	Class<? extends B> arg = factory.get().getClass();
            	// ...
            }
     
        }
    Mais attention car cela va créer une instance "inutilement". Je ne sais pas si c'est gênant ou pas.


    L'avantage c'est qu'avec Java 8 et les références de méthodes, cela s'utilise aussi facilement (on peut utiliser LaClass::new pour obtenir une factory qui appelle le constructeur) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
        A<String,String> a1 = new A<>();
        a1.foo(B::new); // ok
        a1.foo(C::new); // ok
        a1.foo(D::new); // ok
     
        A<Date,Date> a2 = new A<>();
        a2.foo(B::new); // ok
        a2.foo(C::new); // ok
        a2.foo(D::new); // erreur

    a++

  10. #10
    Membre éprouvé
    Inscrit en
    Août 2010
    Messages
    1 124
    Détails du profil
    Informations forums :
    Inscription : Août 2010
    Messages : 1 124
    Points : 1 277
    Points
    1 277
    Par défaut
    Merci adiGuba,

    Effectivement, l'approche par factory me permet le compile-time check car j'englobe tous les appels à hadoop.
    Son seul défaut est de m'obliger à créer une factory pour toutes les classes que je dois envoyer aux différents setters (il y en a un paquet).

    Par contre, ce sont les factory qui sont typées, donc on ne peut pas garantir qu'elles vont renvoyer ce qu'on attends (il me semble ?)...
    Mais on a au moins séparé les responsabilités.


    Je mentionne juste une autre approche pour être complet : vérifier par instrospection les paramètres génériques déclarés.
    Ca reste du compile-time, mais ca peut être fait très tôt (avant que je communiques ces classes à hadoop, donc bien avant que les jobs soient lancés).


    Encore merci !

  11. #11
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par shaiHulud Voir le message
    Effectivement, l'approche par factory me permet le compile-time check car j'englobe tous les appels à hadoop.
    Son seul défaut est de m'obliger à créer une factory pour toutes les classes que je dois envoyer aux différents setters (il y en a un paquet).
    Si tu utilises les références de méthode tu n'en as pas besoin puisque tu peux utiliser la syntaxe NomDeLaClasse::new

    Citation Envoyé par shaiHulud Voir le message
    Par contre, ce sont les factory qui sont typées, donc on ne peut pas garantir qu'elles vont renvoyer ce qu'on attends (il me semble ?)...
    Mais on a au moins séparé les responsabilités.
    Comment pourraient-elles renvoyer autre chose ???


    Citation Envoyé par shaiHulud Voir le message
    Je mentionne juste une autre approche pour être complet : vérifier par instrospection les paramètres génériques déclarés.
    Ca reste du compile-time, mais ca peut être fait très tôt (avant que je communiques ces classes à hadoop, donc bien avant que les jobs soient lancés).
    Oui mais cela pourrait être plus complexe à mettre en place pour gérer les différents cas d'héritage/implémentation...


    a++

  12. #12
    Membre éprouvé
    Inscrit en
    Août 2010
    Messages
    1 124
    Détails du profil
    Informations forums :
    Inscription : Août 2010
    Messages : 1 124
    Points : 1 277
    Points
    1 277
    Par défaut
    Comment pourraient-elles renvoyer autre chose ???
    Si le factory.get() est declaré comme (B est l'équivalent du B que je définis dans l'exemple PipoTest)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Class<? extends B> get()
    Comment peux-tu implémenter celle qui renvoie C<String,String> ou D (toujours dans mon exemple PipoTest) ?

  13. #13
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Non je parlais d'une factory qui retourne directement une instance, pas une Class... sinon on se retrouve avec le même problème.


    Car là tu peux vraiment restreindre les types paramétrés :

    Supplier<B<K,V>> : les types K,V doivent être identique
    Supplier<B<? extends K, ? extends V>> : les types K,V peuvent correspondre à des classes filles
    Supplier<B<? super K, ? super V>> : les types K,V peuvent correspondre à des classes parentes
    Supplier<B<? extends K, ? super V>> : le type K peut correspondre à une classe fille, et V peut correspondre à une classe parente
    ...



    a++

  14. #14
    Membre éprouvé
    Inscrit en
    Août 2010
    Messages
    1 124
    Détails du profil
    Informations forums :
    Inscription : Août 2010
    Messages : 1 124
    Points : 1 277
    Points
    1 277
    Par défaut
    D'accord. Ca oblige effectivement à instancier les classes que je passe... pas très efficace pour les gros objets.
    Ca doit être faisable dans la cas d'objets Hadoop, mais j'ai tellement peu l'habitude d'instancier moi même ces objets que je dois aller voir les args des constructeurs. J'espère qu'ils ont tous un constructeur ou factory publique.

    Par contre je trouve ca amusant (mais finalement assez logique après tes explications) que le type-safety soit assuré au runtime !

    Merci bcp !

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 41
    Dernier message: 30/03/2010, 16h42
  2. tri générique entre objets
    Par cashmoney dans le forum Langage
    Réponses: 31
    Dernier message: 12/03/2009, 14h03
  3. Réponses: 3
    Dernier message: 07/05/2008, 15h57

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo