2009-11-05 2 views
4

У меня есть несколько Comparator с - один для Date с, один для знаков после запятой, один за проценты и т.д.Как я могу улучшить этот компаратор?

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

class NumericComparator implements Comparator<String> { 

    @Override 
    public int compare(String s1, String s2) { 
    final Double i1 = Double.parseDouble(s1); 
    final Double i2 = Double.parseDouble(s2); 
    return i1.compareTo(i2); 
    } 

} 

Жизнь была проста , Конечно, это не относится к случаю, когда строки не обрабатываются. Таким образом, я улучшил compare():

class NumericComparator implements Comparator<String> { 

    @Override 
    public int compare(String s1, String s2) { 
    final Double i1; 
    final Double i2; 

    try { 
     i1 = Double.parseDouble(s1); 
    } catch (NumberFormatException e) { 
     try { 
     i2 = Double.parseDouble(s2); 
     } catch (NumberFormatException e2) { 
     return 0; 
     } 
     return -1; 
    } 
    try { 
     i2 = Double.parseDouble(s2); 
    } catch (NumberFormatException e) { 
     return 1; 
    } 

    return i1.compareTo(i2); 
    } 
} 

Жизнь была лучше. Испытания стали более твердыми. Однако мой обозреватель моего кода отметил: «А как насчет null

Великий, так что теперь я должен повторить выше NullPointerException или предварять тело метода с:

if (s1 == null) { 
    if (s2 == null) { 
    return 0; 
    } else { 
    return -1; 
    } 
} else if (s2 == null) { 
    return 1; 
} 

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

Я не эксперт по Java. Есть ли более чистый, более чистый раствор, чем - gasp - копирование и склеивание? Должен ли я торговать правильностью из-за отсутствия сложности, если она документально подтверждена?


Update: Некоторые предположили, что это не задание Comparator «s для обработки null значений. Поскольку результаты сортировки отображаются пользователям, я действительно хочу, чтобы нули сортировались последовательно.

+0

Хотелось бы узнать, почему вам нужны компараторы, которые обрабатывают все возможные сценарии без ошибок. что вы делаете, мне кажется, что это маскирование ошибок, которое не помогает в большинстве ситуаций. – pstanton

+0

Простите меня, если я неправильно понял код, вы говорите, что s1 и s2 равны, если ни один из них не может быть проанализирован? Это кажется немного странным ... – Grundlefleck

+0

Что касается маскировки ошибок, эти классы предназначены для сортировки столбцов в таблице GWT. С точки зрения пользователя это должно быть «достаточно хорошим». Что касается равенства, то да, если ни одна строка не обрабатывается, ни одна строка не сопоставима. Таким образом, они равны в своей нерушимости. –

ответ

1

Вот как я бы улучшить компаратор:

Во-первых, exctract способ преобразования значения. Это повторяется, несколько попыток ... уловы всегда уродливы -> лучше иметь как можно меньше из них.

private Double getDouble(String number) { 
try { 
    return Double.parseDouble(number); 
} catch(NumberFormatException e) { 
    return null; 
} 
} 

Далее, запишите простые правила, чтобы показать, как вы хотите, чтобы поток компаратора был.

if i1==null && i2!=null return -1 
if i1==null && i2==null return 0 
if i1!=null && i2==null return 1 
if i1!=null && i2!=null return comparison 

Наконец делать ужасное запутывание к фактическому компаратору, чтобы поднять несколько WTF: с в обзоре коды (или как другие любят говорить, «Реализовать компаратор»):

class NumericComparator implements Comparator<String> { 

    public int compare(String s1, String s2) { 
     final Double i1 = getDouble(s1); 
     final Double i2 = getDouble(s2); 

     return (i1 == null) ? (i2 == null) ? 0 : -1 : (i2 == null) ? 1 : i1.compareTo(i2); 
    } 
    private Double getDouble(String number) { 
      try { 
       return Double.parseDouble(number); 
      } catch(NumberFormatException e) { 
       return null; 
      } 
    } 
} 

... да, это ветвистая вложенная тройка. Если кто-то жалуется на это, скажите, что говорили другие: Обработка нулей - это не работа компаратора.

+0

Это, по-видимому, соответствует моему сценарию лучше всего. –

1

Вы можете создать метод утилиты, который обрабатывает разбор и возвращает определенное значение в случае исключений NULL или синтаксического анализа.

+0

Какими будут эти определенные значения? –

+0

Зависит от того, как вы хотите, чтобы сравнение получилось. Вы можете вернуть что-то вроде Double.MIN_VALUE или что-то еще, если вы хотите, чтобы сравнение все еще работало, или просто хотите только проверить один случай, вернуть null и проверить его, и это будет сигнализировать о любой ошибке. –

2

Есть много субъективных ответов на этот вопрос. Вот мои собственные $ 0,02.

Во-первых, проблема, которую вы описываете, - это канонический симптом языка, в котором отсутствуют первоклассные функции, которые позволят вам кратко описать эти шаблоны.

