Java: Instanceof та Generics


135

Перш ніж переглянути свою загальну структуру даних щодо індексу значення, я хотів би дізнатися, чи не параметризовано навіть екземпляр типу this.

Але Eclipse скаржиться, коли я роблю це:

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

Це повідомлення про помилку:

Неможливо виконати примірник перевірки параметру типу E. Використовуйте замість цього об'єкт стирання, оскільки загальна інформація про тип буде стерта під час виконання

Який кращий спосіб це зробити?

Відповіді:


79

Повідомлення про помилку каже все це. Під час виконання тип не зник, немає можливості перевірити його.

Ви можете зловити його, зробивши завод для вашого об'єкта, як це:

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

І тоді в конструкторі об'єкта зберігається такий тип, такий змінний, щоб ваш метод міг виглядати так:

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

3
Я не думаю, що ти хочеш Class.isAssignableFrom.
Том Хоутін - тайклін

@Tom, я написав це вчора ввечері з пам'яті, і я зафіксував це, щоб насправді пройти клас (так!), Але в іншому випадку я не розумію, чому ви цього не хотіли (можливо, мені потрібна ще кава сьогодні вранці, я м лише на моїй першій чашці).
Yishai

Я з Томом. Чи можете ви це уточнити? Використання isAssignableFrom () було б моїм вибором для роботи. Можливо, мені чогось не вистачає?
luis.espinal

6
@luis, коментар Тома, ймовірно, означав, що я повинен використовувати isInstance () і передавати фактичний параметр arg0. Він має перевагу уникнути нульової перевірки.
Yishai

1
У мене схожа ситуація, але я не можу повністю зрозуміти відповідь. Ви можете, будь ласка, уточнити це краще для манекена, як я?
Рубенс Маріуццо

40

Два варіанти перевірки типу виконання за допомогою дженериків:

Варіант 1 - Пошкодити ваш конструктор

Припустимо, ви переосмислюєте indexOf (...), і ви хочете перевірити тип лише на продуктивність, щоб зберегти собі ітерацію всієї колекції.

Зробіть брудний конструктор так:

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

    this.t = t;
}

Тоді ви можете використовувати isAssignableFrom для перевірки типу.

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

Кожен раз, коли ви створюєте об'єкт, вам доведеться повторити:

new MyCollection<Apples>(Apples.class);

Ви можете вирішити, що це не варто. При реалізації ArrayList.indexOf (...) вони не перевіряють, чи відповідає тип.

Варіант 2 - Нехай це не вдасться

Якщо вам потрібно використовувати абстрактний метод, який вимагає вашого невідомого типу, тоді все, що ви дійсно хочете, - це компілятор перестав плакати про instanceof . Якщо у вас є такий метод:

protected abstract void abstractMethod(T element);

Ви можете використовувати його так:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

Ви кидаєте об'єкт на T (ваш загальний тип), просто щоб обдурити компілятор. Ваш актер нічого не робить під час виконання , але ви все одно отримаєте ClassCastException, коли спробуєте передати неправильний тип об'єкта у свій абстрактний метод.

ПРИМІТКА 1. Якщо ви робите додаткові неперевірені касти в абстрактному методі, ваші ClassCastExceptions потраплять сюди. Це може бути добре чи погано, тому продумайте.

ПРИМІТКА 2: Ви отримуєте безкоштовну нульову перевірку, коли використовуєте instanceof . Оскільки ви не можете використовувати його, можливо, вам доведеться перевірити наявність нуля голими руками.


18

Стара публікація, але простий спосіб зробити загальний екземпляр перевірки.

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

21
Незрозуміло, що ви насправді робите тут.
Андрій

12

За умови, що ваш клас розширює клас із загальним параметром, ви також можете отримати це під час виконання за допомогою відображення, а потім використовувати його для порівняння, тобто

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

У вищенаведеному випадку під час виконання ви отримаєте String.class від getParameterizedClass (), і він кешується, щоб ви не отримали жодних рефлексій під час декількох перевірок. Зауважте, що ви можете отримати інші параметризовані типи за індексом методом ParameterizedType.getActualTypeArguments ().


7

У мене була така ж проблема, і ось моє рішення (дуже скромно, @george: цього разу компілюючи І працюючи ...).

Моя проблема була всередині абстрактного класу, який реалізує Observer. Оновлення методу спостережуваних пожеж оновлено (...) з класом Object, який може бути будь-яким типом Object.

Я хочу лише обробляти об'єкти типу T

Рішення полягає в тому, щоб передати клас конструктору, щоб мати можливість порівнювати типи під час виконання.

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

}

Для реалізації нам просто потрібно передати клас конструктору

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

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

5

Або ви могли зловити невдалу спробу вкинути в E, наприклад.

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

1
+1 Прагматичне інженерне рішення для простої перевірки! Я хотів би, щоб я міг проголосувати за це більше
higuaro

24
Це не працює. E стирається під час виконання, тому акторський склад не вийде з ладу, ви просто отримаєте попередження про це компілятора.
Yishai

1

З технічної точки зору, вам це не повинно бути, в цьому полягає суть загальної інформації, тому ви можете робити перевірку типу компіляції:

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

але тоді @Override може бути проблемою, якщо у вас є ієрархія класів. В іншому випадку дивіться відповідь Yishai.


так, інтерфейс List вимагає, щоб функція приймала параметр об'єкта.
Нік Хайнер

Ви реалізуєте Список? Чому ви не реалізуєте Список <E>?
Jason S

(див., наприклад, декларацію ArrayList: java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html )
Jason S

@Rosarch: метод indexOf () інтерфейсу списку не вимагає, щоб наведений аргумент був того ж типу, що і об'єкт, який ви шукаєте. він просто повинен бути .equals (), а об'єкти різних типів можуть бути .equals () один одному. Це та ж проблема , як і для методу видалення (), см: stackoverflow.com/questions/104799 / ...
newacct

1
[[[по-собачому]]] Неважливо, я вважав, що indexOf () вимагає E як параметр, а не Об'єкт. (чому вони це зробили?! ??!)
Джейсон S

1

Тип виконання об'єкта є відносно довільною умовою для фільтрації. Я пропоную тримати таку лукавину подалі від своєї колекції. Це просто досягається, якщо ваш делегат колекції для фільтра передається в конструкції.

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

(Хоча ви, можливо, захочете перевірити тип екземпляра, якщо ви використовуєте Comparatorчи подібне.)
Том Хоутін - tackline
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.