Forcing type-safe IDs for use with Collections and Maps Introduction

I have a hierarchically nested tree class (like SampleFolder, s.b.) that I want to serialize. 

The problem arises with the collection of sub items like this one:

List&lt;SampleFolder&gt; subFolders;


I do not want to load the entire tree but only one folder at a time, when I deserialize! 
So instead of creating a list of the actual objects I need to use IDs. Naturally I am using a primitive based data type for reduced overhead: 

List&lt;Double&gt; subFolders;


All good with serialization here. But in a more complex project handling all those Double-IDs, esp. within maps starts to become very confusing during development. As the project increases in complexity it happens that I would hand the wrong ID to a collection. 

Map&lt;Double, List&lt;Double&gt;&gt; myAssociations; // what? which id goes where?


So to battle this issue of lost-type safety I created an interface Item and inner class Id (s.b.). This is the resulting usage (more in-depth example see SampleFolder, below):

Map&lt;Id&lt;Car&gt;, List&lt;Id&lt;Wheel&gt;&gt;&gt; myAssociations; // a lot more readable, isn't it?




Question

I couldn't find a best-practice for this issue, so if there is a better way to solve this "type-safety" issue please let me know. 
Even though this seems to work, I am wondering if this is good code in respect to the use of generics and if the implementation of hashCode() and equals() is correct. 
I am open for optimization suggestions and other critics. 

code to review: Item with Id 

interface Item {

    double getId();    

    class Id&lt;T extends Item&gt; implements Serializable {

        private double id;

        public Id(T item) {
            this.id = item.getId();
        }

        public Id(double id) {
            this.id = id;
        }

        public double asDouble() {
            return id;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }

            if (!(obj instanceof Id)) {
                return false;
            }

            Id&lt;?&gt; cast = (Id&lt;?&gt;) obj;

            return id == cast.id;
        }

        @Override
        public int hashCode() {
            return Double.valueOf(id).hashCode();
        }

        public static &lt;E extends Item&gt; Id&lt;E&gt; from(double id) {
            return new Id&lt;E&gt;(id);
        }

        public static &lt;E extends Item&gt; Id&lt;E&gt; from(E enumItem) {
            return new Id&lt;E&gt;(enumItem);
        }

        @Override
        public String toString() {
            return String.valueOf(id);
        }
    }
}




Sample usage (class not part of the actual project; for illustration purposes only):

public class SampleFolder implements Item, Serializable {

    private final double id;
    private List&lt;Id&lt;SampleFolder&gt;&gt; subFolders;
    private List&lt;Id&lt;MoreItem&gt;&gt; moreItems;
    private List&lt;Id&lt;EvenMoreItem&gt;&gt; evenMoreItems;

    public SampleFolder() {
        id = new Date().getTime();
        subFolders = new ArrayList&lt;&gt;();
    }

    public SampleFolder(double id, List&lt;Id&lt;SampleFolder&gt;&gt; subFolders) {
        this.id = id;
        this.subFolders = subFolders;
    }

    @Override
    public double getId() {
        return id;
    }

    public List&lt;Id&lt;SampleFolder&gt;&gt; getSubFolders() {
        return subFolders;
    }

    public void setSubFolders(List&lt;Id&lt;SampleFolder&gt;&gt; subFolders) {
        this.subFolders = subFolders;
    }
}
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.