41

I have a String like "09a" and I need a method to confirm if the text is hexadecimal. The code I've posted does something similar, it verifies that a string is a decimal number. I want to do the same, but for hexadecimal.

    private static boolean isNumeric(String cadena) {
    try {
        Long.parseLong(cadena);
        return true;
    } catch (NumberFormatException nfe) {
        JOptionPane.showMessageDialog(null,"Uno de los números, excede su capacidad.");
        return false;
    }
}
Kasun Siyambalapitiya
  • 3,956
  • 8
  • 38
  • 58
Axel Vazquez
  • 411
  • 1
  • 4
  • 3

8 Answers8

38

Horrible abuse of exceptions. Don't ever do this! (It's not me, it's Josh Bloch's Effective Java). Anyway, I suggest

private static final Pattern HEXADECIMAL_PATTERN = Pattern.compile("\\p{XDigit}+");

private boolean isHexadecimal(String input) {
    final Matcher matcher = HEXADECIMAL_PATTERN.matcher(input);
    return matcher.matches();
}
Yury
  • 722
  • 7
  • 14
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • 2
    Your misquoting Effective Java, one should avoid exceptions to control logic flow. Provided incorrect hex strings are indeed an exceptional occurrence, then there is no reason not to use `parseLong` and catch the exception. Josh's book gives an example where the exception will always be thrown to control a loop, this is not really like that. Furthermore Josh's concerns are also to do with efficiency and readability, but you propose REGEX, which is both slower and less readable. – samthebest Aug 05 '14 at 11:50
  • 6
    Now (from my benchmarking) using your regex is a bit slower than using the `parseLong` assuming that the vast majority of the strings are actually correct. I actually agree that using exceptions might not be perfect, and there are better solutions out there, my main objection to your answer is that you suggest something that is worse than using exceptions. – samthebest Aug 05 '14 at 11:55
  • This will compile the regex each time it's checked, it's generally better to use a Pattern. – Czyzby Dec 14 '14 at 10:11
  • +1 for the only solution here that doesn't loop through the string characters, which I generally consider yucky from a purely aesthetic standpoint (even if it's more efficient) :) – Yury May 21 '20 at 03:21
  • 1
    Issue of compiling noted by @Czyzby fixed in answer by later edits. Enjoy! – Mark Stewart Aug 05 '20 at 13:49
  • @samthebest google banned exceptions on their development on Java and C++, therefore in Golang also no exceptions ) – Jurabek Azizkhujaev Jan 06 '21 at 14:36
  • 1
    @JurabekAzizkhujaev I can agree with that, but it would be much better to ban Java and C++ altogether ;). I'm sure Go probably has really nice libraries to handle exceptions some other way. In functional programming we use validation Monads, which is awesome. – samthebest Jan 07 '21 at 19:07
36

There's an overloaded Long.parseLong that accepts a second parameter, specifying the radix:

Long.parseLong(cadena,16);

