46

After a discussion with some my colleagues, I've a 'philosophical' question about how treat the char data type in Java, following the best practices.

Suppose a simple scenario (obviously this is only a very simple example in order to give a practice meaning to my question) where, given a String 's' as input, you have to count the number of numeric characters present in it.

These are the 2 possible solutions:

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++;
        }
    }

Which of the two is more 'clean' and compliant with the Java best practices?

  • 141
    Why would you write 48 and 57 when you actually mean '0' and '9'? Just write what you mean. – Brandin Nov 25 '15 at 15:07
  • 9
    Wait what are you doing, Java has the VK_ constants your supposed to use, secondly using char codes is better than char Java is a type safe language your not supposed to do cross-type checking. @Brandin It's called coding practices – Martin Barker Nov 25 '15 at 16:22
  • 12
    Without bothering to do more than judge the 6 people WHO THOUGHT THIS IS A GOOD QUESTION. Are you using chars as numbers? If so use numbers. Are you using it as letters? If so use letters. – Alec Teal Nov 26 '15 at 1:14
  • 17
    @MartinBarker The VK_* constants correspond to keys not characters. – CodesInChaos Nov 26 '15 at 9:46
  • 2
    It took me a few minutes to determine what this code does in relation to your question. Already it isn't clear because it assumes I know in (1) that I know this is the digit range of ISO-Latin 1. So this makes it problematic from a maintenance standpoint. – CyberSkull Nov 27 '15 at 10:56
124

Both are horrible, but the first is more horrible.

Both ignore Java's built-in capability to decide what characters are "numeric" (via methods in Character). But the first one not only ignores the Unicode nature of strings, assuming that there can be only 0123456789, it also obscures even this invalid reasoning by using character codes that make sense only if you know something about the history of character encodings.

  • 33
    Why are you assuming that non rejecting non-ASCII digits is wrong? That depends on context. – CodesInChaos Nov 25 '15 at 14:21
  • 21
    @CodesInChaos If you really want to find numeric characters, scanning for 0123456789 is plain wrong. If you actually do want to scan for only these ten characters, then they are essentially meaningless tokens which only accidentally look familiar for people who know only ASCII/ISO-Latin. There's nothing wrong with that - I often have to do precisely that, e.g. to interact with legacy software that really does only accept those ten characters. But then you should make your intentions clear by using something like matches("[0-9]+"), rather than exploit the historically motivated range trick. – Kilian Foth Nov 25 '15 at 14:27
  • 15
    There are full width digits, which look like the same as the ASCII digits, and in general a lot of software are required to accept them in place of ASCII digits. (Obviously a lot of software are broken, depending on the definition of "a lot of". You can easily tell because software vendors in one country find it impossible to sell to another country because the vendors do not honor the other countries' requirements.) – rwong Nov 25 '15 at 17:30
  • 37
    I have a Japanese IME installed, and accidentally type in full-width all the time. – BlueRaja - Danny Pflughoeft Nov 25 '15 at 22:49
  • 14
    "Both are horrible", but you forgot to say the right solution ;-) – Kromster says support Monica Nov 26 '15 at 5:47
163

Neither. Let Java's built-in Character class figure it out for you.

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

There are a few more character ranges than the ASCII digits that count as digits, and neither example you posted will count them. The JavaDoc for Character.isDigit() lists these character ranges as being valid digits:

Some Unicode character ranges that contain digits:

  • '\u0030' through '\u0039', ISO-LATIN-1 digits ('0' through '9')
  • '\u0660' through '\u0669', Arabic-Indic digits
  • '\u06F0' through '\u06F9', Extended Arabic-Indic digits
  • '\u0966' through '\u096F', Devanagari digits
  • '\uFF10' through '\uFF19', Fullwidth digits

Many other character ranges contain digits as well.

That being said, one should delegate to Character.isDigit() even with this list. As new Unicode planes are populated, the Java code will be updated. Upgrading the JVM could make old code work with new digit characters seamlessly. It is also DRY: by localizing the "is this a digit" code to one place referenced elsewhere, the negative aspects of code duplication (i.e. bugs) can be avoided. Finally, note the last line: this list is not exhaustive, and there are other digits.

Personally, I would rather delegate to the core Java libraries and spend my time on more productive tasks than "figuring what is a digit."


The only exception to this rule is if you really do need to test for the literal ASCII digits and not other digits. For example, if you are parsing a stream and only ASCII digits (as opposed to other digits) have special meaning, then it would not be appropriate to use Character.isDigit().

