Forcing type-safe IDs for use with Collections and Maps
Do not use floating point numbers as IDs! Floating point is a horrible minefield of gotchas out there to sabotage your program in subtle ways: Losing precision, imprecise equality, NaNs, …. Using a double only defers, but not solves these issues.

For IDs, always use an integral type like long.

Now you've found a sufficiently elegant solution to represent typesafe IDs by using generics. There are a few other possibilities you might want to consider:


Lazy self-loading classes. Each instance has a boolean field initialized which defaults to false. In each public method you put in a if (!initialized) this.initialize() test to load the other fields – but only when they are needed.

Usage example:

class LazyThing {
    private boolean initialized = false;
    ... // state that is loaded lazily

    private void initialize() {
        ... // initialization code
        initialized = true;
    }

    public void frobnicate() {
        if (! initialized) initialize();
        ...
    }
}


The advantage here is that this pattern is completely invisible to the user.
A Deferred&lt;T&gt; wrapper which is basically a “promise” or “future” (or any other single-element monad-like container of your choice). A deferral wrapper represents an object that may or may not be fully loaded. You can get() the element to force loading. This is a bit more awkward to use but properly separates the lazy loading from the wrapped type. The full interface might look like:

abstract class Deferred&lt;T&gt; {
  boolean initialized = false;
  T instance;

  abstract T initialize();  // sadly, Java does not have traits

  public T get() {
    if (! initialized) {
      instance = initialize();
      initialized = true;
    }
    return instance;
  }
}


Usage example:

Deferred&lt;Thing&gt; foo = new Deferred&lt;Thing&gt;() {
  private long id;

  // anon classes can't have constructors :(
  Deferred&lt;Thing&gt; init(long id) {
    this.id = id;
    return this;
  }

  @Override
  Thing initialize() {
    ... // code to load the instance
  }
}.init(1234);

foo.get().frobnicate();


This scheme is extremely flexible because it does not assume the existence or type of an ID.