У меня есть абстрактный класс с универсальными типами на уровне класса <O, P>
. В нем есть метод, который принимает O
и BiFunction<B, Class<B>, String>
. Для метода определено <B>
.
public interface ParamDescription<O, P> {
//...
<B> String getParamString(O obj, BiFunction<B, Class<B>, String> objectToString);
}
Затем у меня есть три класса, которые реализуют этот интерфейс по-разному, но все они имеют переменные Class<O>
и Class<P>
. У двух тоже есть несколько дополнительных обобщенных типов на уровне класса, и переменные Class<>
должны идти вместе с ними. Во всех них я пытаюсь вызвать этот метод objectToString, используя объект одного из универсальных типов уровня класса и связанную с ним переменную класса.
Вот самый простой:
Function<? super O, P> getter;
Class<P> paramClass;
public <B> String getParamString(O obj, BiFunction<B, Class<B>, String> objectToString) {
return objectToString.apply(getter.apply(obj), paramClass);
}
Однако, когда я пытаюсь скомпилировать его, я получаю следующее:
error: method apply in interface BiFunction<T,U,R> cannot be applied to given types;
return objectToString.apply(getter.apply(obj), paramClass);
^
required: B,Class<B>
found: P,Class<P>
reason: argument mismatch; P cannot be converted to B
where B,O,P,T,U,R are type-variables:
B extends Object declared in method <B>getParamString(O,BiFunction<B,Class<B>,String>)
O extends Object declared in class ParamDescriptionSingle
P extends Object declared in class ParamDescriptionSingle
T extends Object declared in interface BiFunction
U extends Object declared in interface BiFunction
R extends Object declared in interface BiFunction
В этом примере я мог бы решить эту проблему, изменив B
s на P
s. Однако это не работает для двух других реализаций. В двух других реализациях мне нужно использовать несколько универсальных типов, о которых интерфейс не знает.
Единственное реальное ограничение, которое у меня есть, это то, что параметр Class<B>
, предоставленный для BiFunction, должен соответствовать параметру B
, также заданному для него. По сути, «вот объект, а вот его тип».
Я пытался создать вспомогательную функцию, подобную этой
public <B> String getParamString(O obj, BiFunction<B, Class<B>, String> objectToString) {
return paramToString(obj, objectToString);
}
public String paramToString(O obj, BiFunction<P, Class<P>, String> objectToString) {
return objectToString.apply(getter.apply(obj), paramClass);
}
Но ему не понравилось кастинг на BiFunction.
Я также пробовал на самом деле кастовать так:
public <B> String getParamString(O obj, BiFunction<B, Class<B>, String> objectToString) {
return objectToString.apply((B)getter.apply(obj), (Class<B>)paramClass);
}
Но это дает неконтролируемые кастовые предупреждения для обоих приведений, и я не знаю достаточно, чтобы знать, действительно ли это безопасно или нет. Насколько я понимаю, это в основном просто бросило бы первое в Object, а второе - в Class
Является ли применение и подавление предупреждений подходом или есть лучший путь?
Для справки, вот мои две другие реализации:
//Class level: <O, E, P extends Collection<? extends E>>
Class<O> objClass;
Class<P> paramClass;
Class<E> entryClass;
Function<? super O, P> getter;
public <B> String getParamString(O obj, BiFunction<B, Class<B>, String> objectToString) {
P collection = getter.apply(obj);
if (collection == null) {
return objectToString.apply(collection, paramClass);
}
return collection.stream()
.map(e -> objectToString.apply(e, entryClass))
.collect(Collectors.toList())
.toString();
}
и
//Class level: <O, K, V, P extends Map<? extends K, ? extends V>>
Class<O> objClass;
Class<P> paramClass;
Class<K> keyClass;
Class<V> valueClass;
Function<? super O, P> getter;
public <B> String getParamString(O obj, BiFunction<B, Class<B>, String> objectToString) {
P map = getter.apply(obj);
if (map == null) {
return objectToString.apply(map, paramClass);
}
return map.entrySet()
.stream()
.collect(Collectors.toMap(e -> objectToString.apply(e.getKey(), keyClass),
e -> objectToString.apply(e.getValue(), valueClass)))
.toString();
}
Обновление 1 с большим количеством вещей, которые я пробовал, которые не работали:
Я попытался установить цель:
public <B> String getParamString(O obj, BiFunction<B, Class<B>, String> objectToString) {
return objectToString.<P>apply(getter.apply(obj), paramClass);
}
Но это все равно дает
required: B,Class<B>
found: P,Class<P>
reason: argument mismatch; P cannot be converted to B
Я попытался использовать подстановочный знак вместо B
:
public String getParamString(O obj, BiFunction<?, Class<?>, String> objectToString) {
return objectToString.apply(getter.apply(obj), paramClass);
}
с теми же результатами:
required: CAP#1,Class<?>
found: P,Class<P>
reason: argument mismatch; P cannot be converted to CAP#1
Похоже, что явное приведение и подавление предупреждений может быть единственным способом.
@SuppressWarnings("unchecked")
public <B> String getParamString(O obj, BiFunction<B, Class<B>, String> objectToString) {
return objectToString.apply((B)getter.apply(obj), (Class<B>)paramClass);
}
Это расстраивает, хотя, потому что я тогда теряю проверку во время компиляции, что предоставленный класс (2-й параметр) представляет класс 1-го параметра. Но он работает, и в противном случае он ведет себя правильно.
Обновление 2 с тем, что я пошел с
Так как приведение было единственным способом, которым я мог заставить его работать на самом деле, и это убирает проверку, упомянутую выше, я решил просто избавиться от универсального типа B
в целом.
Мой интерфейс имеет это:
String getParamString(O obj, BiFunction<Object, Class, String> objectToString);
и мои реализации выглядят так:
public String getParamString(O obj, BiFunction<Object, Class, String> objectToString) {
return objectToString.apply(getter.apply(obj), paramClass);
}
и
public String getParamString(O obj, BiFunction<Object, Class, String> objectToString) {
P collection = getter.apply(obj);
if (collection == null) {
return objectToString.apply(collection, paramClass);
}
return collection.stream()
.map(e -> objectToString.apply(e, entryClass))
.collect(Collectors.toList())
.toString();
}
и
public String getParamString(O obj, BiFunction<Object, Class, String> objectToString) {
P map = getter.apply(obj);
if (map == null) {
return objectToString.apply(map, paramClass);
}
return map.entrySet()
.stream()
.collect(Collectors.toMap(e -> objectToString.apply(e.getKey(), keyClass),
e -> objectToString.apply(e.getValue(), valueClass)))
.toString();
}
Затем при вызове этих функций я должен подавить предупреждения, но это из-за того, как я создаю бифункцию. Я создаю его, используя другой метод, который использует метод generic P
и Class<P>
, который приводится к Object
и raw Class
для BiFunction. Однако, на мой взгляд, это лучше, чем создавать и подавлять предупреждения при каждом использовании BiFunction.
В итоге: я заставил это работать, но я не смог сделать это таким образом, чтобы обеспечить защиту, которую я хотел.