Лучше проверить `c> = '0'` или` c> = 48`?

46

После обсуждения с некоторыми моими коллегами у меня возник «философский» вопрос о том, как относиться к типу данных char в Java, следуя рекомендациям.

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

Это 2 возможных решения:

1)

    for(int i=0; i<s.length(); i++) {
        if(s.charAt(i) >= 48 && s.charAt(i) <= 57) {
            n++;
        }
    }

2)

    for(int i=0; i<s.length(); i++) {
        if(s.charAt(i) >= '0' && s.charAt(i) <= '9' ) {
            n++;
        }
    }

Какой из этих двух вариантов более «чистый» и соответствует лучшим практикам Java?

wyr0
источник
141
Почему вы пишете 48 и 57, когда на самом деле имеете в виду «0» и «9»? Просто напиши, что ты имеешь в виду.
Брандин
9
Подождите, что вы делаете, в Java есть VK_константы, которые вы должны использовать, во-вторых, использование кодов символов лучше, чем char. Java - это типобезопасный язык, который не должен выполнять перекрестную проверку типов. @Brandin Это называется практика кодирования
Мартин Баркер
12
Не удосужившись сделать больше, чем судить 6 человек, которые думали, что это хороший вопрос. Вы используете символы в качестве чисел? Если это так, используйте цифры. Вы используете это как письма? Если это так, используйте буквы.
Алек Тил
17
@MartinBarker VK_*Константы соответствуют ключам, а не символам .
CodesInChaos
2
Мне потребовалось несколько минут, чтобы определить, что этот код делает по отношению к вашему вопросу. Уже не ясно, потому что предполагается, что я знаю в (1), что я знаю, что это диапазон цифр ISO-Latin 1. Таким образом, это делает его проблематичным с точки зрения обслуживания.
CyberSkull

Ответы:

124

Оба ужасны, но первый ужаснее.

Оба игнорируют встроенную в Java возможность определять, какие символы являются «числовыми» (с помощью методов in Character). Но первый не только игнорирует природу Unicode строк, предполагая, что может быть только 0123456789, но также затеняет даже это недопустимое рассуждение, используя коды символов, которые имеют смысл, только если вы что-то знаете об истории кодировок символов.

Килиан Фот
источник
33
Почему вы предполагаете, что не отвергать не-ASCII цифры не так? Это зависит от контекста.
CodesInChaos
21
@CodesInChaos Если вы действительно хотите найти числовые символы, сканирование на 0123456789 совершенно неверно. Если вы действительно хотите сканировать только эти десять символов, то это по сути бессмысленные токены, которые только случайно выглядят знакомыми для людей, которые знают только ASCII / ISO-Latin. В этом нет ничего плохого - мне часто приходится делать именно это, например, взаимодействовать с устаревшим программным обеспечением, которое действительно принимает только эти десять символов. Но тогда вы должны прояснить свои намерения, используя что-то вроде matches("[0-9]+"), а не использовать исторически мотивированный трюк диапазона.
Килиан Фот
15
Существуют цифры полной ширины , которые выглядят так же, как цифры ASCII, и, как правило, требуется много программного обеспечения, чтобы принимать их вместо цифр ASCII. (Очевидно, что много программного обеспечения сломано, в зависимости от определения «много». Вы можете легко сказать, потому что поставщики программного обеспечения в одной стране считают невозможным продавать в другую страну, потому что поставщики не соблюдают требования других стран. )
rwong
37
I have a Japanese IME installed , and accidentally in full full width all all time..
BlueRaja - Дэнни Пфлюгофт
14
«Оба ужасны», но вы забыли сказать правильное решение ;-)
Кромстер говорит, что поддерживает Монику
163

Ни. Позвольте встроенному в Java классу Character понять это за вас.

for (int i = 0; i < s.length(); ++i) {
  if (Character.isDigit(s.charAt(i))) {
    ++n;
  }
}

Есть несколько больше диапазонов символов, чем цифры ASCII, которые считаются цифрами, и ни один из опубликованных вами примеров не будет их считать. JavaDoc для Character.isDigit()списков этих диапазонов символов , как быть действительные цифры:

Некоторые диапазоны символов Юникода, содержащие цифры:

  • цифры от \ u0030 до \ u0039, ISO-LATIN-1 (от 0 до 9)
  • '\ u0660' до '\ u0669', арабско-индийские цифры
  • '\ u06F0' до '\ u06F9', расширенные арабско-индийские цифры
  • '\ u0966' до '\ u096F', цифры деванагари
  • '\ uFF10' до '\ uFF19', цифры полной ширины

Многие другие диапазоны символов также содержат цифры.

При этом следует делегировать Character.isDigit()даже с этим списком. По мере заполнения новых плоскостей Unicode код Java будет обновляться. Обновление JVM может обеспечить беспроблемную работу старого кода с новыми символами. Это также СУХОЙ : локализуя код «это цифра» в одном месте, на которое есть ссылки в другом месте, можно избежать негативных аспектов дублирования кода (например, ошибок). Наконец, обратите внимание на последнюю строку: этот список не является исчерпывающим, и есть другие цифры.

