Компаратор с нулевыми значениями

У нас есть код, который сортирует список адресов на основе расстояния между их координатами. это делается через collection.sort с пользовательским компаратором.

Однако время от времени в списке появляется адрес без координат, вызывающий исключение NullPointerException. Моя первоначальная идея исправить это состояла в том, чтобы компаратор возвращал 0 как расстояние для адресов, где хотя бы одна из координат равна нулю. Я боюсь, что это может привести к тому, что порядок порядка станет "правильным" в списке.

поэтому возвращает значения "0" для нулевых данных в компараторе в порядке или есть более чистый способ разрешить это.

Ответ 1

Обращаться так, как null означает бесконечно далеко. Таким образом:

  • comp(1234, null) == -1
  • comp(null, null) == 0
  • comp(null, 1234) == 1

При этом вы получаете последовательный порядок.

Ответ 2

Чтобы развернуть ответ Вилли Шённборна, я пришел сюда, чтобы сказать, что google-collections - это именно то, что вы здесь делаете.

В общем случае вы можете просто написать свой собственный Comparator, чтобы игнорировать значения NULL (предположим, что они не равны нулю, поэтому могут сосредоточиться на важной логике), а затем использовать Ordering для обработки нулей:

Collections.sort(addresses, Ordering.from(new AddressComparator()).nullsLast());

В вашем случае, однако, это данные WITHIN Address (Координаты), которые используются для сортировки, правильно? google-коллекции в этом случае еще более полезны. Таким образом, у вас может быть что-то большее:

// Seems verbose at first glance, but you'll probably find yourself reusing 
// this a lot and it will pay off quickly.
private static final Function<Address, Coordinates> ADDRESS_TO_COORDINATES = 
  new Function<Address, Coordinates>() {
      public Coordinates apply(Address in) {
          return in.getCoordinates();
      }
  };

private static final Comparator<Coordinates> COORDINATE_SORTER = .... // existing

тогда, когда вы хотите сортировать:

Collections.sort(addresses,
    Ordering.from(COORDINATE_SORTER)
            .nullsLast()
            .onResultOf(ADDRESS_TO_COORDINATES));

и что, когда мощь google-коллекций действительно начинает окупаться.

Ответ 3

Я считаю, что все, что вы пытаетесь сделать, чтобы "сделать хорошо", координаты null - это просто обклеивание трещин. Вам действительно нужно найти и исправить ошибки, которые вводят ложные координаты null.

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

  • неадекватная проверка входных параметров,
  • с помощью null, чтобы избежать создания пустых массивов или коллекций,
  • возврат null, когда должно быть выбрано исключение, или
  • используя null для представления "нет значения", когда есть лучшее решение.

(Более эффективные решения проблемы "нет значения" обычно включают в себя переписывание кода, так что вам не нужно представлять это и/или использовать ненулевое значение, например, пустую строку, специальный экземпляр, зарезервированное значение Вы не всегда можете найти лучшее решение, но часто можете это сделать.)

Если это описывает ваше приложение, вы должны тратить время на устранение проблем с кодом, вместо того, чтобы думать о способах скрыть NPE.

Ответ 4

Мое решение (может быть полезно для кого-то, смотрящего здесь) - выполнить нормальное сравнение, при этом нулевые значения заменяются не на 0, а максимальное возможное значение (например, Integer.MAX_VALUE). Возвращение 0 несовместимо, если у вас есть значения, которые сами являются 0. Вот правильный пример:

        public int compare(YourObject lhs, YourObject rhs) {
            Integer l = Integer.MAX_VALUE;
            Integer r = Integer.MAX_VALUE;
            if (lhs != null) {
                l = lhs.giveMeSomeMeasure();
            }
            if (rhs != null) {
                r = rhs.giveMeSomeMeasure();
            }
            return l.compareTo(r);
        }

Я просто хотел добавить, что вам не нужно максимальное значение для integer. Это зависит от того, что может вернуть метод giveMeSomeMeasure(). Если, например, вы сравниваете градусы Цельсия для погоды, вы можете установить l и r на -300 или +300, в зависимости от того, где вы хотите установить нулевые объекты - в голову или хвост списка.

Ответ 5

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

Шок должен был переместить их в нижнюю часть списка (но это уродливо!)

Ответ 6

Нет, нет более чистого способа. Возможно:

  • если координаты обоих сравниваемых объектов равны нулю, верните 0
  • если координаты одного из объектов равны нулю, возвращайте -1/1 (в зависимости от того, является ли это первым или вторым аргументом)

Но что еще более важно - попробуйте избавиться от/заполнить недостающие координаты или, лучше: не помещать адреса с отсутствующими координатами в списке.

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

Вы можете создать другой список, содержащий адреса с отсутствующими координатами, и дать понять, кому нужна эта информация (конечный пользователь, пользователь API), что первый список содержит только адреса с необходимыми данными, а второй список содержит адреса, в которых отсутствует требуемая информация.

Ответ 7

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

  • Если вы пытаетесь сортировать их, чтобы сначала отображать наиболее релевантные решения для пользователя, может быть хорошей идеей поставить неизвестные местоположения последними, поэтому относитесь к нему как к бесконечности (возвращая 0/-1/1 в зависимости от того, из них имеет значение null).
  • Если вы собираетесь использовать этот результат, чтобы нарисовать какой-либо граф или выполнить некоторые другие вычисления, которые зависят от них, которые действительно сортируются по их расстоянию, то нули, вероятно, не должны были быть там в любом случае (так что сначала удалите их, или выбросить исключение, если в этот момент на самом деле не должно было быть никаких адресов с нулевым местоположением).

Как вы уже поняли, всегда возвращается 0, когда один из них является нулевым, здесь не очень хорошая идея; это может повредить результат. Но то, что вы должны делать, зависит от того, что вам нужно, а не от того, что другие обычно делают/нуждаются. Как ваша программа ведет себя с адресами, у которых нет местоположения (так, что пользователь увидит), не должно зависеть от некоторых технических деталей, таких как "лучшая практика" для компараторов. (Для меня, спрашивая, что такое "лучшая практика" здесь, звучит как спрашивать, каковы "лучшие требования" ).

Ответ 8

Я лично ненавижу общаться со специальными нулевыми случаями всюду в моих компараторах, поэтому я искал более чистую оценку и, наконец, нашел коллекцию google. Их заказ просто потрясающий. Они поддерживают составные компараторы, предлагают сортировать нули до вершины и до конца и позволяют выполнять определенные функции перед сравнением. Написание компараторов никогда не было так просто. Вы должны попробовать.

Ответ 9

Если вы используете Java 8, у вас есть 2 новых статических метода в классе Comparator, которые пригодится:

public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator)
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator)

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

Следующий пример:

List<String> monkeyBusiness = Arrays.asList("Chimp", "eat", "sleep", "", null, "banana",
            "throw banana peel", null, "smile", "run");
Comparator<? super String> comparator = (a, b) -> a.compareTo(b);
monkeyBusiness.stream().sorted(Comparator.nullsFirst(comparator))
            .forEach(x -> System.out.print("[" + x + "] "));

напечатает: [null] [null] [] [Chimp] [банан] [есть] [пробег] [сон] [улыбка] [бросить банановую кожуру]