Java: Instanceof et génériques

135

Avant de parcourir ma structure de données générique pour l'index d'une valeur, j'aimerais voir s'il s'agit même d'une instance du type this lequel a été paramétrée.

Mais Eclipse se plaint quand je fais cela:

@Override
public int indexOf(Object arg0) {
    if (!(arg0 instanceof E)) {
        return -1;
    }

Voici le message d'erreur:

Impossible d'effectuer une vérification instanceof par rapport au paramètre de type E. Utilisez à la place son objet d'effacement car les informations de type générique seront effacées à l'exécution

Quelle est la meilleure façon de procéder?

Nick Heiner
la source

Réponses:

79

Le message d'erreur dit tout. Au moment de l'exécution, le type a disparu, il n'y a aucun moyen de le vérifier.

Vous pouvez l'attraper en créant une usine pour votre objet comme ceci:

 public static <T> MyObject<T> createMyObject(Class<T> type) {
    return new MyObject<T>(type);
 }

Et puis dans le constructeur de l'objet stocker ce type, donc variable pour que votre méthode puisse ressembler à ceci:

        if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass()))
        {
            return -1;
        }
Yishai
la source
3
Je ne pense pas que tu veuilles Class.isAssignableFrom.
Tom Hawtin - tackline
@Tom, j'ai écrit ceci hier soir de mémoire, et je l'ai corrigé pour réussir le cours (duh!) Mais sinon, je ne comprends pas pourquoi vous ne le voudriez pas (peut-être que j'ai besoin de plus de café ce matin, je '' m seulement sur ma première tasse).
Yishai
Je suis avec Tom. Pourriez-vous clarifier cela? Utiliser isAssignableFrom () aurait été mon choix pour le travail. Peut-être que je manque quelque chose?
luis.espinal
6
@luis, le commentaire de Tom signifiait probablement que je devrais utiliser isInstance () et passer le paramètre arg0 réel. Il a l'avantage d'éviter le contrôle nul.
Yishai
1
J'ai une situation similaire mais je ne comprends pas entièrement la réponse. Pouvez-vous s'il vous plaît mieux le clarifier pour un mannequin comme moi?
Rubens Mariuzzo
40

Deux options pour la vérification du type d'exécution avec des génériques:

Option 1 - corrompez votre constructeur

Supposons que vous surchargez indexOf (...), et que vous souhaitez vérifier le type uniquement pour les performances, pour vous éviter d'itérer toute la collection.

Créez un constructeur sale comme celui-ci:

public MyCollection<T>(Class<T> t) {

    this.t = t;
}

Ensuite, vous pouvez utiliser isAssignableFrom pour vérifier le type.

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

Chaque fois que vous instanciez votre objet, vous devrez vous répéter:

new MyCollection<Apples>(Apples.class);

Vous pourriez décider que cela n'en vaut pas la peine. Dans l'implémentation de ArrayList.indexOf (...) , ils ne vérifient pas que le type correspond.

Option 2 - Laissez-le échouer

Si vous avez besoin d'utiliser une méthode abstraite qui nécessite votre type inconnu, alors tout ce que vous voulez vraiment, c'est que le compilateur arrête de pleurer sur instanceof . Si vous avez une méthode comme celle-ci:

protected abstract void abstractMethod(T element);

Vous pouvez l'utiliser comme ceci:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

Vous transtypez l'objet en T (votre type générique), juste pour tromper le compilateur. Votre distribution ne fait rien au moment de l'exécution , mais vous obtiendrez toujours une ClassCastException lorsque vous essayez de transmettre le mauvais type d'objet dans votre méthode abstraite.

REMARQUE 1: si vous effectuez des transtypages non vérifiés supplémentaires dans votre méthode abstraite, vos ClassCastExceptions seront interceptées ici. Cela pourrait être bon ou mauvais, alors réfléchissez-y.

REMARQUE 2: vous obtenez une vérification nulle gratuite lorsque vous utilisez instanceof . Puisque vous ne pouvez pas l'utiliser, vous devrez peut-être vérifier la valeur null à mains nues.

SharkAlley
la source
18

Ancien message, mais un moyen simple de faire une vérification générique d'instance.

public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
    return clazz.isInstance(targetClass);
}
Jonas Pedersen
la source
21
Ce que vous contribuez réellement ici n'est pas clair.
Andrew
12

Si votre classe étend une classe avec un paramètre générique, vous pouvez également l'obtenir au moment de l'exécution via la réflexion, puis l'utiliser pour la comparaison, c'est-à-dire

class YourClass extends SomeOtherClass<String>
{

   private Class<?> clazz;

