0

For various reasons I'm stuck with this bit of Java code that uses Scala types:

scala.Tuple2<scala.Enumeration.Value, Integer>[] tokens = new scala.Tuple2[] {
    new scala.Tuple2(scala.math.BigDecimal.RoundingMode.UP(), 0)
};

IntelliJ throws this warning on line 1:

Unchecked assignment: 'scala.Tuple2[]' to 'scala.Tuple2<scala.Enumeration.Value,java.lang.Integer>[]' 

And it throws two warnings on line 2:

Raw use of parameterized class 'scala.Tuple2' 
Unchecked call to 'Tuple2(T1, T2)' as a member of raw type 'scala.Tuple2' 

I can get rid of the warnings on line 2 by simply adding <> after new scala.Tuple2 and before (:

scala.Tuple2<scala.Enumeration.Value, Integer>[] tokens = new scala.Tuple2[] {
    new scala.Tuple2<>(scala.math.BigDecimal.RoundingMode.UP(), 0)
};

But the warning on line 1 remains. Adding <> after new scala.Tuple2 and before [] doesn't help. I also tried this:

scala.Tuple2<scala.Enumeration.Value, Integer>[] tokens = new scala.Tuple2<scala.Enumeration.Value, Integer>[] {
    new scala.Tuple2<>(scala.math.BigDecimal.RoundingMode.UP(), 0)
};

This causes an error: Generic array creation. I don't understand what this means or why it wouldn't work.

Big McLargeHuge
  • 14,841
  • 10
  • 80
  • 108
  • It looks like a pure Java problem (which, in general, cannot instantiate anything of shape `new Foo[]`, regardless of whether `Foo` comes from Scala or from elsewhere). Does [this](https://stackoverflow.com/a/7132580/2707792) answer your question? – Andrey Tyukin Oct 18 '22 at 18:59
  • `various reasons I'm stuck with` - I see, you're stuck with it, and you don't seem to endorse this. But, honestly: leaking Scala Tuples into Java API, and then additionally mess around with generic arrays on Java-side, that's two deadly sins in a single line. That's also how examples of unmaintainable Scala-related code are born. What was the author thinking _facepalm_ :/ – Andrey Tyukin Oct 18 '22 at 19:03
  • @AndreyTyukin my thoughts exactly *sigh*. The answer you linked doesn't really help because I need a tuple of two types. I might just leave the warning as is. – Big McLargeHuge Oct 18 '22 at 19:06
  • Why would the number of generic arguments be relevant here? Have you tried`... = (scala.Tuple2[])(new scala.Tuple2, ?>[]{ ... })` ? – Andrey Tyukin Oct 18 '22 at 19:07
  • @AndreyTyukin sorry, I misunderstood. `scala.Tuple2[] tokens = (scala.Tuple2[])(new scala.Tuple2, ?>[1]);` warns `Unchecked cast: 'scala.Tuple2,?>[]' to 'scala.Tuple2[]' `. – Big McLargeHuge Oct 18 '22 at 19:14
  • Well, yes. You'd nee a `SuppressWarnings("unchecked")` as well. You [would be in a good company](https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.base/share/classes/java/util/ArrayList.java#L427). I'm not sure how much better it is over the raw type, it seems to be just a matter of taste which warning to suppress. – Andrey Tyukin Oct 18 '22 at 19:18
  • 1
    Regarding `edit` - yes, if you can use `ArrayList`, by any means, do that. The authors of the standard library already did all the unsafe array-to-arraylist-and-back conversions, and they have slapped all the necessary `SuppressWarnings` on it. – Andrey Tyukin Oct 18 '22 at 19:22

1 Answers1

2

Generics are entirely a compile time thing. The stuff in the <> either doesn't end up in class files at all, or if it does, it is, as far as the JVM is concerned, a comment. It has no idea what any of it means. The only reason <> survives is purely for javac's needs: It needs to know that e.g. the signature of the List interface is boolean add(E), even though as far as the JVM is concerned, it's just boolean add(Object).

As a consequence, given an instance of some list, e.g.:

// snippet 1:

List<?> something = foo();

List<String> foo() {
  return new ArrayList<String>();
}

// snippet 2:

List<?> something = foo();

List<Integer> foo() {
  return new ArrayList<String>();
}

These are bytecode wise identical, at least as far as the JVM is concerned. There's this one weird comment thing the JVM doesn't know about that is ever so slightly different, is all. The runtime structure of the object created here is identical and hence it is simply not possible to call anything on the something variable to determine if it is a list of strings or a list of integers.

But, array types are a runtime thing. You can figure it out:

// snippet 1:

Object[] something = foo();

String[] foo() {
  return new String[0];
}

// snippet 2:

Object[] something = foo();

Integer[] foo() {
  return new Integer[0];
}

Here, you can tell the difference: something.getClass().getComponentType() will be String.class in snippet 1, and Integer.class in snippet 2.

Generics are 100% a compile time thing. If javac (or scalac, or whatever compiler you are using) doesn't stop you, then the runtime never will. You can trivially 'break' the heap if you insist on doing this:

List<String> strings = new ArrayList<String>();
List /* raw */ raw = strings; // warning, but, compiles
raw.add(Integer.valueOf(5));
String a = strings.get(0); // uhoh!

The above compiles fine. The only reason it crashes at runtime is because a ClassCastException occurs, but you can avoid that with more shenanigans if you must.

In contrast to arrays where all this is a runtime thing:

Object[] a = new String[10];
a[0] = Integer.valueOf(5);

The above compiles. At runtime you get an ArrayStoreException.

Thus, generics and arrays are like fire and water. Mutually exclusive; at opposite ends of a spectrum. Do not play together, at all.

Now we get to the construct new T[]. This doesn't even compile. Because javac doesn't know what T is going to be, but arrays know the component type, and it is not possible to derive T at runtime, so this creation isn't possible.

In other words, mixing arrays and generics is going to fail, in the sense that generics are entirely a compile time affair, and tossing arrays into the mix means the compiler can no longer do the job of ensuring you don't get 'heap corruption' (the notion that there's an integer in a list that a variable of type List<String> is pointing at).

You simply write this:

List<String>[] arr = new List[10];

And yes, the compiler will warn you that it has no way of ensuring that arr will in fact only contain this; you get an 'this code uses unchecked/unsafe operations' warning. But, key word, warning. You can ignore them with @SuppressWarnings.

There's no way to get rid of this otherwise: Mixing arrays and generics usually ends up there (in warnings that you have to suppress).

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
rzwitserloot
  • 85,357
  • 5
  • 51
  • 72