Skip to main content

Mason's Blindingly Obscure Tips & Tricks, Vol. 1

Posted by mason on November 1, 2006 at 5:52 PM PST

1) I often find myself writing factory type interfaces, but one of the problems with interfaces in Java is that, while you can include static variables, there isn't any way to include static methods.

This means if you want your getInstance and setInstance methods inside the factory interface itself, you either have to resort to using a static variable or you have to create a second class just to handle these two methods.

For this reason, I often has pairs of Java files that looked like this:

public interface WidgetFactory {

  ...

}

public abstract class WidgetFactoryManager {

  private static final WidgetFactory _instance = ...

  public static WidgetFactory getInstance() {
      return _instance;
  }

  public static void setInstance(WidgetFactory factory) {
      _instance = factory;
  }
}

Well, the trick that caused this problem's head bonking concerns the fact that, while interfaces can't contain static methods, they CAN contain new class definitions. It's not quite as pretty as I would like it to be, but it's good enough for me to work with:

public interface WidgetFactory {

  ...

  public static class Instance {

      private static final WidgetFactory _instance = ...

      public static WidgetFactory get() {
          return _instance;
      }

      public static void set(WidgetFactory factory) {
          _instance = factory;
      }
}

It doesn't reduce the overall amount of code, but it does make it easier to place all the factory framework into one file and yet still keep the usefulness of having an interface. To use:

WidgetFactory.Instance.set(new WidgetFactoryImpl());
WidgetFactory factory = WidgetFactory.Instance.get();

2) The second is slightly more complex, but is also blindingly obvious when you sit and think about it.

When was the last time you ever looked at that obscure little "hashCode" method hiding inside of each object? Have you ever actually written your own? And if you have, what value could your own custom hashcode be compared to what's already there?

I've just begun noticing all the interesting things that are already written for you if you are willing to overwrite the hash code.

The problem here was matching up similar objects in a long list and merging them together. For example, let's say you have a bunch of Order objects, and you want to compress all orders by the same person into a single object, what is the simplest way to do it?

public class Order {

    public Person _owner ...
    public List _items ...
   
    public void merge(Order other) {
        if (!_owner.equals(other._owner)) {
            throw new RuntimeException("Cannot merge orders that don't belong to the same person!");
        }
        _items.addAll(other._items);       
    }

}

I had a problem very much like this, and naively started by looping through the list of objects and looping through the remainder of the list and finding matches and merging them. Once that was done, I'd do it again, and again, until I ended up with zero matches. As you can imagine, this was slow as a cow, so I started putting together a special table to do most of the work for me, when I suddenly realized I was just reimplementing the HashMap class, badly...

With one small change to the Order class I was able to turn an ugly looping twisting mess into this little elegant solution:

List allOrders = ...
Map orders = new HashMap();
for(Order order: allOrders) {
Order key = orders.get(order);
if(key != null) {
key.merge(order);
} else {
orders.put(order, order);
}
}

And what was the small change to the Order class tat makes this possible? You guessed it, just changing the hashCode:

public class Order {
    ...   
    public int hashCode() {
        return _owner.hashCode();
    }
}

And the end result is that you only need to traverse the loop once, but you still capture all the possible merges. This solution is not particularly brilliant, it's Algorithms 101 kind of stuff, but it was still such a fun rediscovery of basic principles that I thought it would be fun to share.

3) Finally, one last HashMap trick that I discovered recently that is very simple, yet still so simple that you'd never think of it is the "row locked" HashMap. Basically this is a very simple way to synchronize individual fields in a hash map without needed to lock the entire table every time you read and write.

public class RowLockedMap extends HashMap {

    private final Object NULL = new Object();

    public V get(Object key)
    {
        if (key == null) {
            synchronized(NULL) {
                return super.get(key);
            }
        } else {
            synchronized(key) {
                return super.get(key);
            }
           
        }
    }

    public V put(K key, V value)
    {
        if (key == null) {
            synchronized(NULL) {
                return super.put(null, value);
            }
        } else {
            synchronized(key) {
                return super.put(key, value);
            }
        }
    }

    public V remove(Object key)
    {
        if (key == null) {
            synchronized(NULL) {
                return super.remove(key);
            }
        } else {
            synchronized(key) {
                return super.remove(key);
            }
        }
    }   
/*   Of course, do do this properly, you'd need to handle the contains(),
  *   putAll() and other methods, or at least force them to throw
  *   exceptions.
  */   
}

The concept is quite simple... You just synchronize on the KEY objects. For simple HashMaps with Strings, Numbers and other standard objects as keys, this should work properly. You'll have some reasonable thread safety when reading/writing individual values, but you won't need to lock the entire table every time.

(I should add a small warning with this one, however. I am not absolutely sure how the internal implementation of the HashMap works in a multi-threaded environment. It's quite possible that this will not always work properly! I have only played with it a little bit, and so far it looks like it works fine, but watch yourself!)

Related Topics >>