Java Generics Аргумент Несоответствие типов при переходе от универсального класса к универсальному методу - PullRequest
0 голосов
/ 04 июля 2019

У меня есть абстрактный класс с универсальными типами на уровне класса <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.

В итоге: я заставил это работать, но я не смог сделать это таким образом, чтобы обеспечить защиту, которую я хотел.

1 Ответ

0 голосов
/ 05 июля 2019

Краткий ответ: не используйте универсальный тип B для этого метода.

Подробнее: Невозможно сказать компилятору, что для данного вызова P (или некоторый другой универсальный тип) следует считать B, кроме приведения:

return objectToString.apply((B)getter.apply(obj), (Class<B>)paramClass);

Однако, так как вы явно приводите, вы теряете гарантию того, что предоставленный класс (2-й параметр) представляет класс 1-го параметра. Единственная цель определения универсального типа B - получить эту защиту. Так что лучше просто не использовать универсальный здесь и иметь этот аргумент BiFunction<Object, Class, String>.

String getParamString(O obj, BiFunction<Object, Class, String> objectToString);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...