Строка в Int в Java - вероятно, плохие данные, нужно избегать исключений - PullRequest
37 голосов
/ 06 октября 2008

Видя, что в Java нет обнуляемых типов и нет TryParse (), как вы обрабатываете проверку входных данных без исключения?

Обычный способ:

String userdata = /*value from gui*/
int val;
try
{
   val = Integer.parseInt(userdata);
}
catch (NumberFormatException nfe)
{
   // bad data - set to sentinel
   val = Integer.MIN_VALUE;
}

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

Как лучше всего справляться с этой ситуацией?

РЕДАКТИРОВАТЬ: Обоснование: На SO много говорилось об обработке исключений, и общее мнение таково, что исключения должны использоваться только для непредвиденных сценариев. Тем не менее, я думаю, что плохой пользовательский ввод ОЖИДАЕТСЯ, а не редко. Да, это действительно академическая точка.

Дополнительные правки:

Некоторые из ответов наглядно демонстрируют, что не так с SO. Вы игнорируете задаваемый вопрос и отвечаете на другой вопрос, который не имеет к этому никакого отношения. Вопрос не о переходе между слоями. Вопрос не в том, что вернуть, если число не разбирается. Для всего, что вы знаете, val = Integer.MIN_VALUE; это точно правильный вариант для приложения, из которого был взят этот полностью контекстно-свободный фрагмент кода.

Ответы [ 16 ]

27 голосов
/ 22 мая 2013

Я спросил , существуют ли библиотеки утилит с открытым исходным кодом, в которых есть методы для этого анализа , и ответ - да!

Из Apache Commons Lang вы можете использовать NumberUtils.toInt :

// returns defaultValue if the string cannot be parsed.
int i = org.apache.commons.lang.math.NumberUtils.toInt(s, defaultValue);

Из Google Guava вы можете использовать Ints.tryParse :

// returns null if the string cannot be parsed
// Will throw a NullPointerException if the string is null
Integer i = com.google.common.primitives.Ints.tryParse(s);

Нет необходимости писать свои собственные методы для разбора чисел без выдачи исключений.

17 голосов
/ 06 октября 2008

Для пользовательских данных Integer.parseInt обычно является неправильным методом, поскольку он не поддерживает интернационализацию. Пакет java.text - ваш (многословный) друг.

try {
    NumberFormat format = NumberFormat.getIntegerInstance(locale);
    format.setParseIntegerOnly(true);
    format.setMaximumIntegerDigits(9);
    ParsePosition pos = new ParsePosition(0);
    int val = format.parse(str, pos).intValue();
    if (pos.getIndex() != str.length()) {
        // ... handle case of extraneous characters after digits ...
    }
    // ... use val ...
} catch (java.text.ParseFormatException exc) {
    // ... handle this case appropriately ...
}
16 голосов
/ 06 октября 2008

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

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

11 голосов
/ 06 октября 2008

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

6 голосов
/ 06 октября 2008

Я уверен, что это плохая форма, но у меня есть набор статических методов в классе Utilities, которые делают такие вещи, как Utilities.tryParseInt(String value), который возвращает 0, если строка не разбирается, и Utilities.tryParseInt(String value, int defaultValue), которая позволяет указать значение использовать, если parseInt() выдает исключение.

Я считаю, что бывают случаи, когда возвращение известного значения при неправильном вводе является вполне приемлемым. Очень надуманный пример: вы запрашиваете у пользователя дату в формате ГГГГММДД, и он дает вам неверный ввод. Может быть вполне приемлемо сделать что-то вроде Utilities.tryParseInt(date, 19000101) или Utilities.tryParseInt(date, 29991231); в зависимости от требований программы.

3 голосов
/ 07 октября 2008

Я собираюсь перефразировать точку зрения, которую Стинкиминки делал в нижней части поста:

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

Как только вы узнаете, что правильно проверили введенные пользователем данные, , тогда можно безопасно проанализировать и игнорировать, зарегистрировать или преобразовать в RuntimeException NumberFormatException.

Обратите внимание, что этот подход требует от вас рассмотрения вашей модели в двух частях: бизнес-модель (где мы на самом деле заботимся о наличии значений в формате int или float) и модель пользовательского интерфейса (где мы действительно хотим, чтобы пользователь мог поместить во что угодно).

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

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

Библиотеки связывания, такие как JGoodies Binding и JSR 295, значительно упрощают реализацию такого рода вещей, чем это может показаться, и многие веб-платформы предоставляют конструкции, которые отделяют пользовательский ввод от реальной бизнес-модели, заполняя бизнес-объекты только после завершения проверки. .

С точки зрения проверки файлов конфигурации (другой вариант использования представлен в некоторых комментариях), одно значение - указать значение по умолчанию, если определенное значение вообще не указано, - но если данные отформатированы неправильно (кто-то набирает «о» вместо «ноль» - или они скопированы из MS Word, и все обратные метки получили забавный символ юникода), тогда требуется какая-то системная обратная связь (даже если приложение просто сбрасывает приложение, бросая исключение времени выполнения).

2 голосов
/ 06 октября 2008

Вот как я это делаю:

public Integer parseInt(String data) {
  Integer val = null;
  try {
    val = Integer.parseInt(userdata);
  } catch (NumberFormatException nfe) { }
  return val;
}

Тогда ноль сигнализирует о недопустимых данных. Если вы хотите значение по умолчанию, вы можете изменить его на:

public Integer parseInt(String data,int default) {
  Integer val = default;
  try {
    val = Integer.parseInt(userdata);
  } catch (NumberFormatException nfe) { }
  return val;
}
1 голос
/ 05 декабря 2012

Попробуйте org.apache.commons.lang.math.NumberUtils.createInteger(String s). Это мне очень помогло. Там есть аналогичные методы для парных, длинных и т.д.

1 голос
/ 06 октября 2008

Я думаю, что лучшая практика - это код, который вы показываете.

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

0 голосов
/ 11 июля 2017

Более чистая семантика (Java 8 OptionalInt)

Для Java 8+ я бы, вероятно, использовал бы RegEx для предварительной фильтрации (чтобы избежать исключения, как вы отметили), а затем обернул бы результат в необязательный примитив (для решения проблемы «по умолчанию»):

public static OptionalInt toInt(final String input) {
    return input.matches("[+-]?\\d+") 
            ? OptionalInt.of(Integer.parseInt(input)) 
            : OptionalInt.empty();
}

Если у вас много строковых входов, вы можете рассмотреть возможность возврата IntStream вместо OptionalInt, чтобы вы могли flatMap().

Ссылки

...