Во-вторых, на мой взгляд, это должно быть быть ошибкой сравнивать две строки как пары, если один из них нельзя считать представлением двойника. (То же самое верно для нулей и т. Д.). Поэтому вы должны разрешать распространение исключений! Это будет спорным мнение, я ожидаю.

+0

Я смущен, почему вы думаете, что «первоклассные функции» помогут. –

+1

Я согласен с исключениями, возможно, также и нулями в зависимости от вашего сценария/использования – pstanton

+0

Если функции первого класса могут помочь здесь, вы могли бы продемонстрировать, что с использованием интерфейсов одного метода и анонимных внутренних типов? Потому что я хотел бы знать, как они будут использоваться, чтобы исправить это :-) – Grundlefleck

0

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

public int compare(Object o1, Object o2) throws ClassNotFoundException { 
     String[] supportedClasses = {"String", "Double", "Integer"}; 
     String j = "java.lang."; 
     for(String s : supportedClasses){ 
      if(Class.forName(j+s).isInstance(o1) && Class.forName(j+s).isInstance(o1)){ 
       // compare apples to apples 
       return ((Comparable)o1).compareTo((Comparable)o2); 
      } 
     } 
     throw new ClassNotFoundException("Not a supported Class"); 
    } 

Можно даже определить рекурсивно, где вы приводите ваши строки в парном разряде, а затем возвращает результат вызова себя с этими объектами.

+0

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

0

IMHO вы должны сначала создать метод, который возвращает Double из String, встраивая случаи сбоя с ошибкой и синтаксического анализа (но вы должны определить, что делать в таких случаях: вызывать исключение? Возвращать значение по умолчанию?).

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

Другими словами, рефакторинг ...

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

4

Вы используете Comparator<String>. String, в том числе compareTo, введите NullPointerException, если ему передан нуль, поэтому вам тоже нужно. Аналогично, Comparator выбрасывает ClassCastException, если типы аргументов не позволяют сравнивать их. Я бы рекомендовал вам реализовать эти унаследованные поведения.

class NumericComparator implements Comparator<String> { 

    public int compare(String s1, String s2) { 
    final Double i1; 
    final Double i2; 
    if(s1 == null) 
    { 
     throw new NullPointerException("s1 is null"); // String behavior 
    } 
    try { 
     i1 = Double.parseDouble(s1) 
    } catch (NumberFormatException e) { 
     throw new ClassCastException("s1 incorrect format"); // Comparator behavior 
    } 

    if(s2 == null) 
    { 
     throw new NullPointerException("s2 is null"); // String behavior 
    } 
    try { 
     i2 = Double.parseDouble(s1) 
    } catch (NumberFormatException e) { 
     throw new ClassCastException("s2 incorrect format"); // Comparator behavior 
    } 
    return i1.compareTo(i2); 
    } 
} 

Вы можете почти восстановить первоначальную элегантность путем extracting a method сделать проверку типов и преобразования.

class NumericComparator implements Comparator<String> { 

    public int compare(String s1, String s2) { 
    final Double i1; 
    final Double i2; 

    i1 = parseStringAsDouble(s1, "s1"); 
    i2 = parseStringAsDouble(s2, "s2"); 
    return i1.compareTo(i2); 
    } 

    private double parseStringAsDouble(String s, String name) { 

    Double i; 
    if(s == null) { 
     throw new NullPointerException(name + " is null"); // String behavior 
    } 
    try { 
     i = Double.parseDouble(s1) 
    } catch (NumberFormatException e) { 
     throw new ClassCastException(name + " incorrect format"); // Comparator behavior 
    } 
    return i; 
    } 
} 

Если вы не используете сообщения об исключении, вы можете потерять параметр «имя». Я уверен, что вы можете потерять лишнюю строку здесь или слово там, применив небольшие трюки.

Вы говорите, что вам нужно repeat this pattern with three other classes which compare different types of strings and could raise three other exceptions. Трудно предлагать специфику там, не видя ситуации, но вы можете использовать "Pull Up Method" в версии моего parseStringAsDouble в общем предке NumericComparator, который сам реализует java's Comparator.

+0

Мне понравилось предложение «ClassCastException». Тем не менее, это приводит к ошибкам, возникающим из-за 'Collections.sort()' Я выполняю, и вся сортировка терпит неудачу. И это нежелательное поведение для отображения «отсортированных» результатов для пользователя. (Я должен был упомянуть последнее в вопросе.) –

+0

Вы можете быть разрешительным в любом из нескольких пунктов. Похоже, что эта часть кода нуждается в управлении сложностью, поэтому, возможно, вседозволенность может пойти куда-то еще. Возможно, вы можете реализовать 'santize (Collection c)', чтобы удалить из коллекции недостающие элементы. Если вы ожидаете, что большинство входов будет плохо сформировано (как определено реализацией компаратора), вы можете запустить санитацию в каждую коллекцию до сортировки. В противном случае, если вы ожидаете, что плохо сформированные коллекции будут редкими, вы можете «попробовать» сортировку и запустить 'sanitize' и' sort', если вы получите исключение. –

1

Сделайте шаг назад. Откуда берутся эти Strings? Для чего это Comparator для использования? У вас есть CollectionStrings, который вы хотели бы отсортировать или так?