   public Class<?> getParameterizedClass()
   {
      if(clazz == null)
      {
         ParameterizedType pt = (ParameterizedType)this.getClass().getGenericSuperclass();
          clazz = (Class<?>)pt.getActualTypeArguments()[0];
       }
       return clazz;
    }
}

Dans le cas ci-dessus, à l'exécution, vous obtiendrez String.class de getParameterizedClass (), et il se met en cache afin que vous n'obteniez aucune surcharge de réflexion lors de plusieurs vérifications. Notez que vous pouvez obtenir les autres types paramétrés par index à partir de la méthode ParameterizedType.getActualTypeArguments ().

terryscotttaylor
la source
7

J'ai eu le même problème et voici ma solution (très humble, @george: cette fois compiler ET travailler ...).

Mon problème était à l'intérieur d'une classe abstraite qui implémente Observer. La méthode Observable déclenche la mise à jour (...) avec la classe Object qui peut être n'importe quel type d'Object.

Je veux uniquement gérer des objets de type T

La solution est de passer la classe au constructeur afin de pouvoir comparer les types à l'exécution.

public abstract class AbstractOne<T> implements Observer {

  private Class<T> tClass;
    public AbstractOne(Class<T> clazz) {
    tClass = clazz;
  }

  @Override
  public void update(Observable o, Object arg) {
    if (tClass.isInstance(arg)) {
      // Here I am, arg has the type T
      foo((T) arg);
    }
  }

  public abstract foo(T t);

}

Pour l'implémentation, nous devons juste passer la classe au constructeur

public class OneImpl extends AbstractOne<Rule> {
  public OneImpl() {
    super(Rule.class);
  }

  @Override
  public void foo(Rule t){
  }
}
hsaturn
la source
5

Ou vous pourriez attraper une tentative ratée de lancer dans E par exemple.

public int indexOf(Object arg0){
  try{
    E test=(E)arg0;
    return doStuff(test);
  }catch(ClassCastException e){
    return -1;
  }
}
ben
la source
1
+1 Une solution pragmatique non sur-machinée pour un contrôle simple! J'aurais aimé pouvoir voter pour cela plus
higuaro
24
Cela ne marcherait pas. E est effacé au moment de l'exécution, donc le cast n'échouera pas, vous recevrez juste un avertissement du compilateur à ce sujet.
Yishai
1

Techniquement, vous ne devriez pas avoir à le faire, c'est le but des génériques, vous pouvez donc vérifier le type de compilation:

public int indexOf(E arg0) {
   ...
}

mais alors le @Override peut être un problème si vous avez une hiérarchie de classes. Sinon, voyez la réponse de Yishai.

Jason S
la source
ouais, l'interface List exige que la fonction prenne un paramètre d'objet.
Nick Heiner
vous implémentez List? Pourquoi n'implémentez-vous pas List <E>?
Jason S
(voir par exemple la déclaration de ArrayList: java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html )
Jason S
@Rosarch: la méthode indexOf () de l'interface List ne nécessite pas que l'argument donné soit du même type que l'objet recherché. il doit juste être .equals () pour lui, et les objets de différents types peuvent être .equals () les uns par rapport aux autres. C'est le même problème que pour la méthode remove (), voir: stackoverflow.com/questions/104799/…
newacct
1
[[[penaud]]] peu importe, je pensais que indexOf () exigeait E comme paramètre plutôt que Object. (pourquoi ont-ils fait ça?! ??!)
Jason S
1

Le type d'exécution de l'objet est une condition relativement arbitraire sur laquelle filtrer. Je suggère de garder cette saleté loin de votre collection. Ceci est simplement réalisé en ayant votre délégué de collection à un filtre passé dans une construction.

public interface FilterObject {
     boolean isAllowed(Object obj);
}

public class FilterOptimizedList<E> implements List<E> {
     private final FilterObject filter;
     ...
     public FilterOptimizedList(FilterObject filter) {
         if (filter == null) {
             throw NullPointerException();
         }
         this.filter = filter;
     }
     ...
     public int indexOf(Object obj) {
         if (!filter.isAllows(obj)) {
              return -1;
         }
         ...
     }
     ...
}

     final List<String> longStrs = new FilterOptimizedList<String>(
         new FilterObject() { public boolean isAllowed(Object obj) {
             if (obj == null) {
                 return true;
             } else if (obj instanceof String) {
                 String str = (String)str;
                 return str.length() > = 4;
             } else {
                 return false;
             }
         }}
     );
Tom Hawtin - Tacle
la source
(Bien que vous souhaitiez peut-être faire une vérification du type d'instance si vous utilisez Comparatorou similaire.)
Tom Hawtin - tackline