In that case, I would write another method, e.g. MyClass.isAsciiDigit() and put the logic in there. You get the same benefits of code reuse, the name is super-clear as to what it is checking, and the logic is correct.

  • 4
    Great answer for actually providing the clean code that does the trick. – Pierre Arlaud Nov 26 '15 at 9:17
27

If you ever write an application in C that uses EBCDIC as the basic character set and needs to process ASCII characters then use 48 and 57. Are you doing that? I don't think so.

About using isDigit(): it depends. Are you writing a JSON parser? Only 0 to 9 are accepted as digits, so don't use isDigit(), check for >= '0' and <= '9'. Are you processing user input? Use isDigit() as long as the rest of your code actually can handle the string and turn it into a number correctly.

  • 3
    Actually you can write applications in Java which gets and returns EBCDIC. This is no fun. – Thorbjørn Ravn Andersen Nov 27 '15 at 16:25
  • Similar 'not fun' was going through code that was written using the decimal values of the EBCDIC characters when converting it to a cross-platform environment... – Gwyn Evans Nov 28 '15 at 22:25
  • 1
    If you are processing EBCDIC data in Java then you should probably be converting it to the Java native UTF-16 charset before processing it as characters. But I guess that really depends on the application; hopefully if your program has to deal with EBCDIC, then you'll understand what needs to be done. – Michael Burr Nov 29 '15 at 10:36
  • 1
    Main point is that for processing EBCDIC in Java both '0' and 48 are wrong to detect a digit zero. More current, in C, C++ etc. '\n' and '\r' are implementation defined so if you want to detect a Windows CR/LF pair in a file using a non-windows compiler, better check the decimal values instead of checking for '\n' and '\r'. – gnasher729 Nov 29 '15 at 12:42
12

The second example is clearly superior. The meaning of the second example is immediately obvious when you look at the code. The meaning of the first example is only obvious if you have memorized the entire ASCII table in your head.

You should distinguish between checking for a specific character, or checking for a range or class of characters.

1) Checking for a specific character.

For ordinary characters, use the character literal, e.g., if(ch=='z').... If you check against special characters like tab or line break, you should use the escapes, like if (ch=='\n').... If the character you are checking for is unusual (e.g not immediately recognizable or not available on a standard keyboard), you might use a hex character code rather than the literal character. But since a hex code is a "magic value", you would extract it to a constant and document it:

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

Hex codes is the standard way of specifying character codes.

2) Checking for a character class or range

You really shouldn't be doing this directly in application code, but should encapsulate it in a separate class only concerned with character classification. And you should be vary of this, since libraries already exists for this purpose, and character classification is usually more complex than you think, at least if you consider characters outside the ASCII-range.

If you are only concerned about characters in the ASCII range, you could use character literals in this library, otherwise you would probably use hex-literals. If you look at the source code for the the Java builtin character library, it also refers to character values and ranges using hexadecimal, since this is how they are specified in the Unicode standard.

  • 1
    I would also recommend writing the character literal in hex using '\x2603' instead to be explicit that you are testing the value for a character with a hexadecimal encoding and not just any random number. – wefwefa3 Nov 28 '15 at 22:54
-4

It's always better to use c >= '0' because for c >= 48 you need to convert c in ascii code.

  • 3
    What does this answer state that was not already said in the previous answers from a week ago? – user22815 Dec 2 '15 at 4:00
-5

Regular Expressions (RegExs) have a specific character class for digits - \d - that can be used to remove any other character from your string. The length of the resulting string is the desired value.

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

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

Notice, however, that RegExs are computationally more demanding than the other proposed solutions therefore they should not be generally preferred.

  • Very elegant way to do the check! – Kevin Robatel Nov 27 '15 at 15:07
  • Regexes are overkill for a task like this – Pharap Nov 28 '15 at 19:36
  • 2
    @StefanoBragaglia After re-reading your answer I think it doesn't really answer the question. – Pharap Nov 29 '15 at 18:39
  • 2
    Your answer provides a different way of solving the problem of "how do I count digits in a string". It doesn't answer the underlying issue with the code samples and the representation of the constants - either as numbers or characters. – user40980 Nov 29 '15 at 23:34
  • 2
    This doesn't actually count the digits (it just tells you what the length of the string is after you've removed all the digits, which is neither here nor there), but I agree it doesn't actually answer the question. Like, for instance, nobody was asking about removing characters from strings. The question's just asking about the appropriate best-practice way to check whether a character's numeric. – doppelgreener Nov 30 '15 at 7:03

Not the answer you're looking for? Browse other questions tagged or ask your own question.