0

I am looking for a way to have a Map with generic types in the Map's types and then retreive those using the key and the correct type. For example:

private final <T> Map<A<T>,B<T>> map = new HashMap<>();

public <T> B<T> getB(final A<T> a) {
  return map.get(a);
}

An example of using this would be:

final A<String> a = ...;
final B<String> b = getB(a);

Does this in any way exist or is there any workaround?

Edit: I know I can work around it by casting things, but I am wondering if there is a more elegant way that does not require me to cast every value I retreive.

20
  • Just compile it. Commented Jun 9, 2018 at 14:09
  • 1
    As for what I think you're asking, no it's not possible to declare a Map that way. Possibly see stackoverflow.com/questions/44422685/… for some examples of workarounds (possible duplicate?). Commented Jun 9, 2018 at 14:15
  • 3
    @zlakad which specific bit of that document do you think answers the question? Commented Jun 9, 2018 at 14:20
  • 2
    It might be a good idea if you read through my question a few times. If your question needs to be reread several times it would probably be a good idea to edit your question so it is more clear Commented Jun 9, 2018 at 14:35
  • 2
    @zlakad I await your alternative answer with interest. But as a user of the library, the presence of a suppressed unchecked cast is neither visible nor troublesome, provided the code is written to enforce the type safety. I mean, you are going to trust (or prove otherwise) that the get method gets something, as opposed to wiping your hard drive, right? In the same way, you have to trust (or prove otherwise) that the code is type safe. Commented Jun 9, 2018 at 15:10

2 Answers 2

6

You can't declare a map in this way. Basically, Java does not have an expressive-enough type system for this.

But luckily, the language provides an escape hatch from using the type system, in the form of casts. Basically, casts are a way to provide type information that the compiler doesn't have; the onus is then on you, however, to make sure that it actually is type safe.

Firstly, declare the map with wildcard types:

private final Map<A<?>,B<?>> map;

Then, only put key/value pairs into the map which do meet the constraint:

<T> void put (A<T> key, B<T> value) {
  map.put(key, value);
}

And then cast when you get the element out again:

@SuppressWarnings("unchecked") // Safe
<T> B<T> get(A<T> key) {
  return (B<T>) map.get(key);
}

The point is that you can know more about the types than the compiler. Provided you take care to only put in safely-castable pairs, it is safe to cast them. (You also need to ensure that the equals method takes the type into account, so no A<T> equals A<S> unless S == T).

This is basically a "type-safe heterogeneous container", as described in Effective Java (Item 33 in 3rd Edition).

I am wondering if there is a more elegant way that does not require me to cast every value I retreive.

For one thing, you aren't actually doing a cast in the get method at runtime: that's what an unchecked cast means.

For another, generics introduces loads of casts - basically generics is just a way to avoid inserting casts manually. So if there were a problem with the casts (such as performance), you'd already observe this in lots of places.

To paraphrase Kubrick: stop worrying, and love the cast.

Sign up to request clarification or add additional context in comments.

1 Comment

This is a good answer, and I'll upvote it. The only thing what makes me a little bit nervous is when I see something like A<?> instead of A<? super T> for example. I still think that is better to know (in advance) what should be K and V in the Map<K, V>.
-4

Well, I was provoked to post this answer as a kind of view of OOP principles.

In the question stands Map<A<T>,B<T>>, so my approach would be to do something like this:

class A<T> {}
class B<T> {}

public class Answer {

    public static void main(String[] args) {

        Map<A, B> map = new HashMap<>();
        A<Integer> a = new A<>(); //very safe
        B<String> b = new B<>(); //very safe

        map.put(a, b); //very safe

    }

}

I admit, there is an extra work, but...

11 Comments

Ow. My eyes. . .
@Priv, please, be free to down vote if you don't like it - I'll never delete this... Good luck.
You are actually doing the opposite of what I was looking for. Map<A<T>,B<T>> means T in A is the same as T in B, in other words, if you have A<String>, you must have B<String> too; I also mentioned in my post a method <T> B<T> getB(A<T> a): if you pass an A<String>, you will always get a B<String> back.
@Priv, that's not true. Simple as that... You can declare B<Integer> or B<WHATEVER>...
^That's what I asked. Simple as that.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.