Когда дженерики были добавлены в 1.5, java.lang.reflect добавил интерфейс Type с различными подтипами для представления типов. Class модифицирован для реализации Type для типов до 1.5. Type подтипы доступны для новых типов универсального типа от 1,5.
Это все хорошо. Немного неловко, так как Type должен быть опущен, чтобы сделать что-нибудь полезное, но выполнимое с пробным, ошибочным, изменчивым и (автоматическим) тестированием. За исключением случаев, когда речь идет о реализации...
Как должно быть реализовано equals и hashCode. Описание API для подтипа ParameterizedType Type говорит:
Экземпляры классов, которые реализуют этот интерфейс, должны реализовывать метод equals(), который приравнивает любые два экземпляра, которые совместно используют одно и то же объявление универсального типа и имеют одинаковые параметры типа.
(Я предполагаю, что это означает getActualTypeArguments и getRawType но не getOwnerType??)
Из общего контракта java.lang.Object мы знаем, что hashCode также должен быть реализован, но, похоже, не существует спецификации относительно того, какие значения должен генерировать этот метод.
Ни один из других подтипов Type видимому, не упоминает equals или hashCode, за исключением того, что у Class есть отдельные экземпляры для каждого значения.
Так, что я помещаю в мои equals и hashCode?
(В случае, если вам интересно, я TypeVariable<?> заменить параметры типа для реальных типов. Поэтому, если я знаю, что во время выполнения TypeVariable<?> T равен Class<?> String тогда я хочу заменить Type s, поэтому List<T> становится List<String>, T[] становится String[], List<T>[] (может случиться!) Становится List<String>[] и т.д.)
Или я должен создать свою собственную параллельную иерархию типов типов (без дублирования Type по предполагаемым юридическим причинам)? (Есть ли библиотека?)
Изменить: было несколько вопросов о том, почему мне это нужно. Действительно, зачем вообще смотреть на информацию общего типа?
Я начинаю с неуниверсального типа класса/интерфейса. (Если вам нужны параметризованные типы, такие как List<String> тогда вы всегда можете добавить слой косвенности с новым классом.) Затем я следую за полями или методами. Они могут ссылаться на параметризованные типы. Пока они не используют подстановочные знаки, я все еще могу определять реальные статические типы, когда сталкиваюсь с подобными T
Таким образом, я могу делать все с помощью качественной статической печати. Ни один из этих instanceof динамических проверок типов не виден.
Конкретное использование в моем случае это сериализация. Но это может относиться к любому другому разумному использованию отражения, например, к тестированию.
Текущее состояние кода, который я использую для замены ниже. typeMap - это Map<String,Type>. Представлено как снимок "как есть". В любом случае не прибрано (throw null; если не веришь мне).
Type substitute(Type type) {
if (type instanceof TypeVariable<?>) {
Type actualType = typeMap.get(((TypeVariable<?>)type).getName());
if (actualType instanceof TypeVariable<?>) { throw null; }
if (actualType == null) {
throw new IllegalArgumentException("Type variable not found");
} else if (actualType instanceof TypeVariable<?>) {
throw new IllegalArgumentException("TypeVariable shouldn't substitute for a TypeVariable");
} else {
return actualType;
}
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType)type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
int len = actualTypeArguments.length;
Type[] actualActualTypeArguments = new Type[len];
for (int i=0; i<len; ++i) {
actualActualTypeArguments[i] = substitute(actualTypeArguments[i]);
}
// This will always be a Class, wont it? No higher-kinded types here, thank you very much.
Type actualRawType = substitute(parameterizedType.getRawType());
Type actualOwnerType = substitute(parameterizedType.getOwnerType());
return new ParameterizedType() {
public Type[] getActualTypeArguments() {
return actualActualTypeArguments.clone();
}
public Type getRawType() {
return actualRawType;
}
public Type getOwnerType() {
return actualOwnerType;
}
// Interface description requires equals method.
@Override public boolean equals(Object obj) {
if (!(obj instanceof ParameterizedType)) {
return false;
}
ParameterizedType other = (ParameterizedType)obj;
return
Arrays.equals(this.getActualTypeArguments(), other.getActualTypeArguments()) &&
this.getOwnerType().equals(other.getOwnerType()) &&
this.getRawType().equals(other.getRawType());
}
};
} else if (type instanceof GenericArrayType) {
GenericArrayType genericArrayType = (GenericArrayType)type;
Type componentType = genericArrayType.getGenericComponentType();
Type actualComponentType = substitute(componentType);
if (actualComponentType instanceof TypeVariable<?>) { throw null; }
return new GenericArrayType() {
// !! getTypeName? toString? equals? hashCode?
public Type getGenericComponentType() {
return actualComponentType;
}
// Apparently don't have to provide an equals, but we do need to.
@Override public boolean equals(Object obj) {
if (!(obj instanceof GenericArrayType)) {
return false;
}
GenericArrayType other = (GenericArrayType)obj;
return
this.getGenericComponentType().equals(other.getGenericComponentType());
}
};
} else {
return type;
}
}