Java: Instanceof e Generics


135

Prima di cercare nella mia struttura di dati generica l'indice di un valore, vorrei vedere se thisè stata parametrizzata anche un'istanza del tipo .

Ma Eclipse si lamenta quando faccio questo:

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

Questo è il messaggio di errore:

Impossibile eseguire il controllo dell'istanza del parametro di tipo E. Utilizzare invece l'oggetto di cancellazione poiché le informazioni di tipo generico verranno cancellate in fase di esecuzione

Qual è il modo migliore per farlo?

Risposte:


79

Il messaggio di errore dice tutto. In fase di esecuzione, il tipo è sparito, non c'è modo di verificarlo.

Potresti prenderlo creando una fabbrica per il tuo oggetto in questo modo:

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

E poi nell'archivio costruttore dell'oggetto quel tipo, così variabile in modo che il tuo metodo possa apparire così:

        if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass()))
        {
            return -1;
        }

3
Non penso che tu voglia Class.isAssignableFrom.
Tom Hawtin - tackline

@Tom, l'ho scritto ieri sera a memoria e l'ho corretto per passare effettivamente la lezione (duh!) Ma per il resto, non capisco perché non lo vorresti (forse ho bisogno di più caffè stamattina, io ' m solo sulla mia prima tazza).
Yishai,

Sono con Tom. Potresti chiarirlo? L'uso di isAssignableFrom () sarebbe stato la mia scelta per il lavoro. Forse mi manca qualcosa?
luis.espinal,

6
@luis, il commento di Tom probabilmente significa che dovrei usare isInstance () e passare il parametro arg0 effettivo. Ha il vantaggio di evitare il controllo null.
Yishai,

1
Ho una situazione simile ma non riesco a comprendere appieno la risposta. Potete per favore chiarire meglio per il manichino come me?
Rubens Mariuzzo

40

Due opzioni per il controllo del tipo di runtime con generics:

Opzione 1: danneggia il costruttore

Supponiamo che tu abbia la precedenza su indexOf (...) e che desideri controllare il tipo solo per le prestazioni, per risparmiare te stesso iterando l'intera raccolta.

Crea un costruttore sporco come questo:

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

    this.t = t;
}

Quindi è possibile utilizzare isAssignableFrom per verificare il tipo.

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

Ogni volta che crei un'istanza del tuo oggetto, dovresti ripetere te stesso:

new MyCollection<Apples>(Apples.class);

Potresti decidere che non ne vale la pena. Nell'implementazione di ArrayList.indexOf (...) , non controllano che il tipo corrisponda.

Opzione 2 - Lascia che fallisca

Se devi usare un metodo astratto che richiede il tuo tipo sconosciuto, allora tutto ciò che vuoi davvero è che il compilatore smetta di piangere su instanceof . Se hai un metodo come questo:

protected abstract void abstractMethod(T element);

Puoi usarlo in questo modo:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

Stai lanciando l'oggetto su T (il tuo tipo generico), solo per ingannare il compilatore. Il tuo cast non fa nulla in fase di esecuzione , ma otterrai comunque ClassCastException quando proverai a passare il tipo di oggetto sbagliato nel tuo metodo astratto.

NOTA 1: se stai eseguendo ulteriori cast non controllati nel tuo metodo astratto, le ClassCastExceptions verranno catturate qui. Potrebbe essere buono o cattivo, quindi pensaci bene.

NOTA 2: si ottiene un controllo null gratuito quando si utilizza instanceof . Poiché non è possibile utilizzarlo, potrebbe essere necessario verificare la presenza di null a mani nude.


18

Vecchio post, ma un modo semplice per fare istanza generica di controllo.

public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
    return clazz.isInstance(targetClass);
}

21
Non è chiaro a cosa stai effettivamente contribuendo qui.
Andrew,

12

Se la tua classe estende una classe con un parametro generico, puoi anche ottenerla in fase di esecuzione tramite reflection e quindi utilizzarla per il confronto, ad es.

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;
    }
}

Nel caso sopra, in fase di esecuzione otterrai String.class da getParameterizedClass () e si memorizza nella cache in modo da non ottenere alcun overhead di riflessione su più controlli. Si noti che è possibile ottenere gli altri tipi con parametri per indice dal metodo ParameterizedType.getActualTypeArguments ().


7

Ho avuto lo stesso problema ed ecco la mia soluzione (molto umile, @george: questa volta compilando E funzionando ...).

Il mio probem era all'interno di una classe astratta che implementa Observer. Il metodo Observable genera l'aggiornamento (...) con la classe Object che può essere qualsiasi tipo di Object.

Voglio solo gestire oggetti di tipo T.

La soluzione è passare la classe al costruttore per poter confrontare i tipi in fase di esecuzione.

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);

}

Per l'implementazione non ci resta che passare la Classe al costruttore

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

  @Override
  public void foo(Rule t){
  }
}

5

Oppure potresti cogliere un tentativo fallito di lanciare in E es.

public int indexOf(Object arg0){
  try{
    E test=(E)arg0;
    return doStuff(test);
  }catch(ClassCastException e){
    return -1;
  }
}

1
+1 Una soluzione pragmatica non ingegnerizzata per un semplice controllo! Vorrei poter votare di più
higuaro,

24
Questo non funzionerebbe. E viene cancellato in fase di esecuzione, quindi il cast non fallirà, riceverai solo un avviso del compilatore a riguardo.
Yishai,

1

Tecnicamente non dovresti, è questo il punto dei generici, quindi puoi fare il controllo del tipo di compilazione:

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

ma allora @Override potrebbe essere un problema se si dispone di una gerarchia di classi. Altrimenti vedi la risposta di Yishai.


Sì, l'interfaccia Elenco richiede che la funzione prenda un parametro oggetto.
Nick Heiner,

stai implementando Elenco? Perché non stai implementando Elenco <E>?
Jason S,

(vedere ad esempio la dichiarazione di ArrayList: java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html )
Jason S,

@Rosarch: il metodo indexOf () dell'interfaccia List non richiede che l'argomento fornito sia dello stesso tipo dell'oggetto che stai cercando. deve solo essere .equals () e oggetti di diverso tipo possono essere .equals () l'uno con l'altro. Questo è lo stesso problema come per il metodo remove (), vedi: stackoverflow.com/questions/104799/...
newacct

1
[[[imbarazzato]]] non importa, pensavo che indexOf () richiedesse E come parametro anziché oggetto. (perché l'hanno fatto?! ??!)
Jason S,

1

Il tipo di runtime dell'oggetto è una condizione relativamente arbitraria su cui filtrare. Suggerisco di tenere lontana tale confusione dalla tua collezione. Ciò si ottiene semplicemente facendo in modo che la raccolta venga delegata a un filtro passato in una costruzione.

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;
             }
         }}
     );

(Anche se potresti voler fare un tipo di istanza controlla se stai usando Comparatoro simili.)
Tom Hawtin - tackline
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.