Java strange warning "suspicious toArray() call" - java

When implementing a Stream-class using a delegate stream, I get strange warning by IntelliJ:
Array of type java.lang.Object[] expected, A[] found
The code which triggers the warning:
public class MyStream<T> implements Stream<T> {
private final Stream<T> delegate;
public MyStream(Stream<T> delegate) {
this.delegate = delegate;
}
*snip*
#Override
public <A> A[] toArray(IntFunction<A[]> generator) {
return delegate.toArray(generator);
}
*snip*
}
The full warning:
Array of type java.lang.Object[] expected, A[] found
Inspection info: Reports two types of suspicious calls to Collection.toArray(). The first type is any calls where the type of the specified array argument is not of the same type as the array type to which the result is casted. Example:
void m(List list) {
Number[] ns = (Number[])
list.toArray(new String[0]);
}
The second type is any calls where the type of the specified array argument does not match the type parameter of the collection declaration. Example:
void m(List<Number> list) {
Number[] ns =
list.toArray(new String[0]);
}
I somewhat assume this is a false positive, caused by type erasure, mostly because I cannot find any sane reason why this would cause a problem, and also because the examples from the warning do not match very well with my code.
However, when thinking about it, I wondered: If this is a false positive triggered because of type erasure, why does it know about A at all? Also, Java does usually cast a A[] to a Object[] implicitly, so why doesn't it here?
So: What is happening here?

