The Joy of Generics
Okay, Generics are old news. But since NetBeans was still targetting JDK 1.4 until a few months ago, and most of my coding is NetBeans plug-ins, I'm just starting to really appreciate them. In particular, I'm finding they have a very nice effect on the things I choose to write and how I write them.
Of course the basics are quite convenient - just being able to eliminate casts and get type checking on uses of collections is handy. But what I find I am doing that's very useful is making different choices about what to write and how to write it.
As a case in point, this week I wrote a little standalone Bézier Spline editor - a project I sort of distracted myself into - I needed to design a very precise GeneralPath as part of some tweaks to NetBeans popup windows' appearance on Mac OS. So rather than sit around with graph paper, I wrote a little app that would let me draw what I needed around a screen shot, and spit out the source code for a corresponding GeneralPath object.
Anyway, since I still have some plans for the Imagine project, I was thinking about how best to design an image editor, and wanted to do some experimenting that might be useful later. What I ended up with is a concept where there are EditingModes, and an EditingMode makes available a set of tools that should be shown in a toolbar and a menu. The user can select editing modes, and the user can select tools (and also EditingModes have lists of actions of their own).
So I've got three things that need basically the same thing - each has a list of objects that share a type. For each list of objects, I need a corresponding list of toggle buttons and menu items. But the list contents are all different types.
This is where generics come to the rescue - in the past, I probably wouldn't have thought to take this approach, simply because all of the casting required would be ugly, and it wouldn't be terribly type-safe. What I did is create a single class with a generic type; its constructor takes an argument of a 3 method interface which must have the same generic type. So all of the logic about manufacturing menu items and such is shared; I just implement the Logic interface for each one and pass it to the constructor and I'm done. The Logic interface handles fetching the display name of an object from the list, fetching the icon for the object, and performing whatever action should be performed when the menu item or button is invoked.
The point here isn't that the code is wildly beautiful - but that it's really enjoyable to find a technique that changes the way you think about what you can do. In the past, what I would have written to do this would be either uglier, or there simply would have been three cut-and-paste implementations, depending on how expedient I was being. So I'm finding that generics are adding a bit of extra fun into programming - I can have more logic be, uh, generic, and write less boilerplate as a result. To illustrate, here's what I mean:
<b style="color: #000099; font-family: Monospaced; font-weight: bold">package</b> pathdesign.app;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">import</b> java.awt.event.ActionEvent;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">import</b> java.util.ArrayList;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">import</b> java.util.List;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">import</b> javax.swing.AbstractAction;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">import</b> javax.swing.Action;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">import</b> javax.swing.ButtonGroup;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">import</b> javax.swing.Icon;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">import</b> javax.swing.JMenuItem;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">import</b> javax.swing.JToggleButton;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">class</b> ActionMapper <T <b style="color: #000099; font-family: Monospaced; font-weight: bold">extends</b> Object> {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> List <T> objects;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">final</b> Logic logic;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> T selection;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">interface</b> Logic <T> {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">void</b> <b style="font-family: Monospaced; font-weight: bold">invoke</b> (T t);
String <b style="font-family: Monospaced; font-weight: bold">getDisplayName</b> (T t);
Icon <b style="font-family: Monospaced; font-weight: bold">getIcon</b> (T t);
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> <b style="font-family: Monospaced; font-weight: bold">ActionMapper</b>(List <T> objects, Logic<T> invoker, T initialSelection) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">this</b>.objects = objects == <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b> ? <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> ArrayList <T> () :
<b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> ArrayList<T>(objects);
<b style="color: #000099; font-family: Monospaced; font-weight: bold">this</b>.selection = initialSelection == <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b> ? objects.<b style="font-family: Monospaced; font-weight: bold">size</b>() > <b style="color: #780000">0</b> ?
objects.<b style="font-family: Monospaced; font-weight: bold">get</b>(<b style="color: #780000">0</b>) : <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b> : initialSelection;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (initialSelection == <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">throw</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> <b style="font-family: Monospaced; font-weight: bold">NullPointerException</b> (<b style="color: #99006b">"Can't determine initial selection"</b>);
}
selection = initialSelection;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">this</b>.logic = invoker;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (invoker == <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">throw</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> <b style="font-family: Monospaced; font-weight: bold">NullPointerException</b> (<b style="color: #99006b">"Invoker null"</b>); <b style="color: #737373">//NOI18N</b>
}
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">interface</b> MapperAction <T> <b style="color: #000099; font-family: Monospaced; font-weight: bold">extends</b> Action {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> T <b style="font-family: Monospaced; font-weight: bold">getItem</b>();
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">boolean</b> <b style="font-family: Monospaced; font-weight: bold">setObjects</b> (List <T> objects, T selection) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (!<b style="color: #000099; font-family: Monospaced; font-weight: bold">this</b>.objects.<b style="font-family: Monospaced; font-weight: bold">equals</b>(objects)) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">this</b>.objects = <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> ArrayList<T>(objects);
<b style="font-family: Monospaced; font-weight: bold">setSelection</b> (selection);
buttons = <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>;
actions = <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>;
menuItems = <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">true</b>;
} <b style="color: #000099; font-family: Monospaced; font-weight: bold">else</b> {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> <b style="font-family: Monospaced; font-weight: bold">setSelection</b> (selection);
}
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> List <T> <b style="font-family: Monospaced; font-weight: bold">getObjects</b>() {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> ArrayList <T> (objects);
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">boolean</b> inSetSelection = <b style="color: #000099; font-family: Monospaced; font-weight: bold">false</b>;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">boolean</b> <b style="font-family: Monospaced; font-weight: bold">setSelection</b> (T selection) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (inSetSelection) <b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">false</b>;
inSetSelection = <b style="color: #000099; font-family: Monospaced; font-weight: bold">true</b>;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">try</b> {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (!objects.<b style="font-family: Monospaced; font-weight: bold">contains</b>(selection)) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">throw</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> <b style="font-family: Monospaced; font-weight: bold">IllegalArgumentException</b> (<b style="color: #99006b">"I don't know about "</b> <b style="color: #737373">//NOI18N</b>
+ selection);
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">boolean</b> result = !selection.<b style="font-family: Monospaced; font-weight: bold">equals</b> (<b style="color: #000099; font-family: Monospaced; font-weight: bold">this</b>.selection);
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (result) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">this</b>.selection = selection;
<b style="font-family: Monospaced; font-weight: bold">updateButton</b>(selection);
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> result;
} <b style="color: #000099; font-family: Monospaced; font-weight: bold">finally</b> {
inSetSelection = <b style="color: #000099; font-family: Monospaced; font-weight: bold">false</b>;
}
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> T <b style="font-family: Monospaced; font-weight: bold">getSelection</b>() {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> selection;
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> List <MapperAction<T>> actions;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> List <MapperAction<T>> <b style="font-family: Monospaced; font-weight: bold">getActions</b>() {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (actions == <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>) {
actions = <b style="font-family: Monospaced; font-weight: bold">createActions</b>();
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> actions;
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> List <JToggleButton> buttons;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> List <JToggleButton> <b style="font-family: Monospaced; font-weight: bold">getButtons</b>() {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (buttons == <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>) {
buttons = <b style="font-family: Monospaced; font-weight: bold">createButtons</b>();
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> buttons;
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> List <JMenuItem> menuItems;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> List <JMenuItem> <b style="font-family: Monospaced; font-weight: bold">getMenuItems</b>() {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (menuItems == <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>) {
menuItems = <b style="font-family: Monospaced; font-weight: bold">createMenuItems</b>();
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> menuItems;
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> List <MapperAction <T>> <b style="font-family: Monospaced; font-weight: bold">createActions</b>() {
List <MapperAction<T>> result = <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> ArrayList <MapperAction<T>> (objects.<b style="font-family: Monospaced; font-weight: bold">size</b>());
<b style="color: #000099; font-family: Monospaced; font-weight: bold">for</b> (T item : objects) {
MapperAction <T> action = <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> MapperActionImpl <T>(item);
action.<b style="font-family: Monospaced; font-weight: bold">putValue</b>(Action.NAME, logic.<b style="font-family: Monospaced; font-weight: bold">getDisplayName</b> (item));
result.<b style="font-family: Monospaced; font-weight: bold">add</b> (action);
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> result;
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> List <JToggleButton> <b style="font-family: Monospaced; font-weight: bold">createButtons</b>() {
List <JToggleButton> result = <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> ArrayList <JToggleButton> (objects.<b style="font-family: Monospaced; font-weight: bold">size</b>());
ButtonGroup grp = <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> <b style="font-family: Monospaced; font-weight: bold">ButtonGroup</b>();
<b style="color: #000099; font-family: Monospaced; font-weight: bold">for</b> (MapperAction<T> action : <b style="font-family: Monospaced; font-weight: bold">getActions</b>()) {
JToggleButton button = <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> <b style="font-family: Monospaced; font-weight: bold">JToggleButton</b>();
button.<b style="font-family: Monospaced; font-weight: bold">setAction</b> (action);
button.<b style="font-family: Monospaced; font-weight: bold">setIcon</b> (logic.<b style="font-family: Monospaced; font-weight: bold">getIcon</b> (action.<b style="font-family: Monospaced; font-weight: bold">getItem</b>()));
result.<b style="font-family: Monospaced; font-weight: bold">add</b> (button);
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (<b style="font-family: Monospaced; font-weight: bold">getSelection</b>() == action.<b style="font-family: Monospaced; font-weight: bold">getItem</b>()) {
button.<b style="font-family: Monospaced; font-weight: bold">setSelected</b>(<b style="color: #000099; font-family: Monospaced; font-weight: bold">true</b>);
}
grp.<b style="font-family: Monospaced; font-weight: bold">add</b> (button);
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> result;
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> List <JMenuItem> <b style="font-family: Monospaced; font-weight: bold">createMenuItems</b>() {
List <JMenuItem> result = <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> ArrayList <JMenuItem> (objects.<b style="font-family: Monospaced; font-weight: bold">size</b>());
ButtonGroup grp = <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> <b style="font-family: Monospaced; font-weight: bold">ButtonGroup</b>();
<b style="color: #000099; font-family: Monospaced; font-weight: bold">for</b> (MapperAction<T> action : <b style="font-family: Monospaced; font-weight: bold">getActions</b>()) {
JMenuItem button = <b style="color: #000099; font-family: Monospaced; font-weight: bold">new</b> <b style="font-family: Monospaced; font-weight: bold">JMenuItem</b>();
button.<b style="font-family: Monospaced; font-weight: bold">setAction</b> (action);
button.<b style="font-family: Monospaced; font-weight: bold">setIcon</b> (logic.<b style="font-family: Monospaced; font-weight: bold">getIcon</b> (action.<b style="font-family: Monospaced; font-weight: bold">getItem</b>()));
result.<b style="font-family: Monospaced; font-weight: bold">add</b> (button);
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (<b style="font-family: Monospaced; font-weight: bold">getSelection</b>() == action.<b style="font-family: Monospaced; font-weight: bold">getItem</b>()) {
button.<b style="font-family: Monospaced; font-weight: bold">setSelected</b>(<b style="color: #000099; font-family: Monospaced; font-weight: bold">true</b>);
}
grp.<b style="font-family: Monospaced; font-weight: bold">add</b> (button);
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> result;
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">void</b> <b style="font-family: Monospaced; font-weight: bold">updateButton</b> (T t) {
JToggleButton b = <b style="font-family: Monospaced; font-weight: bold">buttonFor</b> (t);
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (b != <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>) {
b.<b style="font-family: Monospaced; font-weight: bold">setSelected</b> (<b style="color: #000099; font-family: Monospaced; font-weight: bold">true</b>);
}
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> JToggleButton <b style="font-family: Monospaced; font-weight: bold">buttonFor</b> (T t) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (buttons != <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">for</b> (JToggleButton button : buttons) {
MapperAction<T> action = (MapperAction<T>) button.<b style="font-family: Monospaced; font-weight: bold">getAction</b>();
<b style="color: #000099; font-family: Monospaced; font-weight: bold">if</b> (action.<b style="font-family: Monospaced; font-weight: bold">getItem</b>().<b style="font-family: Monospaced; font-weight: bold">equals</b> (t)) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> button;
}
}
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">null</b>;
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">final</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">class</b> MapperActionImpl <T> <b style="color: #000099; font-family: Monospaced; font-weight: bold">extends</b> AbstractAction <b style="color: #000099; font-family: Monospaced; font-weight: bold">implements</b> MapperAction <T> {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">private</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">final</b> T item;
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> <b style="font-family: Monospaced; font-weight: bold">MapperActionImpl</b> (T item) {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">this</b>.item = item;
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> <b style="color: #000099; font-family: Monospaced; font-weight: bold">void</b> <b style="font-family: Monospaced; font-weight: bold">actionPerformed</b>(ActionEvent e) {
logic.<b style="font-family: Monospaced; font-weight: bold">invoke</b>(<b style="font-family: Monospaced; font-weight: bold">getItem</b>());
}
<b style="color: #000099; font-family: Monospaced; font-weight: bold">public</b> T <b style="font-family: Monospaced; font-weight: bold">getItem</b>() {
<b style="color: #000099; font-family: Monospaced; font-weight: bold">return</b> item;
}
}
}That being said, there are things that I find ugly about generics -
Map m = new HashMap() is less work to read then Map <String, Foo> m = new HashMap <String, Foo> (), and it can get really ugly, e.g. Map <String, Reference<Bar<Baz>>> m = new HashMap <String, Reference<Bar<Baz>>> () - but for cases like the above code, they're darned useful - and moreover, fun.
- Login or register to post comments
- Printer-friendly version
- timboudreau's blog
- 827 reads





