Well, it's finally time to get serious and work out the remaining kinks in my understanding of interfaces when they meet Java 5 generics (also referred to as "parameterized types"). It is all NetWare's fault for me only getting around to this now: I've only had the opportunity recently to begin writing Java 5 code because my code had to support (run on) NetWare, which Novell locked into Java 1.4. I'll try to pick some reasonable examples, but to the extent that they're semantically stupid, please forgive me. Where interfaces get entangled with generics is in the notion of a collection, list or other "dump-truck full of it" sort of classes. |
|
Eclipse tip: Debugging through library codeThe runtime classpath is different from the build path. In debugging, you may have the library source attached correctly, but the launch configuration referring to a different JAR for the classes in the library. Double check that the build path and the launch configuration are the same. If the library in question is yours, check out JARs and Eclipse Build Path to give the best chance of being able to debug down inside the source code.
Adjust the debug configuration by right-clicking on the Java source to debug,
|
Some useful (formal) terminology...
Now, when a baby Java coder, I long did this:
public interface Napping { public void setAudioLevel( int level ); public void setSnoreStyle( int style ); public void setDisturbThreshold( int threshold ); }
...and the implementation is a simple thing.
(Actually, here I'm not over-exemplifying the real power of Java interfaces: I should show a great deal more. Real power lies more in that I could have this behavior of Napping and implement it in a class called Cat that otherwise inherits the characteristics of a super class named Felines. The reason I might choose to make Napping an interface is because I might want to use it for class Human as napping is a behavior of humans also. However, today, we're not really talking about the power of Java interfaces, but about corrupting them usefully, whether it's on a computer or an O2 Dell Streak Mobile broadband, with generics.)
But, when you mix Java 5 generics into it, it gets complicated.
public interface Stuff< I > { public void add( I item ); public void delete( I item ); public int countItems(); public String toString(); ...etc. }
where I can be a String, for example, and I can actually use this "now-magic" generic type-replacement thingy down inside a method definition. Wow. It's like an abstract class on steroids and a high wire act. Cool. I've thought about generics as something to be endured, not taken advantage of. I'll begin to look for reasons to use this "new word" every day now.
So, grammatically, Java interfaces can be loosely defined thus:
[ public ] interface name [ < x [, y [, ... ] ] > ] { variables and methods... }
However, the implementation gets tricky, it works like this. If you create a new class (let's use Eclipse) stubbed out that implements interface Stuff, you get this:
public class PurpleStuff implements Stuff { public void add( Object item ) { ... } public void delete( Object item ) { ... } public int countItems() { ... } public String toString() { ... } }
Now, if you've got warnings on javac just right, it will warn you that:
Stuff is a raw type. References to generic type Stuff< I > should be parameterized.
You can implement PurpleStuff in mere terms of Object (add this type in angle brackets to Stuff and the warning goes away), but you might rather prefer to determine what Stuff can be in one or more "real" implementations.
As practicality's looking for an implementation dedicated to a specific type, choose a more tangible type. For example, if all my stuff are things expressable in String form, then:
import java.util.List; import java.util.ArrayList; import com.etretatlogiciels.samples.Stuff; public interface MyStuff implements Stuff< String > { List< String > mystuff = new ArrayList< String >(); public void add( String item ) { mystuff.add( item ); } public void delete( String item ) { mystuff.remove( item ); } public int countItems() { return mystuff.size(); } public String toString() { return mystuff.toString(); } }
I think if the interface you've conjured up is complicated in its conception, implementations could get quickly very ugly.
For the purposes of this exercise, I decided to multiple the code a lot and try things out to see how they are used or can't be used. Please refer to the following bunch of interfaces and classes when looking at the last segment of code. (Take the package name as written at the top for all this code and, let's lose the toString() method.)
package com.etretatlogiciels.samples; // ---------------------------------- public interface Stuff< I > { public void add( I item ); public void delete( I item ); public int count(); } // ---------------------------------- public class StuffThatsPurple implements Stuff< Object > { public void add( Object item ) { return; } public void delete( Object item ) { return; } public int count() { return 0; } } // ---------------------------------- import java.util.List; import java.util.ArrayList; import com.etretatlogiciels.samples.Stuff; public class MyStuff implements Stuff< String > { List< String > mystuff = new ArrayList< String >(); public void add( String item ) { mystuff.add( item ); } public void delete( String item ) { mystuff.remove( item ); } public int count() { return mystuff.size(); } } // ---------------------------------- public class StuffThatsDerived< I > { public void add( Object item ) { return; } public void delete( Object item ) { return; } public int count() { return 0; } }
Based on what's shown above, let me draw your attention to the following code that attempts to use it. You should spot several lines that will not compile up front. See my remarks, line by line, afterward.
package com.etretatlogiciels.samples; import com.etretatlogiciels.samples.Stuff; import com.etretatlogiciels.samples.MyStuff; import com.etretatlogiciels.samples.StuffThatsPurple; import com.etretatlogiciels.samples.StuffThatsDerived; import org.apache.commons.httpclient.HttpClient; public class StuffFun { static void main( String[] args ) { Stuff stuff = new Stuff(); // error MyStuff myStuff = new MyStuff(); StuffThatsPurple purple = new StuffThatsPurple(); StuffThatsPurple< String > purpleString = new StuffThatsPurple< String >(); // error StuffThatsDerived< String > derivedString = new StuffThatsDerived< String >(); StuffThatsDerived< Long > derivedLong = new StuffThatsDerived< Long >(); StuffThatsDerived< HttpClient > derivedHttpClient = new StuffThatsDerived< HttpClient >(); } }
Multiple errors occur here, but the most important one is that Stuff is an interface and you cannot instantiate an interface any more than you can instantiate an abstract class in Java.
Here is MyStuff, instantiated and read for use, but it is a string type.
StuffThatsPurple is not generic, so it can't be parameterized explicitly with String.
But, we've created a new class, StuffThatsDerived as defined above, that does endure parameterization. It is neither interface nor does it implement an interface, but it demonstrates the free-flowing possibilities with generics that are possible (although it's important that the implementation actually handled whatever is necessary for the types its consumer would be likely to throw at it, witness HttpClient.
There are times when all of this generic stuff doesn't help because the compiler simply can't tell the type. Remember, generics is the promise that if you compile without error or warning (with respect to these type issues), your code is guaranteed to run no matter what the type.
String QUERY = "from DvdTitle title"; Query query = session.createQuery( QUERY ); for( Iterator< DvdTitle > it = ( Iterator< DvdTitle > ) query.iterate(); it.hasNext(); ) { DvdTitle title = ( DvdTitle ) it.next(); System.out.println( "Got id = " + title.getId() + ", title = " + title.getTitle() ); }
In the preceding code, a warning arises for query.iterate() :
Type safety: Unchecked cast from Iterator to IteratorHibernateTest.java
Joshua Bloch says there is nothing to do but suppress the warning. But, we can do it in such a way as to broadcast widely why we are and that we know what we are doing. Constrain the warning-suppressed code to a small, separate method:
/** * Cast Hibernate-returned iterator in one place. * * @param <X> - the cooked type; in our case, Iterator< DvdTitle >. * @param o - the raw object in need of a cast. * @return back a cooked object with actual type. */ @SuppressWarnings( "unchecked" ) private static final < X > X cast( Object o ) { return ( X ) o; }
Whereupon the consuming code changes to this:
String QUERY = "from DvdTitle title"; Query query = session.createQuery( QUERY ); System.out.println( "Querying records..." ); for( Iterator< DvdTitle > it = cast( query.iterate() ); it.hasNext(); ) { DvdTitle title = ( DvdTitle ) it.next(); System.out.println( "Got id = " + title.getId() + ", title = " + title.getTitle() ); }
There is nothing that says you must following this convention, however, it's what you'll commonly see:
Entry is an "inner class" to Map. The period is how you resolve which entry you are talking about. Inner classes can be first-class classes, but because they share the .java file, they share the .class file too. They must be referenced with the outer class name, hence "Map.Entry". See http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.Entry.html.