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 :

java.lang.NoSuchMethodError avec compareTo sur des lambda + generics


Sujet :

Langage Java

  1. #1
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut java.lang.NoSuchMethodError avec compareTo sur des lambda + generics
    Une fois n'est pas coutume, je suis coincé. J'essaie de limiter mon code au maximum ici.

    Pour une raison X ou Y, java pédale dans la semoule quand je mélange des lambda et des interfaces Generic.


    Soit l'interface HistoryData, qui défini en gros 2 méthode: getStartDate et getEndDate.
    Soit l'interface bien connue Comparable.

    Soit la class Contract
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    public class Contract implements Comparable<Contract>, HistoryData

    Soit la méthode statique générique

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static <T extends HistoryData & Comparable<T>> T getContinuousEnd(Collection<? extends T> collection, T startingPoint){
        	return collection.stream()
        			.sorted() // natural order, oldest first thus
        			.filter((c)->c.compareTo(startingPoint)<=0)
        			// Now we have all contracts older or same than current one
        			// Keep only consecutive ones
        			.filter(HistoryData.continuousPredicate())
        			// and now last element
        			.reduce((previous, current) -> current).orElse(null);
        }
    appelée comme ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
            public Contract getContractEmployementEnd(Contract referenceContract){
            	return getContinuousEnd(getContracts(), referenceContract);
            }

    j'obtiens cette joyeuseté sans le moindre warning de compilation (donc théoriquement le compilateur me dit que le typechecking est parfait dans mon code )

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    java.lang.NoSuchMethodError: be.rmi.intranet.rh.HistoryData.compareTo(Ljava/lang/Object;)I
    	at *******.HistoryData.lambda$4(HistoryData.java:165)
    	at *******.HistoryData$$Lambda$6/21894472.test(Unknown Source)
    	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174)
    	at java.util.TreeMap$KeySpliterator.forEachRemaining(TreeMap.java:2741)
    	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
    	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
    	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    	at java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:479)
    Ce qui correspond à la ligne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    .filter((c)->c.compareTo(startingPoint)<=0)
    Est-ce qu'il y a un moyen de s'en sortir sans devoir changer l'interface de mon Contract en

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    public class Contract implements Comparable<Object>, HistoryData
    ??

  2. #2
    Modérateur

    Profil pro
    Inscrit en
    Septembre 2004
    Messages
    12 551
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2004
    Messages : 12 551
    Points : 21 607
    Points
    21 607
    Par défaut
    Ça m'a tout l'air d'un bug du compilateur. J'ai le même problème dans Eclipse, mais pas avec le compilateur d'Oracle.

    J'ai joué un peu mais pas trouvé de contournement dans Eclipse, à part <T extends Contract>
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  3. #3
    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,



    En effet cela a l'ait d'être un bug du compilateur d'eclipse.
    Sur la ligne .filter((c)->c.compareTo(startingPoint)<=0), le paramètre "c" est de type <? extends T> avec <T extends HistoryData & Comparable<T>>, mais le compilateur semble s'embrouiller les pinceaux.
    Au lieu de générer un appel vers Comparable.compareTo(), il génère un appel vers HistoryData.compareTo(), qui n'existe pas.

    Apparemment le fait de typer explicitement le paramètre semble résoudre le problème :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    .filter((T c)->c.compareTo(startingPoint)<=0)

    a++

  4. #4
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    Merci les gars.

    Par contre j'ai du mal à comprendre en quoi il y aurait un différence entre

    Comparable.compareTo
    et
    HistoryData.compareTo

    Dans les deuxième cas ca devrait déclencher une erreur de compilation

    Dans le premier cas ca pointe bien vers compare(Object) qui n'existe pas.

    En fait, depuis tout à l'heure, je me demande comment le compilateur et la JVM s'y retrouve pour trouver la bonne méthode compareTo.

    Quand je fais pour résumer

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public <T extends Comparable<T>> boolean isEquals(T t1, T t2){
        return t1.compareTo(t2)==0;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    public class Machin implements Comparable<Machin>
    public class Bidule implements Comparable<Bidule>
    comment il arrive à différentier à la compilation ou au runtime entre

    compareTo(Machin) si j'appelle avec des machines et
    compareTo(Bidule) si j'appelle avec des Bidule

    ?

    A noter qu'au final je revampé tout le code pour passer un comparateur explicite et simplifier 2/3 trucs

  5. #5
    Modérateur

    Profil pro
    Inscrit en
    Septembre 2004
    Messages
    12 551
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2004
    Messages : 12 551
    Points : 21 607
    Points
    21 607
    Par défaut
    Au runtime il appelle Comparable.compareTo(Object). Lorsque la JVM essaie de résoudre cette méthode sur un objet de classe Machin, elle détecte que la méthode Machin.compareTo(Machin) est implémentation d'une méthode générique et accepte Object comme extrémité générique. Il y a donc cast du paramètre vers Machin, et si ce cast fonctionne il y a appel de Machin.compareTo(Machin). C'est l'une des raisons pour lesquelles on a changé de version de format .class au passage en 1.5 : il fallait ajouter les métadonnées pour exprimer ça, indépendamment de l'introspection la JVM en a besoin au runtime parce que le compilateur ne peut pas donner toutes les infos.

    C'est pour cette raison que ta classe Machin n'a pas le droit d'avoir une autre méthode compareTo(Object) sous prétexte que c'est la même signature de méthode après erasure : si c'était accepté la résolution de méthode au runtime ne choisirait pas la bonne dans le cas d'un appel générique... Et il n'y a pas eu d'autre arrangement pour cause de compatibilité ascendante avec Java pré-générique.

    C'est aussi l'une des raisons pour lesquelles on a des ClassCastException n'importe où si on ignore les messages d'avertissement du compilateur quand il ne peut pas assurer la cohérence des types génériques.

    EDIT : Après réflexion je sais pas pourquoi j'ai écrit tout ça. C'est répété comme un perroquet d'une source dont je me rappelle même plus. Il doit y avoir du vrai mais à vérifier mieux que ça
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  6. #6
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    Mmm j'ignorais que c'était géré maintenant au niveau de la JVM, j'ai du rater ce passage. C'est arrivé avec quelle version de java? J'était resté sur l'information c'était du sucre syntaxique les génériques, gérés 100% au niveau du compilateur.

    Donc pour résumer, quand la JVM tombe en byteCode sur

    invokeinterface #//Comparable.compareTo(java.lang.Object)

    généré par le compilateur, car c'est la seule chose que le compilateur peut connaitre avec mon code, elle se dit

    whoooo là, compareTo c'est une méthode générique déclarée dans une interface genérique Comparable<T>. Donc je vais regarder les interfaces déclarées de la classe réelle (ici Contract), donc je vais trouver qu'elle déclare implémenter Comparable<Contract>, donc au final je vais plutôt appeler Contract.compareTo(Contract),

    c'est bien ça que je dois comprendre?

  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
    Citation Envoyé par tchize_ Voir le message
    Par contre j'ai du mal à comprendre en quoi il y aurait un différence entre

    Comparable.compareTo
    et
    HistoryData.compareTo

    Dans les deuxième cas ca devrait déclencher une erreur de compilation
    Ton T est du type HistodyData & Comparable. Donc il dispose bien à la fois des méthodes d'HistoryData ET de Comparable.
    Donc c'est normal qu'il n'y ait pas d'erreur de compilation.

    Par contre le compilo s'embrouille à la génération du bytecode, en générant un appel vers HistoryData.compareTo()...


    Citation Envoyé par tchize_ Voir le message
    Dans le premier cas ca pointe bien vers compare(Object) qui n'existe pas.
    Non non elle existe bien.
    N'oublies pas que les Generics sont perdus à la compilation, donc compareTo(T) est en fait compareTo(Object).

    Citation Envoyé par tchize_ Voir le message
    En fait, depuis tout à l'heure, je me demande comment le compilateur et la JVM s'y retrouve pour trouver la bonne méthode compareTo.

    Quand je fais pour résumer

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public <T extends Comparable<T>> boolean isEquals(T t1, T t2){
        return t1.compareTo(t2)==0;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    public class Machin implements Comparable<Machin>
    public class Bidule implements Comparable<Bidule>
    comment il arrive à différentier à la compilation ou au runtime entre

    compareTo(Machin) si j'appelle avec des machines et
    compareTo(Bidule) si j'appelle avec des Bidule

    ?
    Il y a 2 étapes :
    [list][*] Le compilateur détermine la méthode à appeler selon le contexte, et la manière dont est déclarée l'instance.
    Cela génère donc soit un invokevirtual ou un invokeinterface, respectivement pour appeler une méthode d'instance ou une méthode définie dans une interface :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Bidule b1 = new Bidule();
    Comparable<Bidule> b2 = new Bidule();
     
    b1.compareTo(...); // invokevirtual Bidule.compareTo(Bidule), puisque b1 est déclarée comme un Bidule.
    b2.compareTo(...); // invokeinterface Comparable.compareTo(Object), puisque b2 est déclarée comme un Comparable<Bidule>
    Lorsqu'on utilise un type paramétré comme Comparable<T>, le T est remplacé par Object via le type-erasure.
    [*] A l'exécution la JVM va vérifier le type réel de l'instance pour déterminer la méthode à utiliser (cas des classes filles).
    Dans le premier cas il appelle donc la méthode Bidule.compareTo(Bidule).

    Et idem dans le second cas, sauf qu'il va rechercher ce coup-ci la méthode Comparable.compareTo(Object).
    Tu me diras que cette méthode n'existe pas dans Bidule... mais en fait si !
    Lorsque tu implémentes une classe/interface paramétré, le compilateur va générer une méthode avec le type-erasure pour assurer la compatibilité. Grosso-modo c'est comme si tu avais écrit ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    	@Override
    	public int compareTo(Bidule o) {
    		// ...
    	}
     
    	// Méthode générée par le compilateur :
    	public int compareTo(Object o) {
    		return compareTo((Bidule)o);
    	}

    a++

  8. #8
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    Merci adiguba, mon Franc (euro?) est tombé. J'avais complètement oublié la partie "le compilateur génère deux copie de la méthode, une avec le type réel déclarer et une compatible avec l'interface", j'arrive a remettre les pièce en place et j'avais pas tilté qu'il y avait un appel explicite à l'interface HistoricData.compareTo dans bytecode (avais oublié qu'on précisait dans le bytecode, la méthode ET la classe déclarée, et pas seulement la méthode).

    Merci à tous

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

Discussions similaires

  1. java.lang.NoSuchMethodError avec reflection
    Par Thibault.B dans le forum Websphere
    Réponses: 5
    Dernier message: 03/09/2009, 17h58
  2. requete sql avec between sur des champs de type Date
    Par ersoufiane dans le forum Langage SQL
    Réponses: 2
    Dernier message: 02/08/2006, 19h43
  3. java.lang.NoSuchMethodError: main
    Par lunart dans le forum Eclipse Java
    Réponses: 7
    Dernier message: 21/04/2006, 16h12
  4. [java.lang] Object/String --> compareTo() ou equals()
    Par wdionysos dans le forum Langage
    Réponses: 17
    Dernier message: 17/01/2006, 23h41
  5. Réponses: 7
    Dernier message: 09/12/2005, 23h26

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