Array return can be used in assignment, but not in a loop

I sent an answer to another question when I came across a little secret. The class definition (slightly modified from the original questionnaire) is here:

public class Playground<T>{ private int pos; private final int size; private T[] arrayOfItems; public Playground(int size){ this.size = size; pos = 0; arrayOfItems = (T[]) new Object[size]; } public void addItem(T item) { arrayOfItems[pos] = item; pos++; } public void displayItems() { for(int i = 0;i<pos;i++){ System.out.println(arrayOfItems[i]); } } public T[] returnItems() { return (T[]) arrayOfItems; } } 

Basically, we create a new Playground<String> animals = new Playground<String>(5); , Playground<String> animals = new Playground<String>(5); and put some lines for animals in it. (Dog, cat, etc.).

The secret is that it works:

 Object[] s = animals.returnItems(); for(int i=0; i < s.length; i++) { System.out.println(s[i]); } 

But this ClassCastException in the loop loop declaration.

 for(int i=0; i < animals.returnItems().length; i++) { System.out.println(animals.returnItems()[i]); } 

Both Object[] and String[] have variable lengths. Why is using the accessor method in a loop declaration an exception?

+6
source share
1 answer

The reason that a ClassCastException exists is that it cannot be thrown from Object[] to String[] because of what the compiler does when using generics. When returnItems() called, the compiler inserts the listing into String[] , because returnItems returns T[] . When compiled, the erasure type means that it returns Object[] , but since T is String here, the compiler inserts the listing into String[] . But the original arrayOfItems object arrayOfItems not String[] , it is a Object[] , so the failure is executed.

This should have led to the prevention of an “unchecked throw” at compile time, from Object[] to T[] .

Instead, you will need to follow the guidelines here. How to create a shared array in Java? when creating a shared array.

Take Class<T> in your constructor so that you can call Array.newInstance and get T[] from the start.

 @SuppressWarnings("unchecked") // This suppression is safe. public Playground(int size, Class<T> clazz){ this.size = size; pos = 0; arrayOfItems = (T[]) Array.newInstance(clazz, size); } 

Then you can create animals by passing String.class :

 Playground<String> animals = new Playground<String>(5, String.class); 

Update

The following is a reasonable explanation of why the first example works (assigning Object[] ) when the second example does not work (accessing the length field directly to the return type of the returnItems() method.

First example

 Object[] s = animals.returnItems(); for(int i=0; i < s.length;i++) { System.out.println(s[i]); } 

JLS, section 5.2 , describes "Assignment Contexts" that determine what happens when an assignment is made from a variable expression.

The only exceptions that may result from conversions in the destination context are:

  • ClassCastException if, after the applied transformations, the resulting value is an object that is not an instance of a subclass or subterminal of erasure (§4.6) of the variable type.

This circumstance can arise only as a result of contamination of the heap (§4.12.2). In practice, implementations should only perform actions when accessing a field or method of an object of a parameterized type, when the erased field type or the erased type of the returned method differs from its uneven type.

...

The compiler did not need to embed the listing in String[] here. When the length field is accessed later, the variable is already of type Object[] , so there is no problem.

Second example

 for(int i=0; i < animals.returnItems().length;i++) { System.out.println(animals.returnItems()[i]); } 

Here ClassCastException is independent of the for loop; this error occurs when printing length simply:

 System.out.println(animals.returnItems().length); 

This is a field access expression covered by JLS, section 15.11.1 .

Identifier

[T] he names one available member field in type T, and the type of the field access expression is the type of the member field after the capture transformation (§5.1.10).

The capture conversion captures the type as String[] . The compiler must insert the cast into String[] here for the same reason as inserting a role to invoke a method — a field or method can exist only on a captured type.

Since the type of arrayOfItems really Object[] , a crash is executed.

As described above, creating a shared array using Array.newInstance solves this problem as a real String[] . With this change, the inserted cast is still present, but this time it succeeds.

+7
source

Source: https://habr.com/ru/post/981122/


All Articles