2009-10-15 21 views
101

Prima di esaminare la mia struttura di dati generici per l'indice di un valore, mi piacerebbe vedere se è stata parametrizzata anche un'istanza del tipo this.Java: Instanceof e Generics

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 instanceof controllare contro parametro di tipo E. Utilizzare invece la sua cancellazione oggetto in quanto le informazioni di tipo generico sarà essere cancellato a runtime

Qual è il modo migliore per farlo?

risposta

67

Il messaggio di errore dice tutto. A runtime, il tipo è sparito, non c'è modo di verificarlo.

Si poteva prenderlo facendo una fabbrica per l'oggetto in questo modo:

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

E poi nel dell'oggetto negozio costruttore di quel tipo, in modo variabile in modo che il metodo potrebbe essere la seguente:

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

Non penso che tu voglia 'Class.isAssignableFrom'. –

+0

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

+0

Sono con Tom. Potresti chiarirlo? Usare isAssignableFrom() sarebbe stata la mia scelta per il lavoro. Forse mi manca qualcosa? –

1

Tecnicamente non si dovrebbe avere per, questo è il punto di farmaci generici, in modo da poter fare la compilazione-controllo di tipo:

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

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

+0

sì, l'interfaccia Elenco richiede che la funzione acquisisca un parametro oggetto. –

+0

stai implementando List? Perché non stai implementando la Lista ? –

+0

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

1

Il tipo di runtime dell'oggetto è una condizione relativamente arbitraria da filtrare. Ti suggerisco di tenere lontana la tua pigrizia dalla tua collezione. Ciò si ottiene semplicemente facendo delegare la raccolta 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; 
      } 
     }} 
    ); 
+0

(Anche se si potrebbe voler fare un controllo di tipo istanza se si utilizza 'Comparator' o simile.) –

12

Purché la classe estende una classe con un parametro generico, è anche possibile ottenere questo in fase di esecuzione attraverso la riflessione, e quindi utilizzare tale per il confronto, cioè

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 di cui sopra, in fase di esecuzione riceverai String.class da getParameterizedClass() e memorizzerà le cache in modo da non ottenere alcun overhead di riflessione su più controlli. Si noti che è possibile ottenere gli altri tipi parametrizzati per indice dal metodo ParameterizedType.getActualTypeArguments().

4

Oppure è possibile rilevare un tentativo fallito di eseguire il cast 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 sovradimensionata per un semplice controllo! Vorrei poter votare più questo – higuaro

+22

Questo non avrebbe funzionato. E viene cancellato in fase di esecuzione, quindi il cast non fallirà, riceverai solo un avvertimento sul compilatore. – Yishai

30

Due opzioni per tipo di runtime verifica con i generici:

Opzione 1 - Corrupt tuo costruttore

Supponiamo modo si sostituisce indexOf (...), e si desidera controlla il tipo solo per le prestazioni, per salvarti ripetendo l'intera collezione.

Fai un costruttore di 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 si istanziare l'oggetto che avrebbe dovuto ripetersi:

new MyCollection<Apples>(Apples.class); 

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

Opzione 2 - Lasciare fallire

Se è necessario utilizzare un metodo astratto che richiede la vostra tipo sconosciuto, quindi tutto quello che vuole veramente è per il compilatore a smettere di piangere su instanceof. Se si dispone di un metodo come questo:

protected abstract void abstractMethod(T element); 

è possibile utilizzarlo in questo modo:

public int indexOf(Object o) { 

    try { 

     abstractMethod((T) o); 

    } catch (ClassCastException e) { 

//... 

si sta casting l'oggetto da T (il vostro tipo generico), giusto per ingannare il compilatore. Your cast does nothing at runtime, ma si otterrà comunque un ClassCastException quando si tenta di passare il tipo di oggetto errato nel metodo astratto.

NOTA 1: se si sta facendo ulteriori calchi incontrollati nel metodo astratto, i tuoi ClassCastExceptions saranno catturati qui. Potrebbe essere buono o cattivo, quindi pensaci.

NOTA 2: You get a free null check when you use instanceof. Dal momento che non è possibile utilizzarlo, potrebbe essere necessario verificare la presenza di null a mani nude.

7

Ho avuto lo stesso problema e qui è la mia soluzione (molto umile, @george: questa volta compilazione e di lavoro ...).

mio probem era dentro una classe astratta che implementa Observer. L'Observable incendi l'aggiornamento del metodo (...) con la classe Object che può essere qualsiasi tipo di oggetto.

voglio solo gestore di oggetti di tipo T

La soluzione è quella di passare la classe al costruttore, al fine di essere in grado di 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'attuazione non ci resta che passare alla classe al costruttore

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

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

Vecchio post, ma un modo semplice per fare la verifica instanceof generico.

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

Non è chiaro cosa stai effettivamente contribuendo qui. – Andrew