Naming and method decomposition
Let's take a look at this method signature:
public static String convertNumbersToWords(final String input) {
The method name suggests converting numbers... And it works with inputs like "1234". It's a bit unnatural that the input is a string, as opposed to a numeric type, and "1234" is one number, not plural numbers.
In addition, the method does two things:
- validate the input consists of digits only
- convert the digits to
SHOUT_CASE
It would be better to decompose this to two methods, for example:
private static String convertValidatedDigitsToWords(final String digits) {
return digits.chars()
.mapToObj(ShoutySnake::getWordFromCharCode)
.collect(Collectors.joining("_"));
}
public static String convertDigitsToWords(final String input) {
if (input == null || !input.matches("\\d*")) {
throw new IllegalArgumentException("input cannot be null or non-numerical.");
}
return convertValidatedDigitsToWords(input);
}
... and perhaps add an overloading for numeric input:
public static String convertDigitsToWords(final int number) {
if (number < 0) {
throw new IllegalArgumentException("input must be non-negative");
}
return convertValidatedDigitsToWords(Integer.toString(number));
}
A small related tip: String.matches
implies ^
and $
, no need to include that in the pattern.
Optimizing for repeated calls
If you expect the method to be called repeatedly,
then it would be good to compile the regular expression.
private static final Pattern pattern = Pattern.compile("\\d*");
public static String convertDigitsToWords(final String input) {
if (!pattern.matcher(input).matches()) {
throw new IllegalArgumentException("input must be non-null and non-negative.");
}
return convertValidatedDigitsToWords(input);
}
Magic number 48
In this code, 48 is a magic number:
private static String getWordFromCharCode(int code){
return words[code - 48];
}
You can easily make it less magic by referring to '0'
instead:
private static String getWordFromCharCode(int code) {
return words[code - '0'];
}