Java: Instanceof и Generics
Прежде чем я просмотрю свою общую структуру данных для индекса значений, я хотел бы увидеть, был ли этот параметр экземпляром типа this
параметризирован.
Но Eclipse жалуется, когда я это делаю:
@Override
public int indexOf(Object arg0) {
if (!(arg0 instanceof E)) {
return -1;
}
Это сообщение об ошибке:
Невозможно выполнить проверку экземпляра с параметром типа E. Используйте вместо этого объект стирания, поскольку информация об универсальном типе будет удалена во время выполнения
Каков лучший способ сделать это?
Ответы
Ответ 1
В сообщении об ошибке говорится все. Во время выполнения тип ушел, нет возможности проверить его.
Вы можете поймать его, создав factory для вашего объекта следующим образом:
public static <T> MyObject<T> createMyObject(Class<T> type) {
return new MyObject<T>(type);
}
И затем в конструкторе объектов сохраните этот тип, поэтому переменная, чтобы ваш метод выглядел так:
if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass()))
{
return -1;
}
Ответ 2
Два варианта проверки типа времени выполнения с помощью дженериков:
Вариант 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: Вы получаете бесплатную нулевую проверку при использовании экземпляра. Поскольку вы не можете его использовать, вам может потребоваться проверить нулевые ваши голые руки.
Ответ 3
Если ваш класс расширяет класс с помощью общего параметра, вы также можете получить это во время выполнения через отражение, а затем использовать его для сравнения, т.е.
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().
Ответ 4
Старый пост, но простой способ выполнить проверку экземпляров instanceOf.
public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
return clazz.isInstance(targetClass);
}
Ответ 5
У меня была та же проблема, и вот мое решение (очень скромное, @george: на этот раз компиляция AND работает...).
Мой probem находился внутри абстрактного класса, который реализует Observer.
Метод Observable fires update (...) с классом 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){
}
}
Ответ 6
Или вы можете поймать неудачную попытку бросить в E , например.
public int indexOf(Object arg0){
try{
E test=(E)arg0;
return doStuff(test);
}catch(ClassCastException e){
return -1;
}
}
Ответ 7
Технически вам не нужно, чтобы точка дженериков, поэтому вы можете выполнить проверку типа компиляции:
public int indexOf(E arg0) {
...
}
но тогда @Override может быть проблемой, если у вас есть иерархия классов. В противном случае см. Ответ Yishai.
Ответ 8
Тип времени выполнения объекта является относительно произвольным условием фильтрации. Я предлагаю сохранить такую махинацию в вашей коллекции. Это просто достигается тем, что ваш делегат коллекции фильтруется в конструкцию.
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;
}
}}
);