Tuesday, April 14, 2009

No Excuse for Ignorance

Java's ArrayList and Scanner classes are two that anyone writing in Java should know about, or else they're stupid.

The following two atrocities are summaries of actual code I had to review, the first at work, the second a submission in a programming competition.

What's an ArrayList?


Inexcusable ignorance:

int size = 0;

int optAIndex = -1;
if (weNeedOptA)
optAIndex = size++;

int optBIndex = -1;
if (weNeedOptB)
optBIndex = size++;

int optCIndex = -1;
if (weNeedOptC)
optCIndex = size++;

String[] options = new String[size];

if (optAIndex != -1)
options[optAIndex] = "-a";

if (optBIndex != -1)
options[optBIndex] = "-b";

if (optCIndex != -1)
options[optCIndex] = "-c";

return options;


Enlightened version:


ArrayList<String> options = new ArrayList<String>();

if (weNeedOptA)
options.add("-a");

if (weNeedOptB)
options.add("-b");

if (weNeedOptC)
options.add("-c");

return options.toArray(new String[options.size()]);

The concept that this code is accomplishing is really pretty basic: add options to an array as we need them. That's just what ArrayList's are for. The ignorant solution uses 18 lines to solve an 8-line problem, and that's the reduced version of the actual code that I've had to deal with.

The toArray(T[]) function is the hardest part of the class to learn, but even the programmers who just copy and paste code they don't understand should be able to pick up that one-liner pretty easily.

If you think you know how to program in Java and you don't know about the ArrayList class, then what you really know is how to be a pain in the neck to other programmers.

The ArrayList api consists of 3 important methods. The constructor, add(T), and toArray(T[]). Don't click on the links, just look at what I did in the example, and that's all you need to know.

Now use it!

I Can Read Numbers All by Myself!


Inexcusable ignorance:

String numberStr = "";
int nextChar = System.in.read();
while (nextChar != -1) { // check for EOF
char c = (char)nextChar;
if ('0' <= c && c <= '9') // check for whitespace
numberStr += c;
else
break; // end of number
nextChar = System.in.read();
}
int number = Integer.parseInt(numberStr);


Enlightened version:


Scanner scanner = new Scanner(System.in);
int number = scanner.nextInt();

Two lines!! The task we're trying to perform is to read a number from standard input. Really, did you not think that someone else ran into this problem before you did? The first line wraps a scanner around standard input, so you can read from it many times successively. Really the enlightened version is a 1-line solution with a line for setup.

There's another issue here as well, folks. The inexcusable ignorance can't read negative numbers. The first time through the while loop breaks because '-' is not in the range [0-9]. Then the Integer.parseInt() call will throw a NumberFormatException because numberStr is still the empty string. This was a real submission to a programming competition I helped run, and the negative number issue is the reason the participant got this question wrong.

Not only is the bad solution bulky, it's broken. Use a Scanner!

The Scanner api has lots of nice functions for you. The code below outlines what I've found to be useful constructors and methods:

// reads from a text file
Scanner file = new Scanner(new File("<filepath>"));
while (file.hasNextLine()) {
String line = file.nextLine();
// ...
}

// reads through a string
Scanner scanner = new Scanner("123 hello 6.67e-11");
int number = scanner.nextInt(); // 123
String token = scanner.next(); // "hello" stops at whitespace
double gravity = scanner.nextDouble(); // Newton's constant
boolean done = !scanner.hasNext(); // true

I hope you, the reader, agree that knowledge these two classes and their api's are an absolute minimum requirement for Java programmers. Please do not put the rest of us through the pain of dealing with their absence in your code.

3 comments:

Roy van de Water said...

Does anybody really read Java input's char by char? Even lower level languages usually have a function that does it for you.

thejoshwolfe said...

Exactly my point @DopplerDeffect. There's no excuse to do that in any language. Scanner is how to avoid it in Java.

Anonymous said...

Well just a few hours ago I used the Buffered Reader(actually just copied) and it worked but then mister JWolfe bites my head off for not using Scanner, I shoulda finished reading the post....
and yes I do have real blog not just Twitter.