web-dev-qa-db-fra.com

Java Classe abstraite implémentant une interface avec des génériques

J'essaie de définir une classe abstraite implémentant Comparable. Quand je définis la classe avec la définition suivante:

public abstract class MyClass implements Comparable <MyClass>

les sous-classes doivent implémenter compareTo(MyClass object). Au lieu de cela, je veux que chaque sous-classe implémente compareTo(SubClass object), acceptant un objet de son propre type. Quand j'essaie de définir la classe abstraite avec quelque chose comme:

public abstract class MyClass implements Comparable <? extends MyClass>

Il se plaint que "Un sur-type ne peut spécifier aucun caractère générique."

Y a-t-il une solution?

63
Cem

C'est un peu trop verbeux à mon avis, mais ça marche:

public abstract class MyClass<T extends MyClass<T>> implements Comparable<T> {

}

public class SubClass extends MyClass<SubClass> {

    @Override
    public int compareTo(SubClass o) {
        // TODO Auto-generated method stub
        return 0;
    }

}
43
whiskeysierra

Mis à part les difficultés mécaniques que vous rencontrez pour déclarer les signatures, l'objectif n'a pas beaucoup de sens. Vous essayez d'établir une fonction de comparaison covariante, ce qui rompt toute l'idée d'établir une interface que les classes dérivées peuvent adapter.

Si vous définissez une sous-classe SubClass de telle sorte que ses instances ne peuvent être comparées qu'à d'autres instances SubClass, alors comment SubClass satisfait-il le contrat défini par MyClass? Rappelez-vous que MyClass dit que lui et tous les types qui en dérivent peuvent être comparés à d'autres instances MyClass. Vous essayez de rendre cela non vrai pour SubClass, ce qui signifie que SubClass ne satisfait pas le contrat de MyClass: vous ne pouvez pas remplacer SubClass par MyClass, car les exigences de SubClass sont plus strictes.

Ce problème est centré sur la covariance et la contravariance, et sur la façon dont elles permettent aux signatures de fonction de changer par dérivation de type. Vous pouvez assouplir une exigence sur le type d'un argument - en acceptant un type plus large que ne l'exige la signature du supertype - et vous pouvez renforcer une exigence sur un type de retour - promettant de renvoyer un type plus étroit que la signature du supertype. Chacune de ces libertés permet toujours une substitution parfaite du type dérivé au supertype; un appelant ne peut pas faire la différence lors de l'utilisation du type dérivé via l'interface du supertype, mais un appelant utilisant le type dérivé peut concrètement profiter de ces libertés.

réponse de Willi enseigne quelque chose sur les déclarations génériques, mais je vous invite à reconsidérer votre objectif avant d'accepter la technique au détriment de la sémantique.

18
seh

voir l'exemple de Java:

public abstract class Enum<E extends Enum<E>> implements Comparable<E>
    public final int compareTo(E o)

sur le commentaire de seh: généralement l'argument est correct. mais les génériques compliquent les relations de type. une sous-classe peut ne pas être un sous-type de MyClass dans la solution de Willi ....

SubClassA est un sous-type de MyClass<SubClassA>, mais pas un sous-type de MyClass<SubClassB>

type MyClass<X> définit un contrat pour compareTo(X) que tous ses sous-types doivent respecter. il n'y a pas de problème là-bas.

3
irreputable

Trouvé une autre solution:

  1. Définir une interface sur les champs qui composent le comaprable (par exemple ComparableFoo)
  2. Implémentez l'interface sur la classe parente
  3. Implémentez Comparable sur la classe parente.
  4. Écrivez votre implémentation.

La solution devrait ressembler à ceci:

public abstract class MyClass implements ComparableFoo,Comparable<ComparableFoo> {
    public int compareTo(ComparableFoo o) {
    // your implementation
    }
}

Cette solution implique que plus de choses pourraient implémenter ComparableFoo - ce n'est probablement pas le cas mais vous codez ensuite sur une interface et l'expression générique est simple.

1
David Levy

Je ne suis pas sûr que vous ayez besoin de la capture:

Tout d'abord, ajoutez le compareTo à la classe abstraite ...

public abstract class MyClass implements Comparable <MyClass> {

@Override
public int compareTo(MyClass c) {
...
}    
}

Ajoutez ensuite les implémentations ...

public class MyClass1 extends MyClass {
...
}

public class MyClass2 extends MyClass {
...
}

Appeler compare appellera la méthode super type ...

MyClass1 c1 = new MyClass1();
MyClass2 c2 = new MyClass2();

c1.compareTo(c2);
1
zevra0
public abstract class MyClass<T> implements Comparable<T> {

}

public class SubClass extends MyClass<SubClass> {

    @Override
    public int compareTo(SubClass o) {
        // TODO Auto-generated method stub
        return 0;
    }

}
1
newacct

Je sais que vous avez dit que vous vouliez "compareTo (objet SubClass), accepter un objet de son propre type", mais je suggère toujours de déclarer la classe abstraite comme ceci:

public abstract class MyClass implements Comparable <Object>

et effectuez une vérification instanceof lors de la substitution de compareTo dans MySubClass:

@Override
public int compareTo(Object o) {
    if (o instanceof MySubClass)) {
        ...
    }
    else throw new IllegalArgumentException(...)
}

de la même manière que "égal" ou "clone"

0
Caroline Even