As an alternative, you could iterate over the characters in the string and call Character.digit(c,16) on them (if any of them return -1 it's not a valid hexadecimal digit). This is especially useful if the string is too large to fit in a long (as pointed out in the comments, that would cause an exception if the first method is used). Example:

private static boolean isNumeric(String cadena) {
    if ( cadena.length() == 0 || 
         (cadena.charAt(0) != '-' && Character.digit(cadena.charAt(0), 16) == -1))
        return false;
    if ( cadena.length() == 1 && cadena.charAt(0) == '-' )
        return false;

    for ( int i = 1 ; i < cadena.length() ; i++ )
        if ( Character.digit(cadena.charAt(i), 16) == -1 )
            return false;
    return true;
}

BTW, I'd suggest separating the concerns of "testing for a valid number" and "displaying a message to the user", that's why I simply returned false in the example above instead of notifying the user first.

Finally, you could simply use a regular expression:

cadena.matches("-?[0-9a-fA-F]+");
mgibsonbr
  • 21,755
  • 7
  • 70
  • 112
  • As an alternative, you could iterate over the characters in the string and call [`Character.forDigit(c,16)`](http://docs.oracle.com/javase/6/docs/api/java/lang/Character.html#forDigit%28int,%20int%29) on them (if any of them return null - `'\0'` - it's not a valid hexadecimal digit). Or use a regular expression (though it would be more complicated and possibly perform worse). – mgibsonbr Jul 11 '12 at 02:53
  • 1
    The `Character.forDigit(..)` approach you specified is invalid. `Character.forDigit(..)` takes in an int and converts it to a char, not the other way around. – Kevin Wheeler Nov 06 '14 at 07:24
  • @KevinWheeler Thanks for the correction! The right method indeed is [`Character.digit(char, int)`](http://docs.oracle.com/javase/6/docs/api/java/lang/Character.html#digit%28char,%20int%29), assuming no characters outside BMP. In this case, a non-digit character would result in `-1` instead of null. – mgibsonbr Nov 06 '14 at 07:41
  • 1
    This will also throw an exception if the String is too long, which might not be desired (especially if the String is used to create a BigInteger, for example). – Czyzby Dec 14 '14 at 10:13
  • @JustACluelessNewbie Thanks for pointing that out! I moved my first comment (that does not have this problem) to the answer's body. – mgibsonbr Dec 14 '14 at 10:28
  • 1
    While we're at it, your isNumeric method returns false for Strings with "-" before the number, which might be valid negative numbers. Before the iteration, I'd put a check if the first char is a '-' and if yes, the iteration should start from the second index. It should also return false if '-' is the only character in the String. – Czyzby Dec 14 '14 at 11:30
  • 1
    Remark: `Long.parseLong("f7e511c46d1b8a03", 16)` won't work, which will fire NumberFormatException (invalid long). So iterate or regex could be better choices for bigger hexadecimals. – Junior Mayhé Dec 30 '15 at 12:33
19

Used this in my own code to check if string is a MAC address

boolean isHex = mac_addr.matches("^[0-9a-fA-F]+$");

My beef with the other answers provided in this thread is that if the String's length is large, it would throw an exception as well. Therefore, not very useful if you are testing if a MAC address consist of valid hexadecimals.

Don't be terrified of using regex!

laycat
  • 5,381
  • 7
  • 31
  • 46
  • 3
    This will compile the regex each time it's checked, it's generally better to use a Pattern. – Czyzby Dec 14 '14 at 10:11
13

Here some code for different options and execution time results (JDK 11):

execution time isHex1: 3670 ms
execution time isHex2: 3294 ms
execution time isHex3: 3946 ms
execution time regex: 31288 ms

Test code:

public class HexPerformanceTest {
    @Test
    public void testPerformance() {
        int count = 100000000;
        char[] chars = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
        };
        String regexString = new String(chars);

        Predicate<String> isHex1Test = testString -> {
            boolean isHex = true;
            for (char c: testString.toCharArray()) {
                if (!isHex1(c)) {
                    isHex = false;
                    break;
                }
            }
            return isHex;
        };

        Predicate<String> isHex2Test = testString -> {
            boolean isHex = true;
            for (char c: testString.toCharArray()) {
                if (!isHex2(c)) {
                    isHex = false;
                    break;
                }
            }
            return isHex;
        };

        Predicate<String> isHex3Test = testString -> {
            boolean isHex = true;
            for (char c: testString.toCharArray()) {
                if (!isHex3(c)) {
                    isHex = false;
                    break;
                }
            }
            return isHex;
        };

        Pattern pattern = Pattern.compile("^[0-9a-fA-F]+$");
        Predicate<String> regexTest = testString -> {
            Matcher matcher = pattern.matcher(regexString);
            return matcher.matches();
        };

        System.out.println("execution time isHex1: " + milliseconds(regexString, isHex1Test, count) + " ms");
        System.out.println("execution time isHex2: " + milliseconds(regexString, isHex2Test, count) + " ms");
        System.out.println("execution time isHex3: " + milliseconds(regexString, isHex3Test, count) + " ms");
        System.out.println("execution time regex: " + milliseconds(regexString, regexTest, count) + " ms");
    }

    private long milliseconds(String testString, Predicate<String> hexTest, int count) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            hexTest.test(testString);
        }
        return System.currentTimeMillis() - start;
    }

    private boolean isHex1(char c) {
        return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
    }

    private boolean isHex2(char c) {
        switch (c) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
                return true;
            default:
                return false;
        }
    }

    private boolean isHex3(char c) {
        return (Character.digit(c, 16) != -1);
    }
}
toongeorges
  • 1,844
  • 17
  • 14
10

Long.parseLong has a second form that takes a radix as its second argument.

private static boolean isHexNumber (String cadena) {
  try {
    Long.parseLong(cadena, 16);
    return true;
  }
  catch (NumberFormatException ex) {
    // Error handling code...
    return false;
  }
}
Tony
  • 3,470
  • 1
  • 18
  • 23
  • what happens if I call isHexNumber("1234567890abcdef1234567890abcdef") ? Will get some other exception even though it is a hex number, just too long for "long" – Gavriel Feb 18 '21 at 09:34
1

No-library approach

public static boolean isHexadecimal(String value)
{
    if (value.startsWith("-"))
    {
        value = value.substring(1);
    }

    value = value.toLowerCase();

    if (value.length() <= 2 || !value.startsWith("0x"))
    {
        return false;
    }

    for (int i = 2; i < value.length(); i++)
    {
        char c = value.charAt(i);

        if (!(c >= '0' && c <= '9' || c >= 'a' && c <= 'f'))
        {
            return false;
        }
    }

    return true;
}
Braden Steffaniak
  • 2,053
  • 3
  • 25
  • 37
1

Try this.

static boolean isHexadecimal(String s) {
    return s.chars()
        .skip(s.startsWith("-") ? 1 : 0)
        .allMatch(c -> "0123456789ABCDEFabcdef".indexOf(c) >= 0);
}

public static void main(String[] args) {
    System.out.println(isHexadecimal("-0e34a29Fb"));
    System.out.println(isHexadecimal("-ff-"));
    System.out.println(isHexadecimal("ef-"));
    System.out.println(isHexadecimal("efg"));
}

output:

true
false
false
false

You can omit .skip(s.startsWith("-") ? 1 : 0) if you do not allow the sign.

0

You can check any length text with below method practically.

public static boolean isHexadecimal(String text) {
    Objects.requireNonNull(text);
    if(text.length() < 1)
        throw new IllegalArgumentException("Text cannot be empty.");

    char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F' };

    for (char symbol : text.toCharArray()) {
        boolean found = false;
        for (char hexDigit : hexDigits) {
            if (symbol == hexDigit) {
                found = true;
                break;
            }
        }
        if(!found)
            return false;
    }
    return true;
}
Redwolf
  • 540
  • 4
  • 17
Warrior
  • 9
  • 2
  • 1
    If you want to check if the input is in hexadecimal format, you shouldn't trim it at the beginning. Also, your loop could fail faster if you just return false on the first occurrence of a non-hex character (there's probably some method to check for containment so you could do: `if (!hexDigits.Contains(symbol)) return false;`). – Oliver Jul 12 '12 at 11:03
  • This is not a very efficient way to check if a String is hexadecimal and the ternary operator in the return statement is redundant. – Czyzby Dec 14 '14 at 10:10