Here's some different code that can serve to illustrate the problem your IDE is detecting:
Stream<Integer> intStream = Stream.of(1, 2, 3);
String[] stringArray = intStream.toArray(i -> new String[3]);
Or, using your own class (I didn't test this):
new MyStream<>(Stream.of(1, 2, 3)).toArray(i -> new String[3]);
That code throws a java.lang.ArrayStoreException on the second line (the second one would be expected to, as well). But it compiles.
That is exactly the problem with delegate.toArray(generator);. You're effectively calling
Stream<T>.toArray(IntFunction<A[]>)
Where A is not guaranteed to be the same as or compatible with T. To relate this to the example code above, T has the place of Integer and A has the place of String. See the problem?
The JavaDocs of Stream.toArray warns about this:
Throws: ArrayStoreException - if the runtime type of any element of this stream is not assignable to the runtime component type of the generated array
That is the problem that IntelliJ is detecting. You cannot assume that <A> and the type-wide parameter <T> are going to coincide.
In other words, it's not a false positive and you can use the example above to reproduce it.

IntelliJ thinks that this is suspicious because your stream is a stream of Ts, but you are producing an array of As. Note that A is the type parameter of the toArray method, which is independent of the type parameter T for the class. Not much to do with type erasure.
This is indeed a false positive. You are implementing the method for Stream, which requires an A[] to be returned after all. The method in Stream is indeed designed rather unsafely like this, allowing you to pass in anything for A, no matter what type of stream it is. It is documented that it will throw an ArrayStoreException if A is the wrong type.
Throws
ArrayStoreException - if the runtime type of the array returned from the array generator is not a supertype of the runtime type of every element in this stream
By delegating to delegate, you will be throwing an ArrayStoreException, which you should, (according to the docs) if someone passed a wrong A to toArray too, assuming the instance stored in delegate is correctly implemented. There's nothing wrong here
You can suppress the inspection for this one line by writing this comment above it:
//noinspection SuspiciousToArrayCall

Related

Experimenting with java generics

I was playing little bit with java generics, I came across this piece of code, which I am confused why it is happening so.
I am passing my second parameter K as Integer and inside generic method I was casting float to my K type, and in main() I am receiving it as Integer,
In my Code inspector I was seeing the Float number completely sitting in my list (not chopped after casting to Integer) which is of Integer type, but when I try to pick element to save it in Integer variable it gives ClassCastException.
Can someone explain what is going wrong with generics so it is not saving us from casting exception.
Note : I reach to this scenario when I removed my second parameter K from signature so there will be nothing defining type of K , in that case I think Java make it Object and then possibly we get cast exception but why in this case when I am passing K type as well.
import java.util.ArrayList;
import java.util.List;
public class IntegerPrinter {
Integer item;
public void print() {
System.out.println(item);
}
public <T,K> List<K> anyPrint(List<T> num,K lo) {
List<K> mylist = new ArrayList<>();
mylist.add( (K) new Float(2.99f));
return mylist;
}
public IntegerPrinter(Integer item) {
this.item = item;
}
}
import java.util.ArrayList;
import java.util.List;
public class GenericsInAction {
public static void main(String[] args) {
IntegerPrinter oldPrinter = new IntegerPrinter(188);
oldPrinter.print();
List<Integer> dates = oldPrinter.anyPrint(new ArrayList<Integer>(),7);
Integer x = dates.get(0);
}
}
I condensed the code down to the essential parts and modified it slightly to highlight the behaviour that is important:
class Ideone {
public static void main(String[] args) {
List<Integer> dates = new IntegerPrinter().anyPrint(7);
System.out.println(dates.get(0)); // succeeds
Integer x = dates.get(0); // Line 8, throws
}
}
class IntegerPrinter {
public <K> List<K> anyPrint(K lo) {
List<K> mylist = new ArrayList<>();
mylist.add((K) Float.valueOf(2.99f));
return mylist;
}
}
When executed, this program will result in the following output:
2.99
Exception in thread "main" java.lang.ClassCastException: class java.lang.Float cannot be cast to class java.lang.Integer (java.lang.Float and java.lang.Integer are in module java.base of loader 'bootstrap')
at Ideone.main(Main.java:8)
Ideone.com demo
Now, let us step through the code and try to understand what is going on.
This line:
mylist.add((K) new Float(2.99f));
basically tells the compiler "do not care for the type, we (as programmers) guarantee that it is a K, tread it as a K".
Then, if we dig deeper, we see that ArrayList uses an Object[] as backing data structure. So there is on problem here, the backing Object[] elementData can store everything.
Things get weird when we start retrieving elements. The JLS is somewhat vague about the type assertions in those cases (I think they are covered under §5.1.5 and §5.1.6.3, but I am not entirely sure). It basically says "the compiler has to assert the types, but only when necessary".
So if we retrieve an element from our List<Integer>, that clearly is not an Integer, but is passed along to a method that can deal with Object, no type-assertion is necessary. This is exactly the case here:
System.out.println(dates.get(0));
The closest signature matching in System.out is the println(Object) method. This is the situation in JLS, §5.1.5: a widening conversion, it will never throw.
On the other hand, if we now try to retrieve an Integer and try to store it in an Integer:
Integer x = dates.get(0);
Now, a type check is in place. And indeed, if we check the output of the program, we see that the System.out.println(...) took place, but the assignment to an int-variable was the statement that triggered the ClassCastException. This is the situation described in JLS, §5.1.6.3: a narrowing conversion at run time (that comes from ArrayList's elementData(int) method).
Footnote
Generics are most certainly one of the most, if not the most, complex and confusing parts in the JLS. I made a best-effort attempt to cite the JLS on its relevant parts, this might be miss-cited. I also know that this question was asked before, but I am unable to find the duplicate. If:
a citation of the JLS is wrong, and another part should be cited instead, please ping me via comments or edit the post
you find the (a) duplicate, please ping me, and I will close the question as duplicate (and delete my answer, if possible)
Since ArrayList is a generic type whose type erasure is java.lang.Object, that is what is stored in the list. You can think of the type erasure as being the run-time type, the "real" type, as opposed to the compile-time type that the compiler knows about. Any type can be stored in the ArrayList when the program runs.
It just so happens the the type erasure of K in anyPrint is also java.lang.Object, because you have no bounds on the type K. The method is compiled once for all usages, and it must be able to accept any type for K. So when the code for anyPrint is compiled, the cast to K in the line mylist.add( (K) new Float(2.99f)); is ignored, since the type erasure of K is java.lang.Object. Casting to java.lang.Object is useless and pointless. It compiles as mylist.add(new Float(2.99f)); and the code inserts an object of type java.lang.Float into a list of type java.lang.Object.
Also, a cast in Java on an object type simply ensures the object has the correct type, it does not change the values of the object, like a cast on a primitive type. So there is no reason for you to believe the value 2.99f could change.
GenericsInAction is compiled separately.
The parametrized type of K is java.lang.Integer in the main method of GenericsInAction, since you pass in a 7 which is converted to java.lang.Integer via auto-boxing, to be compatible with the type erasure of java.lang.Object in anyPrint. So, when that main method is compiled, the compiler inserts a run-time check, a checkcast, right after the call to dates.get, a check that ensures that the call to dates.get(0); dates returns an object of type java.lang.Integer, since the type of K must be java.lang.Integer inside main.
Since you inserted a java.lang.Float into the list, that run-time check fails and throws ClassCastException.

varargs heap pollution : what's the big deal?

I was reading about varargs heap pollution and I don't really get how varargs or non-reifiable types would be responsible for problems that do not already exist without genericity. Indeed, I can very easily replace
public static void faultyMethod(List<String>... l) {
Object[] objectArray = l; // Valid
objectArray[0] = Arrays.asList(42);
String s = l[0].get(0); // ClassCastException thrown here
}
with
public static void faultyMethod(String... l) {
Object[] objectArray = l; // Valid
objectArray[0] = 42; // ArrayStoreException thrown here
String s = l[0];
}
The second one simply uses the covariance of arrays, which is really the problem here. (Even if List<String> was reifiable, I guess it would still be a subclass of Object and I would still be able to assign any object to the array.) Of course I can see there's a little difference between the two, but this code is faulty whether it uses generics or not.
What do they mean by heap pollution (it makes me think about memory usage but the only problem they talk about is potential type unsafetiness), and how is it different from any type violation using arrays' covariance?
You're right that the common (and fundamental) problem is with the covariance of arrays. But of those two examples you gave, the first is more dangerous, because can modify your data structures and put them into a state that will break much later on.
Consider if your first example hadn't triggered the ClassCastException:
public static void faultyMethod(List<String>... l) {
Object[] objectArray = l; // Valid
objectArray[0] = Arrays.asList(42); // Also valid
}
And here's how somebody uses it:
List<String> firstList = Arrays.asList("hello", "world");
List<String> secondList = Arrays.asList("hello", "dolly");
faultyMethod(firstList, secondList);
return secondList.isEmpty()
? firstList
: secondList;
So now we have a List<String> that actually contains an Integer, and it's floating around, safely. At some point later — possibly much later, and if it's serialized, possibly much later and in a different JVM — someone finally executes String s = theList.get(0). This failure is so far distant from what caused it that it could be very difficult to track down.
Note that the ClassCastException's stack trace doesn't tell us where the error really happened; it just tells us who triggered it. In other words, it doesn't give us much information about how to fix the bug; and that's what makes it a bigger deal than an ArrayStoreException.
The difference between an array and a List is that the array checks it's references. e.g.
Object[] array = new String[1];
array[0] = new Integer(1); // fails at runtime.
however
List list = new ArrayList<String>();
list.add(new Integer(1)); // doesn't fail.
From the linked document, I believe what Oracle means by "heap pollution" is to have data values that are technically allowed by the JVM specification, but are disallowed by the rules for generics in the Java programming language.
To give you an example, let's say we define a simple List container like this:
class List<E> {
Object[] values;
int len = 0;
List() { values = new Object[10]; }
void add(E obj) { values[len++] = obj; }
E get(int i) { return (E)values[i]; }
}
This is an example of code that is generic and safe:
List<String> lst = new List<String>();
lst.add("abc");
This is an example of code that uses raw types (bypassing generics) but still respects type safety at a semantic level, because the value we added has a compatible type:
String x = (String)lst.values[0];
The twist - now here is code that works with raw types and does something bad, causing "heap pollution":
lst.values[lst.len++] = new Integer("3");
The code above works because the array is of type Object[], which can store an Integer. Now when we try to retrieve the value, it'll cause a ClassCastException - at retrieval time (which is way after the corruption occurred), instead of at add time:
String y = lst.get(1); // ClassCastException for Integer(3) -> String
Note that the ClassCastException happens in our current stack frame, not even in List.get(), because the cast in List.get() is a no-op at run time due to Java's type erasure system.
Basically, we inserted an Integer into a List<String> by bypassing generics. Then when we tried to get() an element, the list object failed to uphold its promise that it must return a String (or null).
Prior to generics, there was absolutely no possibility that an object's runtime type is inconsistent with its static type. This is obviously a very desirable property.
We can cast an object to an incorrect runtime type, but the cast would fail immediately, at the exact site of casting; the error stops there.
Object obj = "string";
((Integer)obj).intValue();
// we are not gonna get an Integer object
With the introduction of generics, along with type erasure (the root of all evils), now it is possible that a method returns String at compile time, yet returns Integer at runtime. This is messed up. And we should do everything we can to stop it from the source. It is why the compiler is so vocal about every sight of unchecked casts.
The worst thing about heap pollution is that the runtime behavior is undefined! Different compiler/runtime may execute the program in different ways. See case1 and case2.
They are different because ClassCastException and ArrayStoreException are different.
Generics compile-time type checking rules should ensure that it's impossible to get a ClassCastException in a place where you didn't put an explicit cast, unless your code (or some code you called or called you) did something unsafe at compile-time, in which case you should (or whatever code did the unsafe thing should) receive a compile-time warning about it.
ArrayStoreException, on the other hand, is a normal part of how arrays work in Java, and pre-dates Generics. It is not possible for compile-time type checking to prevent ArrayStoreException because of the way the type system for arrays is designed in Java.

Why can't I create an array of a type parameter in Java?

Well, I have read a lot of answers to this question, but I have a more specific one. Take the following snippet of code as an example.
public class GenericArray<E>{
E[] s= new E[5];
}
After type erasure, it becomes
public class GenericArray{
Object[] s= new Object[5];
}
This snippet of code seems to work well. Why does it cause a compile-time error?
In addition, I have known from other answers that the following codes work well for the same purpose.
public class GenericArray<E>{
E[] s= (E[])new Object[5];
}
I've read some comments saying that the piece of code above is unsafe, but why is it unsafe? Could anyone provide me with a specific example where the above piece of code causes an error?
In addition, the following code is wrong as well. But why? It seems to work well after erasure, too.
public class GenericArray<E>{
E s= new E();
}
Array declarations are required to have a reifiable type, and generics are not reifiable.
From the documentation: the only type you can place on an array is one that is reifiable, that is:
It refers to a non-generic class or interface type declaration.
It is a parameterized type in which all type arguments are unbounded wildcards (§4.5.1).
It is a raw type (§4.8).
It is a primitive type (§4.2).
It is an array type (§10.1) whose element type is reifiable.
It is a nested type where, for each type T separated by a ".", T itself is reifiable.
This means that the only legal declaration for a "generic" array would be something like List<?>[] elements = new ArrayList[10];. But that's definitely not a generic array, it's an array of List of unknown type.
The main reason that Java is complaining about the you performing the cast to E[] is because it's an unchecked cast. That is, you're going from a checked type explicitly to an unchecked one; in this case, a checked generic type E to an unchecked type Object. However, this is the only way to create an array that is generic, and is generally considered safe if you have to use arrays.
In general, the advice to avoid a scenario like that is to use generic collections where and when you can.
This snippet of code seems to work well. Why does it cause a compile-time error?
First, because it would violate type safety (i.e. it is unsafe - see below), and in general code that can be statically determined to do this is not allowed to compile.
Remember that, due to type erasure, the type E is not known at run-time. The expression new E[10] could at best create an array of the erased type, in this case Object, rendering your original statement:
E[] s= new E[5];
Equivalent to:
E[] s= new Object[5];
Which is certainly not legal. For instance:
String[] s = new Object[10];
... is not compilable, for basically the same reason.
You argued that after erasure, the statement would be legal, implying that you think this means that the original statement should also be considered legal. However this is not right, as can be shown with another simple example:
ArrayList<String> l = new ArrayList<Object>();
The erasure of the above would be ArrayList l = new ArrayList();, which is legal, while the original is clearly not.
Coming at it from a more philosophical angle, type erasure is not supposed to change the semantics of the code, but it would do so in this case - the array created would be an array of Object rather than an array of E (whatever E might be). Storing a non-E object reference in it would then be possible, whereas if the array were really an E[], it should instead generate an ArrayStoreException.
why is it unsafe?
(Bearing in mind we are now talking about the case where E[] s= new E[5]; has been replaced with E[] s = (E[]) new Object[5];)
It is unsafe (which in this instance is short for type unsafe) because it creates at run-time a situation in which a variable (s) holds a reference to an object instance which is not a sub-type of the variable's declared type (Object[] is not a subtype of E[], unless E==Object).
Could anyone provide me with a specific example where the above piece of code causes an error?
The essential problem is that it is possible to put non-E objects into an array that you create by performing a cast (as in (E[]) new Object[5]). For example, say there is a method foo which takes an Object[] parameter, defined as:
void foo(Object [] oa) {
oa[0] = new Object();
}
Then take the following code:
String [] sa = new String[5];
foo(sa);
String s = sa[0]; // If this line was reached, s would
// definitely refer to a String (though
// with the given definition of foo, this
// line won't be reached...)
The array definitely contains String objects even after the call to foo. On the other hand:
E[] ea = (E[]) new Object[5];
foo(ea);
E e = ea[0]; // e may now refer to a non-E object!
The foo method might have inserted a non-E object into the array. So even though the third line looks safe, the first (unsafe) line has violated the constraints that guarantee that safety.
A full example:
class Foo<E>
{
void foo(Object [] oa) {
oa[0] = new Object();
}
public E get() {
E[] ea = (E[]) new Object[5];
foo(ea);
return ea[0]; // returns the wrong type
}
}
class Other
{
public void callMe() {
Foo<String> f = new Foo<>();
String s = f.get(); // ClassCastException on *this* line
}
}
The code generates a ClassCastException when run, and it is not safe. Code without unsafe operations such as casts, on the other hand, cannot produce this type of error.
In addition, the following code is wrong as well. But why? It seems to work well after erasure, too.
The code in question:
public class GenericArray<E>{
E s= new E();
}
After erasure, this would be:
Object s = new Object();
While this line itself would be fine, to treat the lines as being the same would introduce the semantic change and safety issue that I have described above, which is why the compiler won't accept it. As an example of why it could cause a problem:
public <E> E getAnE() {
return new E();
}
... because after type erasure, 'new E()' would become 'new Object()' and returning a non-E object from the method clearly violates its type constraints (it is supposed to return an E) and is therefore unsafe. If the above method were to compile, and you called it with:
String s = <String>getAnE();
... then you would get a type error at runtime, since you would be attempting to assign an Object to a String variable.
Further notes / clarification:
Unsafe (which is short for "type unsafe") means that it could potentially cause a run-time type error in code that would otherwise be sound. (It actually means more than this, but this definition is enough for purposes of this answer).
it's possible to cause a ClassCastException or ArrayStoreException or other exceptions with "safe" code, but these exceptions only occur at well defined points. That is, you can normally only get a ClassCastException when you perform a cast, an operation that inherently carries this risk. Similarly, you can only get an ArrayStoreException when you store a value into an array.
the compiler doesn't verify that such an error will actually occur before it complains that an operation is unsafe. It just knows that that certain operations are potentially able to cause problems, and warns about these cases.
that you can't create a new instance of (or an array of) a type parameter is both a language feature designed to preserve safety and probably also to reflect the implementation restrictions posed by the use of type erasure. That is, new E() might be expected to produce an instance of the actual type parameter, when in fact it could only produce an instance of the erased type. To allow it to compile would be unsafe and potentially confusing. In general you can use E in place of an actual type with no ill effect, but that is not the case for instantiation.
A compiler can use a variable of type Object to do anything a variable of type Cat can do. The compiler may have to add a typecast, but such typecast will either throw an exception or yield a reference to an instance of Cat. Because of this, the generated code for a SomeCollection<T> doesn't have to actually use any variables of type T; the compiler can replace T with Object and cast things like function return values to T where necessary.
A compiler cannot use an Object[], however, to do everything a Cat[] can do. If a SomeCollection[] had an array of type T[], it would not be able to create an instance of that array type without knowing the type of T. It could create an instance of Object[] and store references to instances of T in it without knowing the type of T, but any attempt to cast such an array to T[] would be guaranteed to fail unless T happened to be Object.
Let's say generic arrays are allowed in Java. Now, take a look at following code,
Object[] myStrs = new Object[2];
myStrs[0] = 100; // This is fine
myStrs[1] = "hi"; // Ambiguity! Hence Error.
If user is allowed to create generic Array, then user can do as I've shown in above code and it will confuse compiler. It defeats the purpose of arrays (Arrays can handle only same/similar/homogeneous type of elements, remember?). You can always use array of class/struct if you want heterogeneous array.
More info here.

Java conversion Integer to String inconsistency

Why does conversion from collection of Integers to collection of String work but it fails when I actually has to convert the Integer to String?
Why dosent it fail/catch it earlier?
In my example below
static <T extends List<?> >
void testConversion(T... args)
{
**// Didnt catch here?**
List<String>[] slarray = (List<String>[])args;
System.out.printf("Value is %s\n", slarray[0].get(0));
**// Didnt catch here?**
List<String> slist = (List<String>)args[0];
// FAIL runtime exception
String s2 = slist.get(0);
// FAIL exception
String s = slarray[0].get(0);
}
public static void main( String[] args )
{
testConversion(Arrays.asList(11,12), Arrays.asList(21,22));
}
This is because Java Generics are a compile time thing, and the actual type is erased when the program runs.
You defined your T as a "List of whatever". You then cast a reference to a "List of whatever" to a "List of String". It may be possible that this type cast will work (thus, it is not a compile-time error), and so the compiler doesn't complain, but leaves the issue to the run-time system.
In run time, it's just a reference to a List. So the run time environment doesn't complain either - you cast an array of List and assign it to an array of List, and the actual type is erased at this point.
But when it comes to getting the actual values, the run time environment can see that the value cannot actually be assigned to a String, and so it throws an exception.
When you provide a cast in your code, you are telling the compiler, "I know you have an instance of x, but I know it's really a y. Treat it as a y." If the cast is possible at all, then the compiler will allow it.
You are telling the compiler that args, which it knows is a T[], can be cast to a List<String>[]. You are also telling the compiler that args[0], which it accepts to be a T, is a List<String>.
It happens here that when calling testConversion, you are passing List<Integer>s. The type T is inferred to be Integer. However, because of type erasure, at runtime, they are really Lists. Also, the cast to List<String>[] and List<String> succeed, because at runtime, they are really casts to List[] and List, respectively.
When you compiled, you should have received an unchecked cast warning about casting to List<String>[] and to List<String>. It was warning you about type safety. It also inserted a cast to String on the call to get, because you told it that it was a List<String>. When you finally attempted to get a String out your List<String>, the JVM threw a runtime exception because the element really was an Integer.
When you casted args to a List<String>[], and when you casted args[0] to a List<String>, you lied to the compiler, but the JVM caught your lie.

Generic and cast in java

suppose i have the following code
E[] arrayVar = (E[])new Object[1];// It causes a compiler warning
the question is, in what situation when the code will cause a run time error,
That code will only cause a runtime error if you try to assign the result to the reified type of E:
createArray(); // no exception
String[] arr = createArray(); // causes ClassCastException
public static <E> E[] createArray() {
return (E[]) new Object[1];
}
The safe way to create generic arrays is with the use of Array.newInstance:
public static <E> E[] createArray(Class<E> component) {
return (E[]) Array.newInstance(component, 1);
}
This method will not cause a ClassCastException.
to expand LastStar007's Answer
The problem with that statement is that java cannot determine the type of object E until runtime. also unfortunately, java does not provide a simple way to determine the type of a specified object (getClass() does not work for this).
your best bet (if it is numerical data) would be to check against the maximum of each mnemonic or for character data, check against the storage size per a character. yes it sounds tedious, but java has no typeinfo, like C++ which would make things a lot easier
It will never cause a run-time error because it will always cause a compile-time error. An Object cannot be down-cast to an E, and therefore an Object[] cannot be down-cast to an E[]. Beyond that, you can't create an array of type E because the class has no idea what the type of E is at compile-time.

Categories

Resources