Лично я предпочел бы делегировать основные библиотеки Java и тратить свое время на более производительные задачи, чем на «вычисление, что такое цифра».


Единственное исключение из этого правила - если вам действительно нужно проверять буквенные цифры ASCII, а не другие цифры. Например, если вы разбор поток и только ASCII цифр (в отличии от других цифр) имеет особое значение, то это было бы не целесообразно использовать Character.isDigit().

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


источник
4
Отличный ответ за предоставление чистого кода, который делает свое дело.
Пьер Арло
27

Если вы когда-нибудь напишите приложение на C, которое использует EBCDIC в качестве базового набора символов и нуждается в обработке символов ASCII, тогда используйте 48и 57. Ты это делаешь? Я так не думаю.

Об использовании isDigit(): это зависит. Вы пишете парсер JSON? Только 0к 9принимаются как цифры, так что не используйте isDigit(), проверьте >= '0'и <= '9'. Вы обрабатываете пользовательский ввод? Используйте isDigit()до тех пор, пока остальная часть вашего кода действительно может обработать строку и превратить ее в число правильно.

gnasher729
источник
3
На самом деле вы можете писать приложения на Java, который получает и возвращает EBCDIC. Это не весело.
Турбьерн Равн Андерсен
Подобное «не весело» проходило через код, который был написан с использованием десятичных значений символов EBCDIC при преобразовании его в кроссплатформенную среду ...
Гвин Эванс
1
Если вы обрабатываете данные EBCDIC в Java, вам, вероятно, следует преобразовать их в собственный кодировку UTF-16 Java, прежде чем обрабатывать их как символы. Но я думаю, что это действительно зависит от приложения; Надеемся, что если ваша программа имеет дело с EBCDIC, вы поймете, что нужно сделать.
Майкл Берр
1
Суть в том, что для обработки EBCDIC в Java и «0», и «48» неверны для определения нуля. Более современные, в C, C ++ и т. Д. '\ N' и '\ r' определяются реализацией, поэтому, если вы хотите обнаружить пару Windows CR / LF в файле с помощью компилятора, отличного от Windows, лучше проверить десятичные значения вместо проверка на наличие \ n и \ r.
gnasher729
12

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

Вы должны различать проверку определенного символа или проверку диапазона или класса символов.

1) Проверка на конкретного персонажа.

Для обычных символов используйте литеру, например, if(ch=='z').... Если вы проверяете наличие специальных символов, таких как табуляция или разрыв строки, вам следует использовать экранирование, например if (ch=='\n').... Если проверяемый вами символ необычен (например, не распознается сразу или недоступен на стандартной клавиатуре), вы можете использовать шестнадцатеричный код символа, а не буквальный символ. Но поскольку шестнадцатеричный код является «магическим значением», вы должны извлечь его в константу и задокументировать его:

const char snowman = 0x2603; // snowman char used to detect encoding issues
...
if (ch==showman)...

Шестнадцатеричные коды - это стандартный способ задания кодов символов.

2) Проверка класса персонажа или диапазона

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

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

JacquesB
источник
1
Я бы также рекомендовал записывать символьный литерал в шестнадцатеричном '\x2603'формате, используя вместо этого явное указание, что вы проверяете значение для символа с шестнадцатеричной кодировкой, а не просто для любого случайного числа.
wefwefa3
-4

Всегда лучше использовать, c >= '0'потому что c >= 48вам нужно конвертировать c в коде ascii.

Прем Патель
источник
3
Что означает этот ответ, что еще не было сказано в предыдущих ответах от недели назад?
-5

Регулярные выражения ( RegEx ) имеют специальный символьный класс для цифр, \dкоторый можно использовать для удаления любого другого символа из вашей строки. Длина полученной строки является желаемым значением.

public static int countDigits(String str) {
    str = Objects.requireNonNull(str).trim();

    return str.replaceAll("[^\\d]", "").length();
}

Обратите внимание, однако, что RegEx s в вычислительном отношении более требовательны, чем другие предложенные решения, поэтому они не должны быть в целом предпочтительными .

Стефано Брагалья
источник
Очень элегантный способ сделать чек!
Кевин Робатель
Для такой задачи
регулярные выражения излишни
2
@ StefanoBragaglia Перечитав ваш ответ, я думаю, что он не отвечает на этот вопрос.
Pharap
2
Ваш ответ предоставляет другой способ решения проблемы «как считать цифры в строке». Он не отвечает на основную проблему с примерами кода и представлением констант - в виде чисел или символов.
2
На самом деле это не подсчет цифр (он просто говорит вам, какова длина строки после того, как вы удалили все цифры, чего нет ни здесь, ни там), но я согласен, что на самом деле это не отвечает на вопрос. Как, например, никто не спрашивал об удалении символов из строк. Вопрос только в том, чтобы спросить о наилучшем способе проверки того, является ли персонаж числовым.
Doppelgreener