2017-01-13 2 views
0

Я пытаюсь решить следующие упражнения из «ядер Java для нетерпеливого» по Cay Horstmann:Streams с возвратным TreeMap некогерентных результатами

Когда кодер из Charset с частичным охватом Unicode не может кодировать , он заменяет его стандартным, но не всегда, кодировкой «?». Найти все замены всех доступных наборов символов, поддерживающих кодирование. Используйте метод newEncoder, чтобы получить кодировщик, и позвоните по его методу replacement, чтобы получить замену . Для каждого уникального результата сообщайте канонические имена кодировок , которые его используют.

Ради образования, я решил взяться за упражнения с гигантскими однострочника использованием потокового API, несмотря на то, - на мой взгляд - это чистое решение будет разделить расчеты на ряд шагов, с промежуточные переменные между ними (конечно, это облегчило бы отладку). Без дальнейших церемоний, вот монстр кода я создал:

Charset.availableCharsets().values().stream().filter(charset -> charset.canEncode()).collect(
      Collectors.groupingBy(
        charset -> charset.newEncoder().replacement(), 
        () -> new TreeMap<>((arr1, arr2) -> Arrays.equals(arr1, arr2) == true ? 0 : Integer.compare(arr1.hashCode(), arr2.hashCode())), 
        Collectors.mapping(charset -> charset.name(), Collectors.toList()))). 
      values().stream().map(list -> list.stream().collect(Collectors.joining(", "))).forEach(System.out::println); 

В основном, мы принимаем во внимание только кодировок, что canEncode; создать Map с replacement как ключ и список канонических имен в качестве значений; потому что группировка не работала для массивов с реализацией по умолчанию groupingBy, которая использует HashMap, я решил использовать TreeMap. Затем мы работаем с каноническими именами Lists, присоединяем их к запятой и печати.

К сожалению, я нашел его для получения некогерентных результатов. Если я дважды запускаю функцию в той же программе, первый экземпляр возвращает результаты, состоящие из 23 Strings, второй - только 21 Strings. Я подозреваю, что это что-то делать с плохой реализацией Comparator для TreeMap, которая была определена следующим образом:

((arr1, arr2) -> Arrays.equals(arr1, arr2) == true ? 0 : Integer.compare(arr1.hashCode(), arr2.hashCode())) 

Если это является причиной, что должно быть в этом случае собственно Comparator? Кроме того, можно ли улучшить однострочный лайнер?

Мне также интересно, если такие сложные конструкции, как написанный мной код, встречаются в профессиональных программах? Может быть, только мне это кажется нечитаемым?

+0

убедитесь, что ваш компаратор удовлетворяет следующим требованиям: * Порядок, заданный компаратором c на наборе элементов S, считается согласованным с равными тогда и только тогда, когда c.compare (e1, e2) == 0 имеет то же логическое значение как e1.equals (e2) для каждого e1 и e2 в S * – Jobin

+0

Спасибо. Я не уверен, что в этом случае можно определить компаратор, совместимый с равными, поскольку для массивов сравниваются ссылки, и мне нужно сравнить значения, хранящиеся в этом массиве. Есть ли какая-либо другая структура данных, которую я мог бы использовать в этом случае, помимо TreeMap? – lukeg

ответ

2

Нет гарантии, что хеш-код двух разных экземпляров будет отличаться. Это была бы идеальная ситуация, но она никогда не гарантируется. Верно только противоположное: если два объекта равны, они имеют одинаковый хеш-код.

Итак, если вы создадите компаратор, который считает объекты одинаковыми, когда они имеют один и тот же хэш-код, произвольные объекты могут считаться одинаковыми. Поскольку массивы byte[], возвращенные replacement(), являются защитными копиями, считывают временные объекты, результат может варьироваться в каждом прогоне этого кода.

Кроме того, поскольку хеш-код массива не имеет ничего общего с его содержанием, ваш компаратор нарушает правило транзитивности: два массива с равным содержимым должны быть одинаковыми, но поскольку они могут/очень вероятно иметь разные хэши коды, они имеют другое отношение при сравнении с третьим массивом, не имеющим одного и того же содержимого, a == b, но a < c и b > c.Вот почему даже равные массивы, которые вы сравниваете по Arrays.equals, могут оказаться в разных группах, так как TreeSet не смог найти существующий ключ при сравнении с другими ключами.

Если вы хотите, чтобы массивы можно сравнить по значению, вы можете использовать:

Charset.availableCharsets().values().stream().filter(Charset::canEncode).collect(
    Collectors.groupingBy(
      charset -> charset.newEncoder().replacement(), 
      () -> new TreeMap<>(Comparator.comparing(ByteBuffer::wrap)), 
      Collectors.mapping(Charset::name, Collectors.joining(", ")))) 
    .values().forEach(System.out::println); 

ByteBuffer s является Comparable и последовательно оценивать содержимое обернутого массива.

я переехал Collectors.joining коллектор в grouping коллектор, чтобы избежать создания временного List, содержимое которого вы собираетесь присоединиться потом в любом случае.

Кстати, никогда не используйте код expression == true. Нет причин добавлять == true, поскольку expression уже достаточно.


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

Charset.availableCharsets().values().stream().filter(Charset::canEncode).collect(
    Collectors.groupingBy(
      charset -> ByteBuffer.wrap(charset.newEncoder().replacement()), 
      TreeMap::new, 
      Collectors.mapping(Charset::name, Collectors.joining(", ")))) 
    .values().forEach(System.out::println); 

Это изменение даже позволяет прибегать к хеширования, если не требуется последовательный порядок итерации:

Charset.availableCharsets().values().stream().filter(Charset::canEncode).collect(
    Collectors.groupingBy(
      charset -> ByteBuffer.wrap(charset.newEncoder().replacement()), 
      Collectors.mapping(Charset::name, Collectors.joining(", ")))) 
    .values().forEach(System.out::println); 

Это работает, потому что ByteBuffer также реализует equals и hashCode.