In Python, the best practice is to use exceptions for many more situations than is best practice in Java. In Python, you shoot first, ask questions later .... you seek forgiveness, not permission.... you ... {add cheesy analogy here....}
In Java, Exceptions are expensive. When an exception happens, the virtual machine needs to backtrack the call stack, often synchronizing on critical aspects of the runtime, perhaps bringing the entire application in to a locked state, while the exception is built. The Java compiled code is often inlined, and optimized, and the call stack for the actual optimized code has to be translated back to the source code, etc.
If you expect your input data to have invalid double input, then it is not an exceptional case to have invalid data, is it. In Java, you seek permission before having to seek forgiveness... so, you validate the data before doing the hard work.
The documentation for Double.valueOf(String)
even goes so far as to show you exactly how to validate an input String before parsing it... using a regular expression, and shows how to use it too.
So, let's compare two ways of parsing the values.... the forgiveness way:
private static final double parseForgiveness(String value) {
try {
return Double.parseDouble(value);
} catch (NumberFormatException nfe) {
return 0.0;
}
}
(Note, there's something important in there, I use double
and not Double
. Using primitives in Java when you can is almost always a better idea for performance than using the full Object version, like Double
).
So, the above system begs forgiveness when the data is broken.... what about prevalidating the number...? The code is basically copied from the Javadoc:
private static final double parsePermission(String value) {
if (DOUBLE_RE.matcher(value).matches()) {
return Double.parseDouble(value);
}
return 0.0;
}
where the DOUBLE_RE
is defined as:
private static final String Digits = "(\\p{Digit}+)";
private static final String HexDigits = "(\\p{XDigit}+)";
// an exponent is 'e' or 'E' followed by an optionally
// signed decimal integer.
private static final String Exp = "[eE][+-]?"+Digits;
private static final String fpRegex =
("[\\x00-\\x20]*"+ // Optional leading "whitespace"
"[+-]?(" + // Optional sign character
"NaN|" + // "NaN" string
"Infinity|" + // "Infinity" string
// A decimal floating-point string representing a finite positive
// number without a leading sign has at most five basic pieces:
// Digits . Digits ExponentPart FloatTypeSuffix
//
// Since this method allows integer-only strings as input
// in addition to strings of floating-point literals, the
// two sub-patterns below are simplifications of the grammar
// productions from section 3.10.2 of
// The Java Language Specification.
// Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt
"((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+
// . Digits ExponentPart_opt FloatTypeSuffix_opt
"(\\.("+Digits+")("+Exp+")?)|"+
// Hexadecimal strings
"((" +
// 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt
"(0[xX]" + HexDigits + "(\\.)?)|" +
// 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt
"(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" +
")[pP][+-]?" + Digits + "))" +
"[fFdD]?))" +
"[\\x00-\\x20]*");// Optional trailing "whitespace"
private static final Pattern DOUBLE_RE = Pattern.compile(fpRegex);
Note that a Pattern is compiled as a single static, and then used to match the String before actually parsing it. That seems like a lot of work, but consider a situation when about half the values are invalid in a String sequence.... how does it affect the performance?
I wrote a UBench tool a little while ago to test performance of these sorts of exercises. The first thing I do is write a function to generate an input array of 50% valid values, and 50% invalid. Here's that code:
private static final char[] alpha = " `1234567890-=~!@#$%^&*()_+qwertyuiop[]\\QWERTYUIOP{}|asdfghjkl;'ASDFGHJKL:\"zxcvbnm,./ZXCVBNM<>?".toCharArray();
private static final String wordRand(Random rand) {
int len = rand.nextInt(10) + 3;
char[] chars = new char[len];
IntStream.range(0, len).forEach(i -> chars[i] = alpha[rand.nextInt(alpha.length)]);
return new String(chars);
}
private static String[] buildInput(int size) {
List<String> values = new ArrayList<>();
Random rand = new Random(size);
for (int i = 0; i < size; i++) {
values.add(Double.toString(rand.nextDouble() * i));
values.add(wordRand(rand));
}
return values.toArray(new String[values.size()]);
}
That buildInput
method will return an array of Strings where half the values are guaranteed valid float Strings, and the other half are almost certainly not valid (thought it's possible they may be). Then, I run it through a comparative benchmark using the following code:
private static final double[] parseAll(String[] values, ToDoubleFunction<String> parser) {
return Stream.of(values).mapToDouble(parser).toArray();
}
public static void main(String[] args) {
UUtils.setStandaloneLogging(Level.INFO);
String[] values = buildInput(1000);
UBench bench = new UBench("Parsing");
bench.addTask("Permission", () -> parseAll(values, DoubleParser::parsePermission));
bench.addTask("Forgiveness", () -> parseAll(values, DoubleParser::parseForgiveness));
bench.press(1000).report();
}
That's complicated, I guess, but really it sets up two tests, one which parses 2000 input values using the forgiveness method, the other test uses the permission method. It runs each test 1000 times, and then takes some measurements of each run, and reports on it. The report, on my computer, looks like:
INFO 2015-08-07 12:13:03.058 net.tuis.ubench.UBench(<init>): Creating UBench for suite Parsing
INFO 2015-08-07 12:13:06.860 net.tuis.ubench.UBench(press): UBench suite Parsing: completed benchmarking all tasks using mode INTERLEAVED in 3785.000ms
Task Parsing -> Permission: (Unit: MILLISECONDS)
Count : 1000 Average : 1.1980
Fastest : 1.0339 Slowest : 26.8094
95Pctile : 1.3693 99Pctile : 2.9096
TimeBlock : 1.702 1.265 1.143 1.176 1.073 1.086 1.168 1.194 1.076 1.096
Histogram : 977 17 5 0 1
Task Parsing -> Forgiveness: (Unit: MILLISECONDS)
Count : 1000 Average : 2.5568
Fastest : 2.3117 Slowest : 12.5496
95Pctile : 2.8740 99Pctile : 5.5604
TimeBlock : 3.054 2.539 2.522 2.645 2.411 2.471 2.470 2.591 2.419 2.446
Histogram : 978 20 2
What that means, is that the average time to parse 2000 values using the regex is 1.20 milliseconds, and using the exception system is 2.56 milliseconds - more than twice as slow.
Additionally the fastest parse is 1.03 milliseconds, vs. 2.31 - again more than twice as slow.
Bottom line is that in Java (unlike Python), you should not use exceptions to handle data validation when you expect data to regularly be invalid....
parseDouble
) \$\endgroup\$