2015-11-07 2 views
2

У меня проблема с дженериками в java, в частности с подстановочными знаками. У меня есть Map (outerMap), значениями которого являются другие элементы Map (innerMap), значения которых составляют List элементов.Метод вызова сбоя компиляции с вложенным шаблоном

См. Следующий класс java, который я создал, чтобы продемонстрировать это поведение. Операции импорта не включены. Игнорировать использование потоков внутри методов, это не имеет значения:

public class SSCCE { 
    public static void main(String[] args) { 
    // Setup 
    List<Double> doubles = Arrays.asList(1.0,2.0,3.0); 
    Map<Integer, List<Double>> innerMap = new HashMap<>(); 
    innerMap.put(1, doubles); 
    Map<String, Map<Integer, List<Double>>> outerMap = new HashMap<>(); 
    outerMap.put("hello", innerMap); 

    // Test  
    // This method call works fine. 
    Stream<Integer> streamFromInner = getStreamOfMappingToN(innerMap); 

    // This method call does not work - causes below compilation error. 
    Stream<Integer> stream = getStreamOfMergedDistinctMappingToN(outerMap); 
    } 

    private static <T, U> Stream<T> getStreamOfMappingToN(Map<T, ? extends Collection<U>> map) { 
    return map.entrySet().stream().map(q -> q.getKey()); 
    } 

    private static <T, U> Stream<T> getStreamOfMergedDistinctMappingToN(Map<String, Map<T, ? extends Collection<U>>> map) { 
    return map.entrySet().stream().flatMap(
      p -> getStreamOfMappingToN(p.getValue()) 
    ).distinct(); 
    } 
} 

Я вижу следующее сообщение об ошибке компиляции, на втором вызове метода:

method getStreamOfMergedDistinctMappingToN in class SSCCE cannot be applied to given types; 
    required: Map<String,Map<T,? extends Collection<U>>> 
    found: Map<String,Map<Integer,List<Double>>> 
    reason: cannot infer type-variable(s) T,U 
    (argument mismatch; Map<String,Map<Integer,List<Double>>> cannot be converted to Map<String,Map<T,? extends Collection<U>>>) 
    where T,U are type-variables: 
    T extends Object declared in method <T,U>getStreamOfMergedDistinctMappingToN(Map<String,Map<T,? extends Collection<U>>>) 
    U extends Object declared in method <T,U>getStreamOfMergedDistinctMappingToN(Map<String,Map<T,? extends Collection<U>>>) 

Может кто-нибудь предложить, почему это происходит со второй вызов метода, а не первый? Обе сигнатуры метода содержат Map<T, ? extends Collection<U>>, но только второй не соответствует вызову. Если я заменил проблемный ? extends Collection<U> на List<U>, он отлично работает, потому что я фактически передаю его List, но это кажется ленивым и не объясняет, почему я вижу эту ошибку.

Я немного поболтал об этом, некоторые люди столкнулись с подобными проблемами и объяснили их проблемы - многие отмечают, что уровень шаблона имеет значение, но без понимания того, что здесь происходит Мне трудно связать другие решения этой проблемы. Это все еще довольно запутанно. Благодаря чтению я обнаружил, что если я заменил Map<T, ? extends Collection<U>>? extends Map<T, ? extends Collection<U>> во втором объявлении метода, он компилируется отлично, но я не понимаю, почему, а затем почему первая сигнатура метода будет работать.

Буду признателен за ваш отзыв.

+0

Более практичная проблема с этим кодом (даже когда вы его компилируете) заключается в том, что очень сложно понять, что происходит. – biziclop

+0

@biziclop Конечно, это не мой фактический код, он просто демонстрирует проблему. Я использую потоки для отображения из 'Entry' в' T' на основе того, что находится в 'Collection', но я оставил много этого, поскольку я не думал, что это имеет отношение к проблеме.То же самое касается T и U, они могут быть чем угодно. –

+0

http://bayou.io/draft/Capturing_Wildcards.html#Nested_Wildcards – ZhongYu

ответ

1

Вы должны изменить свои методы к следующему:

private static <T, U extends Collection<?>> Stream<T> getStreamOfMappingToN(Map<T, U> map) { 
    return map.entrySet().stream().map(q -> q.getKey()); 
} 

private static <T, U extends Collection<?>> Stream<T> getStreamOfMergedDistinctMappingToN(Map<String, Map<T, U>> map) { 
    return map.entrySet().stream() 
      .flatMap(p -> getStreamOfMappingToN(p.getValue())).distinct(); 
} 

Что изменилось в том, что вместо того, чтобы объявить тип входа в ? extends Collection<U> метод принимает Map<T, U> но U ограничивается до U extends Collection<?>.

Это изменение представляет собой то, что вы действительно хотите сделать: в качестве ввода вы вводите Map. Эта карта имеет два типа: T и U. T может быть любым типом, который мы даем, но U действительно должен быть Collection чего-то, то есть U должен extends Collection<?>.


Чтобы узнать, почему ваш пример не компилируется, пожалуйста, обратитесь к this answer (или this one): подстановочные работает только на 1-ом уровне, не глубже. Как вы выяснили, вам нужно добавить ? extends Map<...>, чтобы противостоять тому, что шаблон не был применен рекурсивно.

Тем не менее, я бы порекомендовал вам использовать первое решение, поскольку я чувствую, что он дает более чистый код и правильное намерение, вместо того, чтобы возиться с подстановочными знаками.

Смежные вопросы