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.

Priv
  • 812
  • 2
  • 9
  • 23
  • Just compile it. – mentallurg Jun 09 '18 at 14:09
  • Did you even try to code? This is already supported by `Map` interface – Mạnh Quyết Nguyễn Jun 09 '18 at 14:09
  • I have tried `protected Map,OptionSet> options;` which gave me `Syntax error on token(s), misplaced construct(s)`, that's why I'm asking if this is actually possible. – Priv Jun 09 '18 at 14:13
  • 1
    As for what I think you're asking, no it's not possible to declare a `Map` that way. Possibly see https://stackoverflow.com/questions/44422685/consumert-mapped-classt-in-hashmap/44422954#44422954 for some examples of workarounds (possible duplicate?). – Radiodef Jun 09 '18 at 14:15
  • I have looked at that but those answers didn't really give me what I wanted. – Priv Jun 09 '18 at 14:17
  • Is that hard to read [API reference for Map](https://docs.oracle.com/javase/10/docs/api/java/util/Map.html) *before* asking this kind of question? – zlakad Jun 09 '18 at 14:17
  • 3
    @zlakad which specific bit of that document do you think answers the question? – Andy Turner Jun 09 '18 at 14:20
  • What's the problem with the answers at my link? The basic premise is going to be the same. There's no way to declare a `Map` this way, but sometimes we can do something like this with generic methods and (probably) casting. – Radiodef Jun 09 '18 at 14:22
  • @Radiodef I saw some casting things in the accepted answer. This may solve my problem but I am looking for a more elegant way that does not require me to cast, it it exists. – Priv Jun 09 '18 at 14:24
  • As it's private you could just use `A>` and `B>`. You have to cast the types at use-site. – Bubletan Jun 09 '18 at 14:25
  • @AndyTurner, for examle there is a link to `HashMap`, and then you can actually read about the constructor of that class. – zlakad Jun 09 '18 at 14:26
  • private was just an example. See my edit though, I am looking for a more elegant way. – Priv Jun 09 '18 at 14:26
  • It would be easier to give a better answer then if you could edit your question to make it more clear what you want to do. You could write simple classes for `A` and `B` that show what you need to do with them, or use Java SE classes (e.g. `List`, `Consumer`, etc.) that perform a similar function. (For example, in the Q&A I linked to there was one solution posted that didn't use unchecked casting, but it depended on the specific fact that the question involved mapping `Class` to `Consumer`.) – Radiodef Jun 09 '18 at 14:30
  • @zlakad I'm not really talking about constructors. It might be a good idea if you read through my question a few times. Perhaps you will then understand what I need. – Priv Jun 09 '18 at 14:30
  • What I am trying to do is `protected Map,OptionSet> options;` as I pointed out before. What classes I use doesn't matter for my question though; the only thing I need is a way to link an OptionType with an OptionSet and get the Set from the Type without casting. – Priv Jun 09 '18 at 14:33
  • 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 – GBlodgett Jun 09 '18 at 14:35
  • @zlakad that tells you nothing, because the issue here is with the nature of type variables as a part of the language, not anything specifically related to the Map interface or its implementations. – Andy Turner Jun 09 '18 at 14:47
  • @AndyTurner, I read your answer, and as a potentially consumer of OP's future library, I don't think I would be satisfied. IMHO, wild cards used in your way should be avoided. Why's so hard to think in OOP manner? Is it not possible to write something like this: `class A {} class B {}` and then `Map map = new HashMap<>();`? – zlakad Jun 09 '18 at 15:10
  • 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. – Andy Turner Jun 09 '18 at 15:10
  • @AndyTurner, you edited your previous comment quickly. I have to re-read your answer and try to *prove otherwise*. With this I don't say you're wrong, but *maybe* wrong. – zlakad Jun 09 '18 at 15:26

2 Answers2

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.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • 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`. – zlakad Jun 10 '18 at 13:09
-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...

zlakad
  • 1,314
  • 1
  • 9
  • 16