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