Java: Instanceof i Generics

135

Zanim przejrzę moją ogólną strukturę danych pod kątem indeksu wartości, chciałbym sprawdzić, czy jest to nawet wystąpienie typu, thisdo którego został sparametryzowany.

Ale Eclipse narzeka, kiedy to robię:

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

Oto komunikat o błędzie:

Nie można wykonać sprawdzenia instancji względem parametru typu E. Zamiast tego użyj jego wymazywania Obiekt, ponieważ informacje o typie ogólnym zostaną usunięte w czasie wykonywania

Jaki jest lepszy sposób na zrobienie tego?

Nick Heiner
źródło

Odpowiedzi:

79

Komunikat o błędzie mówi wszystko. W czasie wykonywania tego typu nie ma, nie ma możliwości sprawdzenia go.

Możesz to złapać, tworząc fabrykę dla swojego obiektu w następujący sposób:

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

A następnie w konstruktorze obiektu zapisz ten typ, tak zmienny, aby Twoja metoda mogła wyglądać następująco:

        if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass()))
        {
            return -1;
        }
Yishai
źródło
3
Myślę, że nie chcesz Class.isAssignableFrom.
Tom Hawtin - tackline
@Tom, napisałem to wczoraj wieczorem z pamięci i naprawiłem to, aby faktycznie zdać zajęcia (duh!), Ale poza tym nie rozumiem, dlaczego byś tego nie chciał (może potrzebuję więcej kawy dziś rano, ja ' m tylko na mojej pierwszej filiżance).
Yishai
Jestem z Tomem. Czy mógłbyś to wyjaśnić? Użycie isAssignableFrom () byłoby moim wyborem do tego zadania. Może czegoś mi brakuje?
luis.espinal
6
@luis, komentarz Toma prawdopodobnie oznaczał, że powinienem użyć isInstance () i przekazać rzeczywisty parametr arg0. Ma tę zaletę, że pozwala uniknąć sprawdzenia zerowego.
Yishai
1
Mam podobną sytuację, ale nie mogę w pełni zrozumieć odpowiedzi. Czy możesz to lepiej wyjaśnić dla takiego manekina jak ja?
Rubens Mariuzzo
40

Dwie opcje sprawdzania typu w czasie wykonywania za pomocą typów ogólnych:

Opcja 1 - Zepsuć konstruktora

Załóżmy, że nadpisujesz indexOf (...) i chcesz sprawdzić typ tylko pod kątem wydajności, aby zaoszczędzić sobie iterowania całej kolekcji.

Stwórz brudnego konstruktora, takiego jak ten:

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

    this.t = t;
}

Następnie możesz użyć isAssignableFrom, aby sprawdzić typ.

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

Za każdym razem, gdy tworzysz instancję obiektu, musiałbyś się powtarzać:

new MyCollection<Apples>(Apples.class);

Możesz zdecydować, że to nie jest tego warte. W implementacji ArrayList.indexOf (...) nie sprawdzają, czy typ jest zgodny.

Opcja 2 - Niech to zawiedzie

Jeśli potrzebujesz metody abstrakcyjnej, która wymaga nieznanego typu, to wszystko, czego naprawdę chcesz, to aby kompilator przestał płakać z powodu instancji . Jeśli masz taką metodę:

protected abstract void abstractMethod(T element);

Możesz go używać w ten sposób:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

Rzucasz obiekt na T (twój typ ogólny), aby oszukać kompilator. Twoje rzutowanie nic nie robi w czasie wykonywania , ale nadal otrzymasz ClassCastException, gdy spróbujesz przekazać niewłaściwy typ obiektu do metody abstrakcyjnej.

UWAGA 1: Jeśli wykonujesz dodatkowe niezaznaczone rzuty w swojej metodzie abstrakcyjnej, Twoje ClassCastExceptions zostaną tutaj przechwycone. To może być dobre lub złe, więc przemyśl to.

