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<SampleFolder> 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<Double> 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<Double, List<Double>> 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<Id<Car>, List<Id<Wheel>>> 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<T extends Item> 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<?> cast = (Id<?>) obj;
return id == cast.id;
}
@Override
public int hashCode() {
return Double.valueOf(id).hashCode();
}
public static <E extends Item> Id<E> from(double id) {
return new Id<E>(id);
}
public static <E extends Item> Id<E> from(E enumItem) {
return new Id<E>(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<Id<SampleFolder>> subFolders;
private List<Id<MoreItem>> moreItems;
private List<Id<EvenMoreItem>> evenMoreItems;
public SampleFolder() {
id = new Date().getTime();
subFolders = new ArrayList<>();
}
public SampleFolder(double id, List<Id<SampleFolder>> subFolders) {
this.id = id;
this.subFolders = subFolders;
}
@Override
public double getId() {
return id;
}
public List<Id<SampleFolder>> getSubFolders() {
return subFolders;
}
public void setSubFolders(List<Id<SampleFolder>> subFolders) {
this.subFolders = subFolders;
}
}
double
cannot be used as type parameters. Also double is not suitable for equals check and thus for being used neither as IDs nor as map keys.double id = new Date().getTime()
: ???. \$\endgroup\$double
s as IDs even if you do not tamper with them:long l = Long.MAX_VALUE;
double d1 = l;
double d2 = l - 1;
boolean wot = d1 == d2;
System.out.println(wot);
\$\endgroup\$Double
anddouble
. \$\endgroup\$