Java: Instanceof und Generics

135

Bevor ich meine generische Datenstruktur nach dem Index eines Werts durchsuche, möchte ich sehen, ob es sich überhaupt um eine Instanz des Typs handelt, thisfür den parametrisiert wurde.

Aber Eclipse beschwert sich, wenn ich das tue:

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

Dies ist die Fehlermeldung:

Instanz der Prüfung für Typparameter E kann nicht durchgeführt werden. Verwenden Sie stattdessen das Löschobjekt, da generische Typinformationen zur Laufzeit gelöscht werden

Was ist der bessere Weg, um es zu tun?

Nick Heiner
quelle

Antworten:

79

Die Fehlermeldung sagt alles. Zur Laufzeit ist der Typ weg, es gibt keine Möglichkeit, ihn zu überprüfen.

Sie könnten es fangen, indem Sie eine Fabrik für Ihr Objekt wie folgt erstellen:

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

Und dann speichern Sie im Konstruktor des Objekts diesen Typ, der so variabel ist, dass Ihre Methode folgendermaßen aussehen könnte:

        if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass()))
        {
            return -1;
        }
Yishai
quelle
3
Ich glaube nicht, dass du willst Class.isAssignableFrom.
Tom Hawtin - Tackline
@ Tom, ich habe diese letzte Nacht aus dem Gedächtnis geschrieben und sie repariert, um die Klasse tatsächlich zu bestehen (duh!), Aber ansonsten verstehe ich nicht, warum du sie nicht willst (vielleicht brauche ich heute Morgen mehr Kaffee, ich ' Ich bin nur bei meiner ersten Tasse).
Yishai
Ich bin bei Tom. Könnten Sie das klarstellen? Die Verwendung von isAssignableFrom () wäre meine Wahl für den Job gewesen. Vielleicht fehlt mir etwas?
Luis.espinal
6
@luis, Toms Kommentar bedeutete wahrscheinlich, dass ich isInstance () verwenden und den tatsächlichen arg0-Parameter übergeben sollte. Dies hat den Vorteil, dass die Nullprüfung vermieden wird.
Yishai
1
Ich habe eine ähnliche Situation, aber ich kann die Antwort nicht vollständig verstehen. Kannst du es bitte besser für Dummy wie mich klären?
Rubens Mariuzzo
40

Zwei Optionen für die Überprüfung des Laufzeittyps mit Generika:

Option 1 - Beschädigen Sie Ihren Konstruktor

Nehmen wir an, Sie überschreiben indexOf (...) und möchten den Typ nur auf Leistung überprüfen, um sich die Iteration der gesamten Sammlung zu ersparen.

Machen Sie einen schmutzigen Konstruktor wie folgt:

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

    this.t = t;
}

Anschließend können Sie mit isAssignableFrom den Typ überprüfen.

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

Jedes Mal, wenn Sie Ihr Objekt instanziieren, müssen Sie sich wiederholen:

new MyCollection<Apples>(Apples.class);

Sie könnten entscheiden, dass es das nicht wert ist. Bei der Implementierung von ArrayList.indexOf (...) wird nicht überprüft, ob der Typ übereinstimmt.

Option 2 - Lass es scheitern

Wenn Sie eine abstrakte Methode verwenden müssen, die Ihren unbekannten Typ erfordert, möchten Sie nur, dass der Compiler aufhört, über instanceof zu weinen . Wenn Sie eine Methode wie diese haben:

protected abstract void abstractMethod(T element);

Sie können es so verwenden:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

Sie wandeln das Objekt in T (Ihren generischen Typ) um, nur um den Compiler zu täuschen. Ihre Besetzung führt zur Laufzeit nichts aus , aber Sie erhalten trotzdem eine ClassCastException, wenn Sie versuchen, den falschen Objekttyp an Ihre abstrakte Methode zu übergeben.

HINWEIS 1: Wenn Sie in Ihrer abstrakten Methode zusätzliche ungeprüfte Casts ausführen, werden Ihre ClassCastExceptions hier abgefangen. Das könnte gut oder schlecht sein, also denke darüber nach.