UWAGA 2: Korzystając z instanceof . Ponieważ nie możesz go użyć, może być konieczne sprawdzenie wartości zerowej gołymi rękami.

SharkAlley
źródło
18

Stary post, ale prosty sposób na wykonanie ogólnego sprawdzenia instanceOf.

public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
    return clazz.isInstance(targetClass);
}
Jonas Pedersen
źródło
21
Nie jest jasne, co tak naprawdę tu publikujesz.
Andrew
12

Pod warunkiem, że Twoja klasa rozszerza klasę o parametr ogólny, możesz go również uzyskać w czasie wykonywania za pomocą odbicia, a następnie użyć tego do porównania, tj.

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

W powyższym przypadku w czasie wykonywania otrzymasz String.class z metody getParameterizedClass () i buforuje, aby nie uzyskać żadnego narzutu odbicia po wielu sprawdzeniach. Należy zauważyć, że można uzyskać inne sparametryzowane typy według indeksu z metody ParameterizedType.getActualTypeArguments ().

terryscotttaylor
źródło
7

Miałem ten sam problem i oto moje rozwiązanie (bardzo skromne, @george: tym razem kompiluję i działam ...).

Mój problem tkwił w klasie abstrakcyjnej, która implementuje Observer. Metoda Observable fires update (...) z klasą Object, która może być dowolnym rodzajem Object.

Chcę tylko obsługiwać obiekty typu T.

Rozwiązaniem jest przekazanie klasy do konstruktora, aby móc porównywać typy w czasie wykonywania.

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

}

Do implementacji wystarczy przekazać klasę do konstruktora

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

  @Override
  public void foo(Rule t){
  }
}
hsaturn
źródło
5

Możesz też złapać nieudaną próbę rzucenia w E, np.

public int indexOf(Object arg0){
  try{
    E test=(E)arg0;
    return doStuff(test);
  }catch(ClassCastException e){
    return -1;
  }
}
ben
źródło
1
+1 Pragmatyczne, niezbyt zaawansowane rozwiązanie do prostego sprawdzenia! Chciałbym móc głosować więcej
higuaro
24
To nie zadziała. E jest kasowane w czasie wykonywania, więc rzutowanie nie powiedzie się, po prostu otrzymasz ostrzeżenie kompilatora o tym.
Yishai,
1

Technicznie nie powinieneś tego robić, o to chodzi w przypadku typów ogólnych, więc możesz sprawdzić typ kompilacji:

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

ale wtedy @Override może być problemem, jeśli masz hierarchię klas. W przeciwnym razie zobacz odpowiedź Yishai.

Jason S.
źródło
tak, interfejs List wymaga, aby funkcja przyjęła parametr obiektu.
Nick Heiner
wdrażasz List? Dlaczego nie wdrażasz List <E>?
Jason S
(patrz na przykład deklaracja ArrayList: java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html )
Jason S
@Rosarch: metoda indexOf () interfejsu List nie wymaga, aby podany argument był tego samego typu co obiekt, którego szukasz. po prostu musi być .equals (), a obiekty różnych typów mogą być względem siebie .equals (). To jest ten sam problem, co w przypadku metody remove (), patrz: stackoverflow.com/questions/104799/…
newacct
1
[[[nieśmiało]]] nieważne, pomyślałem, że indexOf () wymaga E jako parametru, a nie Object. (dlaczego to zrobili?! ??!)
Jason S
1

Typ środowiska wykonawczego obiektu jest względnie dowolnym warunkiem do filtrowania. Proponuję trzymać takie zabrudzenie z dala od swojej kolekcji. Osiąga się to po prostu poprzez przekazanie delegata kolekcji do filtra w konstrukcji.

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 - haczyk
źródło
(Chociaż możesz chcieć sprawdzić typ instancji, jeśli używasz Comparatorlub podobnego.)
Tom Hawtin - tackline