5

It's been a while since I touched generics in Java, I have this:

Map<List<MyGenericType>, Set<List<MyGenericType>>> x = new HashMap<>();

In truth, MyGenericType requires a generic parameter, since it is defined like this:

public class MyGenericType<X> {}

I declared x with pre-emptive type-erasure of MyGenericType because I didn't want to make an empty marker interface just for the sake of grouping things. Will this, for the sake of having a collection, work in my favor or will I have to make a marker interface to make this possible?

(EDIT) That is:

  1. public interface SomeCommonInterface {...}
  2. MyGenericType implements SomeCommonInterface ...
  3. Map<List<SomeCommonInterface>, Set<List<SomeCommonInterface>>> x = new HashMap<>();
Novicegrammer
  • 222
  • 1
  • 9
  • Not a good idea to use `List`, i.e. mutable object, as key in a `Map` – Yousaf Jul 11 '20 at 15:27
  • Why do you want to use `MyGenericType` without the generic type? Why do you not add the type? – Progman Jul 11 '20 at 15:30
  • Why not use `MyGenericType>` if you don't know the actual type? – Slaw Jul 11 '20 at 15:31
  • @Progman Because I will be inserting objects with different type parameters into the map. Sort of like having a list of `Fruit` and putting in different kinds of fruit. – Novicegrammer Jul 11 '20 at 15:36
  • @Slaw Would that work for what I'm trying to do? And if it does, what's the difference? I want to be able to insert various "types" of `MyGenericType` into the lists. – Novicegrammer Jul 11 '20 at 15:36
  • @Yousaf I actually declare the lists using `List.of` which I've been told makes an unmodifiable list. I don't like this approach either - Does Java actually support non-primitive arrays? – Novicegrammer Jul 11 '20 at 15:37
  • The `?` is a wildcard that means "can be any type [that matches the type parameter bounds]". Using a wildcard means you're still using a parameterized type, which is typically better than using a raw type. Note it will restrict you in other ways. If any method of `MyGenericType` has `X` as a parameter then you won't be able to call that method on a `MyGenericType>` and if any method has `X` as a return type you'll only know it to be the upper bound of `X` (though the latter is true of raw types as well, I believe). – Slaw Jul 11 '20 at 15:42
  • @Slaw I see - but I still don't understand the difference between `>` and not having it. Edit: I see - is it just a practice to let others know that the type is actually generic? – Novicegrammer Jul 11 '20 at 15:51
  • 4
    It's the difference between having a parameterized type and a raw type. See [What is a raw type and why shouldn't we use it?](https://stackoverflow.com/questions/2770321/). – Slaw Jul 11 '20 at 15:52
  • if you can have a bounded type `public class MyGenericType {}` and all your later operations can be driven off MyInterface. that may be easier. – PrasadU Jul 11 '20 at 16:02
  • 1
    @Slaw `MyGenericType>` isn't a bad advice, but it will not stop things like : `Map>, Set>>> x = new HashMap<>(); x.put(List.of(new MyGenericType()), Set.of(List.of(new MyGenericType())));`. there are _two_ unbounded types in the declaration, these are different types for the compiler – Eugene Jul 11 '20 at 20:47
  • @Eugene Since the OP was considering using raw types I figured that's exactly the behavior they wanted. – Slaw Jul 11 '20 at 20:53
  • @Slaw oh! I though exactly in reverse, since there was a type in `MyGenericType`, he wanted some safety. interesting how divergent both views are. thank you for the follow-up – Eugene Jul 11 '20 at 20:54

2 Answers2

4

Recall that Integer as well as Double both extend Number and in turn, Number extends Object.

A List<Number> is in no way usable as a List<Integer> and vice versa. List<Number> x = new ArrayList<Integer> does not compile. This is called 'invariance', and it is 'correct', because if it did compile, you could add doubles to a list of integers which is obviously not right. It tends to throw people off, though, which is why I mention it. If you want covariance or contravariance, you must opt into this: List<? extends Number> list = new ArrayList<Integer>(); does compile. But then list.add(5); won't, because if it did, you would again be able to add doubles to a list of integers and that wouldn't be right.

Once you entirely omit generics on a thing, all type checking on generics is right out the door, and you don't want that. Can you 'get away with it'? Well, uh, the compiler will toss a bunch of warnings in your face and you turn off quite a bit of type checking. If you mean with 'get away with it': "Does it compile"? Yes, it does. If you mean: "Would this code pass any reasonable java coder's code review"? No, it won't.

Simple solution: What's wrong with using MyGenericType<?> here?

NB: Keys in maps should be immutable; list is not. Using it is therefore quite a bad plan; what is that supposed to represent?

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • I presume you meant to write `list.add(5.0)`? Can the standard java compiler not infer that `list` is actually a list of integers? – Novicegrammer Jul 11 '20 at 16:35
  • Furthermore, I want an immutable ordered collection of objects - I've since changed it to primitive arrays – Novicegrammer Jul 11 '20 at 16:38
  • @Novicegrammer Whether `list.add(5)` or `list.add(5.0)`, both would fail for a `List extends Number>`. And the compiler has nothing to infer; the code states explicitly that the variable type is a `List extends Number>`, not a `List`. Nor would you want the compiler to infer anything. The whole reason for `List extends Number>` is to describe that the "real" type argument be anything that extends `Number` (e.g. `List`, `List`, `List`, etc.). That's why you can't add anything to such a `List`, because you _don't know_ what the real element type is. – Slaw Jul 11 '20 at 19:24
  • @Novicegrammer These Q&As may help: [What is PECS (Producer Extends Consumer Super)?](https://stackoverflow.com/questions/2723397/) and [Difference between super T> and extends T> in Java](https://stackoverflow.com/questions/4343202/). – Slaw Jul 11 '20 at 19:26
  • to me declaring two wildcard unbounded types solves very little for the OP, for example [this](https://stackoverflow.com/questions/62850971/in-java-can-one-get-away-with-using-raw-unparameterised-class-es-instead-of-u#comment111150051_62850971) – Eugene Jul 11 '20 at 20:52
  • @Slaw so what's the point of this being legal Java if you can't add anything to it? – Novicegrammer Jul 11 '20 at 21:36
  • 1
    @Novicegrammer Flexibility. The Q&As I linked to show why you would want to do this. A `List extends Number>` is a _producer_ of some type of `Number`, but it can't _consume_ anything. The opposite is true of `List super Number>` (though it can produce `Object`). It may be more obvious if you look at things like `Function super T, ? extends R>` and `Consumer super T>` and `Supplier extends T>` as parameter types; without the wildcards the _users of the API_ lose flexibility. Again, the linked Q&As go into much more detail. – Slaw Jul 11 '20 at 22:56
0

This is not really an answer, but as said in the comment here, I don't think that adding wildcards solves anything.

Depending were the use-site of this Map is, you could have some kind of safety. I was in your shoes a couple of times, the only solution I know is to do:

@Getter
static class Box<T> {

    private Map<List<MyGenericType<T>>, Set<List<MyGenericType<T>>>> x = new HashMap<>();

}

i.e.: expose the Map via a "wrapper", that has a type variable defined. If callers provide a type when instantiating Box, you will get the needed type safety:

new Box<Integer>().getX().put(...); // you can only put a MyGenericType<Integer> in here now

Otherwise when using raw types or even ?, you could put different type in the key and value.

Eugene
  • 117,005
  • 15
  • 201
  • 306