Showing posts with label java5. Show all posts
Showing posts with label java5. Show all posts

Friday, May 23, 2008

PECS in Action

One funny bit at JavaOne was Josh Bloch introducing the mnemonic PECS. Well actually what was most funny about it was the picture of Arnold Schwarzenegger that accompanied it... Anyways, PECS stands for producer-extends, consumer-super. Instead of repeating what Josh said or plagiarizing Effective Java, I will give an example of how PECS helped me out yesterday.

One there was an API that existed back in olden times, before Java 1.5 It looked like this:

void runInboundCycles(final Module[] modules)

There was also a runOubound, but you get the picture. The class Module is an interface that has many implementations. This API got tweaked courtesy of Java 1.5:

void runInboundCycles(final List<Module> modules);

Before the change you could do this:

runInboundCycles(new Module[] { new MyModule() } );

A logical uprev would be:

runInboundCycles(Arrays.asList(new MyModule()));

Turns out that won't compile! The Arrays.asList call will return a List and the Java compiler says that is not a List. So instead you have to do something annoying like:

List<Module> modules = new ArrayList<Module>(1);
modules.add(new MyModule());
runInboundCycles(modules);

This is particularly annoying if modules is actually a member variable, as now you cannot declare it to be final. Enter PECS.

My API is using the Modules, thus my parameter is a producer to the API. Remember producer-extends, so refactor the API like this:

void runInboundCycles(List<? extends Module> modules);

Now you can pass a List<MyModule> and the compiler won't complain.

There are a couple of things about this that bother me. When my crazy brain looks at the API, it thinks "the API consumes Modules." Maybe that's just me. The other thing that bothers me is writing ? extends Module because Module is an inteface. Now granted it would suck to have to write ? implements Module just because Module is an interface and write ? extends Module just because Module was a class, so I am not advocating the altnernative. It just feels weird to write extends in front of an interface type. Maybe I have been programming in Java too long.

Monday, January 29, 2007

Scanner

I was recently helping somebody who is taking "beginning Java" kind of class. It's interesting to see Java as a first language, especially Java 5+. Java is unusual as a first language because of its object orientation. I think Pascal, Basic, or C are probably better choices. They're much less useful choices in the long run, I suppose. Java 5 as a first language is an even stranger choice, because of all that syntactic sugar...

For example, the Java student in question needed to write a program that accepted user input. Enter Java 5's java.util.Scanner. This thing is like C's scanf, but an OOP version of scanf. It's not quite as easy as C++'s cin, but you just can't do cin in Java because you can't overload the operator like you can in C++. I guess reading user input is one that is dramatically more difficult in Java than C++, well at least before Scanner.

One weird thing with Scanner is that it breaks on all whitespace by default. So don't use it's next() method to read strings that might have spaces in them. Instead use nextLine(). It accepts a regular expression for it's delimiter. That's pretty novel. Of course it means that if you want to use something other than the default delimiter, you'll need to understand Java's overly verbose version of regular expressions.

So I'd still prefer Pascal or C as a first language for a new programming, but Java has become more accommodating at least for beginners. The "old way" of reading lines of input was always something like:

String str = null;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
str = br.readLine();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}

Talk about ugly! Now with Scanner:

String str = null;
Scanner scanner = new Scanner(System.in);
str = scanner.nextLine();

Or even:

String str = new Scanner(System.in).nextLine();

One of the key things to notice here is that you don't have to catch an IOException. Java has finally started being smarter with exceptions. Scanner's exceptions are not checked, so you don't have to catch them. There's no point in catching exceptions that you can't do anything about. If your program is unable to get input from the command line, it's not like you're going to be able to recover.