HINWEIS 2: Sie erhalten eine kostenlose Nullprüfung, wenn Sie instanceof verwenden . Da Sie es nicht verwenden können, müssen Sie möglicherweise mit bloßen Händen nach Null suchen.

SharkAlley
quelle
18

Alter Beitrag, aber eine einfache Möglichkeit, eine generische Instanz der Überprüfung durchzuführen.

public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
    return clazz.isInstance(targetClass);
}
Jonas Pedersen
quelle
21
Es ist unklar, was Sie hier tatsächlich beitragen.
Andrew
12

Vorausgesetzt, Ihre Klasse erweitert eine Klasse mit einem generischen Parameter, können Sie diesen auch zur Laufzeit über Reflection abrufen und dann zum Vergleich verwenden, d. H.

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

Im obigen Fall erhalten Sie zur Laufzeit String.class von getParameterizedClass () und es wird zwischengespeichert, sodass Sie bei mehreren Überprüfungen keinen Reflexionsaufwand erhalten. Beachten Sie, dass Sie die anderen parametrisierten Typen per Index über die Methode ParameterizedType.getActualTypeArguments () abrufen können.

Terryscotttaylor
quelle
7

Ich hatte das gleiche Problem und hier ist meine Lösung (sehr bescheiden, @george: diesmal kompilieren UND arbeiten ...).

Mein Problem befand sich in einer abstrakten Klasse, die Observer implementiert. Das Update der Observable-Feuermethode (...) mit der Object-Klasse, die eine beliebige Art von Objekt sein kann.

Ich möchte nur Objekte vom Typ T behandeln

Die Lösung besteht darin, die Klasse an den Konstruktor zu übergeben, um zur Laufzeit Typen vergleichen zu können.

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

}

Für die Implementierung müssen wir nur die Klasse an den Konstruktor übergeben

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

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

Oder Sie könnten einen fehlgeschlagenen Versuch abfangen, in E zu zaubern, z.

public int indexOf(Object arg0){
  try{
    E test=(E)arg0;
    return doStuff(test);
  }catch(ClassCastException e){
    return -1;
  }
}
ben
quelle
1
+1 Eine pragmatische, nicht überentwickelte Lösung für eine einfache Überprüfung! Ich wünschte, ich könnte dies mehr
abstimmen
24
Das würde nicht funktionieren. E wird zur Laufzeit gelöscht, damit die Umwandlung nicht fehlschlägt. Sie erhalten lediglich eine Compiler-Warnung.
Yishai
1

Technisch gesehen sollten Sie das nicht müssen, das ist der Punkt von Generika, damit Sie die Überprüfung des Kompiliertyps durchführen können:

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

Aber dann kann der @Override ein Problem sein, wenn Sie eine Klassenhierarchie haben. Ansonsten siehe Yishais Antwort.

Jason S.
quelle
Ja, die List-Schnittstelle verlangt, dass die Funktion einen Objektparameter annimmt.
Nick Heiner
Sie implementieren List? Warum implementieren Sie List <E> nicht?
Jason S
(Siehe zum Beispiel die Deklaration von ArrayList: java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html )
Jason S
@Rosarch: Die indexOf () -Methode der List-Schnittstelle erfordert nicht, dass das angegebene Argument vom selben Typ ist wie das gesuchte Objekt. es muss nur .equals () sein, und Objekte verschiedener Typen können .equals () zueinander sein. Dies ist das gleiche Problem wie für die remove () -Methode, siehe: stackoverflow.com/questions/104799/…
newacct
1
[[[verlegen]]] egal, ich dachte, indexOf () benötigt E als Parameter und nicht Object. (Warum haben sie das getan ?! ??!)
Jason S
1

Der Laufzeittyp des Objekts ist eine relativ willkürliche Bedingung zum Filtern. Ich schlage vor, solchen Dreck von Ihrer Sammlung fernzuhalten. Dies wird einfach dadurch erreicht, dass Ihre Sammlung an einen Filter delegiert wird, der in einer Konstruktion übergeben wird.

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 - Tackline
quelle
(Obwohl Sie möglicherweise eine Überprüfung des Comparator
Instanztyps durchführen