2017-09-18 4 views
6

Pourquoi la distribution non sécurisée (T) est-elle nécessaire dans cette interface générique? Si T est comparable à lui-même, c'est-à-dire qu'il met en œuvre ExtendedComparable<super of T>, ce qui signifie également ExtendedComparable<T>, alors pourquoi l'effacement de type nécessite-t-il ExtendedComparable<T> pour être transtypé en T?Comment éviter les conversions non contrôlées dans une interface Comparable étendu de Java Generics?

/* @param <T> T must be comparable to itself or any of its superclass 
* (comparables are consumers, thus acc. to the PECS principle 
* = producer-extends,consumer-super we use the bounded wildcard type "super") 
*/ 
public interface ExtendedComparable<T extends ExtendedComparable<? super T>> { 
    Comparator<? super T> getComparator(); 
    default boolean greaterThen(T toCompare) { 
     return getComparator().compare((T) this, toCompare) > 0; 
    } 
} 
+0

Ce message peut être de votre aide: https://stackoverflow.com/a/25783345/4867374 –

Répondre

5

Parce qu'il n'y a aucune garantie que this est en fait une instance de classe T ou étendent même pas.

Par exemple, considérons ceci:

public class T0 implements ExtendComparable<T0> {...} 
public class T1 implements ExtendComparable<T0> {...} 

En T0 respecte bien comme elle est conforme à la limite: T0 extends ExtendComparable<T0> et T0 est super de T0. Dans ce cas this est une instance de T0 ici donc vous allez bien; la distribution (T)this (donc (T0)this) a du sens.

Avec T1 la déclaration est correcte aussi parce que la limite est appliquée à T0 pas T1, T est substitué T0. Cependant this est T1 et T1 n'est pas super ni un enfant de T0. Oui, les deux implémentent ExtendedCompatible<T0>, mais vous ne pouvez pas convertir entre frères et sœurs. Par exemple Integer et Double extend Number mais (Integer) new Double(0.0) échoue. De même, le cast (T) converti en (T0) échoue.

L'hypothèse que vous faites est que T va être définie à la même que la classe qui a été déclarée et actuellement il n'y a aucun moyen de forcer ces sémantiques. J'espère que cela changera à un moment donné dans les futures versions du langage Java, mais il y a peut-être une raison réelle pour laquelle le "task force" du langage Java évite de le faire.

Il existe un moyen d'éviter totalement la conversion, mais il est préférable de créer ExtendedCompatible une classe abstraite plutôt qu'une interface.

Vous pouvez déclarer un dernier champ de type T dont la valeur serait fixée par un constructeur protégé par classe d'extension qui à son tour doit passer this sa valeur:

public abstract class ExtendedCompatible<T extends ExtendedCompatible<? super T>> { 
    private final T thiz; 

    protected ExtendedCompatible(final T thiz) { 
    if (this != thiz) throw new IllegalArgumentException("you must pass yourself"); 
    this.thiz = thiz; 
    } 
    ... 

    public class MyExtendedCompatible extends ExtendedCompatible<MyExtendedCompatible> { 
    public MyExtendedCompatible() { 
      super(this); 
    } 
    } 

Le prix que vous payez est la mémoire supplémentaire la consommation d'avoir une référence idiote à lui-même et la charge de code/CPU supplémentaire de passer this au constructeur parent.

Une autre solution serait de déclarer une méthode abstraite pour obtenir le T (ce):

// Parent abstract class: 
    protected abstract T getThiz(); 
// Child class... for each class: 
    protected MyChildClass getThiz() { return this; } 
+1

c'est une réponse très détaillée et bonne IMO. Le débordement de pile a la réputation de récompenser parfois seulement les utilisateurs les mieux notés, mais c'est un très bon. +1 – Eugene

+0

Merci. C'est vrai.Nous voulions que l'interface soit utilisée pour beaucoup d'enums. Mais, malheureusement, le travail proposé ne convient pas aux énumérations, car elles ne supportent pas l'héritage et ne peuvent donc pas être dérivées d'une classe abstraite. Le second travail n'est pas tout à fait réalisable, car chaque méthode de l'interface devrait vérifier getThiz()! = Ceci (en Java 9, au moins tous ces contrôles identiques pourraient être refactorisés en une méthode d'interface privée) et chaque énumération besoin d'implémenter getThiz(). – ms34449

+0

@ ms34449 Qu'en est-il des enums? Je pense que vous pouvez remplacer les méthodes par constante en incluant un corps dans leur déclaration. EDIT ah! vous voulez dire à travers les enums pas à travers les constantes. –

0

Merci. Valentin a raison. Même si les deux types implémentent la même interface, cela ne fait pas et ne devrait pas faire fonctionner la distribution entre eux. Et oui, il n'y a pas de mécanisme en Java pour imposer le passage dans T la même classe que la classe déclarée.

+2

cela devrait être un commentaire en réponse à Valentin réponse - et vous devriez accepter son probablement – Eugene