-3

Так я улучшил сравнить() ...

, что вы сделали.

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

что касается чистых ... почему окончательный? почему все ловушки/броски? зачем использовать compareTo для примитивной оболочки?

class NumericComparator implements Comparator<String> { 
public int compare(String s1, String s2) throws NullPointerException, NumberFormatException { 

    double test = Double.parseDouble(s1) - Double.parseDouble(s2); 

    int retVal = 0; 
    if (test < 0) retVal = -1; 
    else if (test > 0) retVal = 1; 

    return retVal; 
} 
} 

кажется, что вы могли бы найти его более четким, переименовав тест к t1 и RetVal к д.

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

+0

Почему бы не использовать compareTo Double? Ваше тестовое значение может переполняться, что не обязательно при сравнении вместо вычитания. – rsp

+0

В каком случае это происходит? не худший случай для этого теста Double.MAX_VALUE - Double.MIN_VALUE? который разрешает Double.POSITIVE_INFINITY, который равен> 0, поэтому вы получите правильный ответ. предоставлен, он не обрабатывает Double.NaN. но я не знаю, как вы будете генерировать Double.NaN, учитывая, что мы бросаем NumberFormatExceptions. – colin

1

tl; dr: Взять руководство от JDK. Двойной компаратор не определен ни для числа, ни для нулей. Заставьте людей дать вам полезные данные (Doubles, Dates, Dinosaurs, что угодно) и напишите для этого своих компараторов.

Насколько я могу судить, это случай проверки ввода пользователем. Например, если вы вносите ввод из диалогового окна, правильно место, чтобы убедиться, что у вас есть синтаксическая строка, которая представляет собой Double, Date или что-то еще в обработчике ввода. Удостоверьтесь, что это хорошо, прежде чем пользователь может уйти, нажмите «Хорошо» или эквивалент.

Вот почему я думаю, что это:

Первый вопрос: если строки не распознаваем как числа, я думаю, вы пытаетесь решить проблему в неправильном месте. Скажем, например, я пытаюсь сравнить "1.0" с "Two". Второй, очевидно, не поддается анализу, как двойной, но он меньше первого? Или это больше. Я хотел бы утверждать, что пользователям нужно будет превратить свои строки в парные, прежде чем они спросят, что больше (на что вы можете легко ответить с помощью Double.compareTo, например).

Второй вопрос: если строки "1.0" и null, что больше? Источник JDK не обрабатывает NullPointerExceptions в компараторе: если вы дадите ему null, автобоксинг завершится неудачей.

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

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

+0

Что касается первого, нет, я не пытаюсь сравнивать '1.0' с 'Two' - формат строк гарантированно будет таким же. Что касается второго, не имеет значения, какой из них больше, так как результат согласован. –

+0

@a платный ботаник, я понимаю, что это не то, что вы пытаетесь решить. Я указываю, что именно это делает ваш код. Возможно, это было бы более очевидно, если бы я использовал «1.0» и «Eggplant» в моем примере. Я добавлю свой итоговый комментарий в начало моего ответа. –

1

Попробуйте это:

import com.google.common.base.Function; 
import com.google.common.collect.Ordering; 

Ordering.nullsFirst().onResultOf(
    new Function<String, Double>() { 
     public Double apply(String s) { 
     try { 
     return Double.parseDouble(s); 
     } catch (NumberFormatException e) { 
     return null; 
     } 
    }) 

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

http://google-collections.googlecode.com

1

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

public class ParsingComparator implements Comparator<String> { 
    private Parser parser; 

    public int compare(String s1, String s2) { 
    Object c1 = parser.parse(s1); 
    Object c2 = parser.parse(s2); 
    new CompareToBuilder().append(c1, c2).toComparison(); 
    } 
} 

интерфейс Parser будет иметь реализации для чисел, дат и т.д. Вы могли бы потенциально использовать класс java.text.Format для интерфейса Parser. Если вы не хотите использовать commons-lang, вы можете заменить использование CompareToBuilder некоторой логикой для обработки нулей и использовать Comparable вместо Object для c1 и c2.

0

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

class NumericComparator implements Comparator<String> { 
    private SafeAdaptor<Double> doubleAdaptor = new SafeAdaptor<Double>(){ 
     public Double parse(String s) { 
      return Double.parseDouble(s); 
     } 
    }; 
    public int compare(String s1, String s2) { 
     final Double i1 =doubleAdaptor.getValue(s1, "s1"); 
     final Double i2 = doubleAdaptor.getValue(s2, "s2"); 
     return i1.compareTo(i2); 
    } 
} 

abstract class SafeAdaptor<T>{ 
    public abstract T parse(String s); 
    public T getValue(String str, String name) { 
     T i; 
     if (str == null) { 
      throw new NullPointerException(name + " is null"); // String 
     } 
     try { 
      i = parse(str); 
     } catch (NumberFormatException e) { 
      throw new ClassCastException(name + " incorrect format"); // Comparator 
     } 
     return i; 
    } 

} 

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

ура.