Когда дженерики были добавлены в 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;
}
}