I've never had the chance to play much with generics before (as in writing classes that are generics), but now the need arises, and I've come across some confusion.
There's this interface, that is meant to be a wrapper of something. The implementations are not collections, so, every instance has access only to one something.
public interface Resource<T> {
// Expected operations:
void write(ResourceState state);
ResourceState read();
}
As implementations, I expect to have an ExclusiveResource<T>, and a ShareableResource<T>, that differ mainly/only in the locking scheme used (regular lock, and read-write lock, respectively).
As to how the read and write are performed, I'm planning on using the Strategy pattern.
For instance, I might have
// This would implement a Strategy<File>.
FileStrategy fs = new FileStrategy();
Resource<File> r = new ExclusiveResource<File>(fs);
Now, I've also got some sort of collection of these resources, say, a resource pool.
I'd like to map a key to each resource, in the resource pool, and I'd like to add, retrieve and remove resources, but I'm not sure how to declare the map and the methods. I've tried the following:
public class ResourcePool {
// instance variables
private final Map<String, Resource<?>> map;
/** Empty constructor of objects of class ResourcePool. */
public ResourcePool() {
map = new HashMap<String, Resource<?>>();
}
/** */
public Resource<?> get(String s) {
return map.get(s);
}
/** */
public void add(String s, Resource<?> r) {
map.put(s, r);
}
// ...
}
This does not seem to be the most appropriate way to do it, and, quoting Josh Bloch, on Effective Java Reloaded:
User should not have to think about wildcards to use your API.
I've tested this code with the following method:
public static void test() {
ResourcePool rp = new ResourcePool();
Resource<String> r1 = new ShareableResource<>("test");
Resource<Integer> r2 = new ShareableResource<>(1);
Resource<List<String>> r3 = new ShareableResource<>(
Arrays.asList(new String[]{"1", "2"})
);
// These are all ok.
rp.add("1", r1);
rp.add("2", r2);
rp.add("3", r3);
// This results in a compiler error (incompatible types).
Resource<String> g1 = rp.get("1");
// This results in a compiler warning (unsafe operation).
Resource<String> g2 = (Resource<String>) rp.get("1");
}
I don't like it, when the code compiles with warnings. Makes me feel guilty, and seems to be a hint at bad coding.
So, my question is how should I handle this situation.
Is this the right way to do what I'm trying to do?
Can this be done in such a way that there are no unsafe operations?
I don't think there's any way to avoid unchecked casts using your design. That said, you can avoid having to do a cast every time you retrieve a Resource:
@SuppressWarnings("unchecked")
public <T> Resource<T> get(String s, Class<T> c) {
return (Resource<T>) map.get(s);
}
When you want to retrieve a Resource, you pass in the desired class, like so:
Resource<String> g1 = rp.get("1", String.class);
You should be careful with this design, though, since there will be no runtime guarantee that the returned Resource is actually a Resource<String>.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With