patternjavaMinor
Forcing type-safe IDs for use with Collections and Maps
Viewed 0 times
withforcingidsmapstypeforsafecollectionsanduse
Problem
Introduction
I have a hierarchically nested tree class (like
The problem arises with the collection of sub items like this one:
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:
All good with serialization here. But in a more complex project handling all those
So to battle this issue of lost-type safety I created an interface
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
I am open for optimization suggestions and other critics.
code to review:
```
interface Item {
double getId();
class Id 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;
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 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 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> 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, List>> 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 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;
Solution
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
For IDs, always use an integral type like
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
Usage example:
The advantage here is that this pattern is completely invisible to the user.
-
A
Usage example:
This scheme is extremely flexible because it does not assume the existence or type of an ID.
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 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 {
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 foo = new Deferred() {
private long id;
// anon classes can't have constructors :(
Deferred 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.
Code Snippets
class LazyThing {
private boolean initialized = false;
... // state that is loaded lazily
private void initialize() {
... // initialization code
initialized = true;
}
public void frobnicate() {
if (! initialized) initialize();
...
}
}abstract class Deferred<T> {
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;
}
}Deferred<Thing> foo = new Deferred<Thing>() {
private long id;
// anon classes can't have constructors :(
Deferred<Thing> init(long id) {
this.id = id;
return this;
}
@Override
Thing initialize() {
... // code to load the instance
}
}.init(1234);
foo.get().frobnicate();Context
StackExchange Code Review Q#38988, answer score: 4
Revisions (0)
No revisions yet.