2015-04-01 2 views
9

Во время игры вокруг с новым Java 8 Stream API меня заинтересовало, почему не:Почему Карта <K,V> не распространяется Функция <K,V>?

public interface Map<K,V> extends Function<K, V> 

Или даже:

public interface Map<K,V> extends Function<K, V>, Predicate<K> 

Было бы довольно легко реализовать с помощью default методов на Mapinterface:

@Override default boolean test(K k) { 
    return containsKey(k); 
} 

@Override default V apply(K k) { 
    return get(k); 
} 

И это позволило бы использование Map в методе map:

final MyMagicMap<String, Integer> map = new MyMagicHashMap<>(); 
map.put("A", 1); 
map.put("B", 2); 
map.put("C", 3); 
map.put("D", 4); 

final Stream<String> strings = Arrays.stream(new String[]{"A", "B", "C", "D"}); 
final Stream<Integer> remapped = strings.map(map); 

Или как Predicate в методе filter.

Я нахожу, что значительная часть моих вариантов использования для Map является именно такой конструкцией или аналогичной - как переназначение/поиск Function.

Итак, почему дизайнеры JDK не решили добавить эту функцию к Map во время редизайна для Java 8?

ответ

12

Команда JDK была, конечно, в курсе математической взаимосвязи между java.util.Map в качестве структуры данных и java.util.function.Function как функция отображения. В конце концов, Function был назван Mapper в начале JDK 8 prototype builds. И операция потока, вызывающая функцию для каждого элемента потока, называется Stream.map.

Было даже обсуждение возможности переименования Stream.map в нечто вроде transform из-за возможной путаницы между преобразующей функцией и структурой данных Map. (Извините, не могу найти ссылку.) Это предложение было отклонено, и обоснование было концептуальным сходством (и что для этой цели используется map).

Главный вопрос: что было бы получено, если java.util.Map были подтипом java.util.function.Function? В comments было обсуждено, связано ли подтипирование с отношением «is-a». Подтипирование меньше связано с «is-a» отношениями объектов, поскольку мы говорим об интерфейсах, а не о классах, - но это подразумевает замену . Так что, если Map были подтипом Function, можно было бы быть в состоянии сделать это:

Map<K,V> m = ... ; 
source.stream().map(m).collect(...); 

Сразу же мы сталкиваемся с выпечкой в ​​поведении, что теперь Function.apply к одному из существующих Map методов. Вероятно, единственным разумным является Map.get, который возвращает null, если ключ отсутствует. Эта семантика, откровенно говоря, немного отвратительна. Реальные приложения, вероятно, придутся написать свои собственные методы, которые поставляют ключ пропущенных политиков в любом случае, так что, кажется, очень небольшое преимущество в возможности написать

map(m) 

вместо

map(m::get) 

или

map(x -> m.getOrDefault(x, def)) 
+2

Я думаю, что вопрос о выпечке в 'map :: get', а не' getOrDefault' или 'computeIfAbsent', или что-то еще, если это самый убедительный аргумент. Я вижу, что «null» будет проблемой, особенно, поскольку «поток» начнет жизнь как «НЕИСПРАВНОСТЬ». Благодарим вас за ценное понимание дизайнерского решения! –

4

Поскольку карта не является функцией. Наследование для A - это отношения B. Не для A может быть предметом различных видов отношений B.

Чтобы иметь функцию преобразующую ключ к его стоимости, вам просто нужно

Function<K, V> f = map::get; 

Чтобы иметь тестирование предикат, если объект содержится в карте, вам просто нужно

Predicate<Object> p = map::contains; 

Это является более четким и понятным, чем ваше предложение.

+1

ковыряться в Scala показывает, что их [ 'Map' делает подмешать в' PartialFunction'] (http://www.scala-lang.org/api/2.11.4/index.html#scala.collection .MapLike). Я полагаю, это просто другое дизайнерское решение, но оно показывает, что, возможно, это не совсем так ясно, как все это ... –

+2

Как карта не является функцией? Я думаю, что отлично определить карту как функцию, такую, что 'f (k) = v' в домене, определяемом ключами на карте, и' f (x) = null', если x не находится в домене. – user2336315

+0

@ user2336315 Карта не является функцией в математическом смысле. Несколько вызовов с одним и тем же ключом могут давать разные результаты. – zeroflagL

8

Вопрос заключается в том, «почему она должна распространяться Function

Ваш пример использования strings.map(map) действительно не оправдывает идею изменения типа наследования (подразумевая добавление методов к интерфейсу Map), учитывая небольшое различие до strings.map(map::get). И неясно, действительно ли использование Map как Function действительно так, что оно должно получить эту специальную обработку по сравнению с, например, используя map::remove как Function или используя map::get от Map<…,Integer> как ToIntFunction или map::get от Map<T,T> как BinaryOperator.

Это еще более сомнительна в случае Predicate; должен map::containsKey действительно получить специальное лечение по сравнению с map::containsValue?

Также стоит отметить сигнатуру типа методов.Map.get имеет функциональную подпись Object → V, в то время как вы предполагаете, что Map<K,V> должен расширять Function<K,V>, что понятно из концептуального вида карт (или просто взглядом на тип), но это показывает, что есть два противоречивых ожидания, в зависимости от того, смотрите ли вы на метод или тип. Лучшим решением является не исправление функционального типа. Затем вы можете назначить map::get к любому Function<Object,V> или Function<K,V> и все довольны ...

+0

Я думаю, что это гораздо лучший аргумент, чем [ответ JB Nizet] (http://stackoverflow.com/a/29388651/2071828), я вижу здесь логику. Ключевым моментом является то, что наличие «Map extends Function » вызовет проблемы с Java-примитивами ('ToXXXFunction') и, что более важно, не будет хорошо играть с (несколько взломанной) сигнатурой' get' - это те основные соображения? Основной ответ в этом случае кажется «устаревшими проблемами» ... –

+1

@Boris the Spider: я не могу заглянуть в ум разработчиков. Возможно, они вообще никогда об этом не задумывались. Тем более, что нет попытки полностью модифицировать существующие типы для функций, например. позволяя 'Comparator' расширять' ToIntBiFunction' и т. д. Возможно, более простой ответ заключается в том, что функции и другие типы считаются в целом отличными. Имея расширение '' '' '' '' ', вы можете создавать такие драгоценные камни, как вызов' firstMap.compose (otherMap) '... – Holger

+5

Слишком легко обвинить его в« устаревших »проблемах. Холгер прав; правильный вопрос - «почему это должно быть», а не «почему бы и нет». И не было никакой веской причины, почему это необходимо, особенно учитывая, насколько просто преобразовать map :: get или list :: contains в функцию или Predicate. –

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