Skip to content

Instantly share code, notes, and snippets.

@martijndwars
Created March 16, 2020 13:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save martijndwars/a356368586b8304c9acc6e044e440909 to your computer and use it in GitHub Desktop.
Save martijndwars/a356368586b8304c9acc6e044e440909 to your computer and use it in GitHub Desktop.
Generics: why does this work?
import java.util.ArrayList;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
ArrayConstructor<Integer> inner = new ArrayConstructor<Integer>();
inner.add("Foo");
inner.add("Bar");
inner.add("Baz");
inner.add(42);
System.out.println(Arrays.toString(inner.materialize()));
System.out.println("Done");
}
public static class ArrayConstructor<T> {
public ArrayList<T> instance;
public ArrayConstructor() {
instance = new ArrayList<T>();
}
public void add(Object value) {
instance.add((T) value);
}
public T[] materialize() {
return (T[]) instance.toArray();
}
}
}
@martijndwars
Copy link
Author

martijndwars commented Mar 16, 2020

> javac Main.java; java Main
[Foo, Bar, Baz, 42]
Done

Why is there no type error anywhere? Why does inner.materialize() have the runtime type Object[]?

@xicalango
Copy link

xicalango commented Mar 18, 2020

import java.util.ArrayList;
import java.util.Arrays;

public class Main {
  public static void main(String[] args) {
    ArrayConstructor<Integer> inner = new ArrayConstructor<Integer>();
    inner.add("Foo");
    inner.add("Bar");
    inner.add("Baz");
    inner.add(42);
    System.out.println(Arrays.toString(inner.materialize()));
    System.out.println("Done");

    System.out.println(inner.get(0));
    Integer i = inner.get(0);
    System.out.println(i);
  }

  public static class ArrayConstructor<T> {
    public ArrayList<T> instance;

    public ArrayConstructor() {
      instance = new ArrayList<T>();
    }

    public T get(int index) {
      return instance.get(index);
    }

    public void add(Object value) {
      instance.add((T) value);
    }

    public T[] materialize() {
      return (T[]) instance.toArray();
    }
  }
}

this compiles with:

$ javac Main.java
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

but running gives:

$ java Main
[Foo, Bar, Baz, 42]
Done
Foo
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
        at Main.main(Main.java:15)

note how "Foo" still prints, although it would have wrong type here as well.

in the bytecode:

// System.out.println(inner.get(0)); (no cast after "get")
      60: iconst_0
      61: invokevirtual #14                 // Method Main$ArrayConstructor.get:(I)Ljava/lang/Object;
      64: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V

//    Integer i = inner.get(0); (notice the "checkcast" after get
      67: aload_1
      68: iconst_0
      69: invokevirtual #14                 // Method Main$ArrayConstructor.get:(I)Ljava/lang/Object;
      72: checkcast     #16                 // class java/lang/Integer
      75: astore_2

//     System.out.println(i);
      76: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      79: aload_2
      80: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V

@xicalango
Copy link

compiling with -Xlint:unchecked gives you this btw:

Main.java:31: warning: [unchecked] unchecked cast
      instance.add((T) value);
                       ^
  required: T
  found:    Object
  where T is a type-variable:
    T extends Object declared in class ArrayConstructor
Main.java:35: warning: [unchecked] unchecked cast
      return (T[]) instance.toArray();
                                   ^
  required: T[]
  found:    Object[]
  where T is a type-variable:
    T extends Object declared in class ArrayConstructor
2 warnings

@xicalango
Copy link

ah one last thing: the bytecode for some methods in ArrayConstructor:

// aweld: no cast here at all
  public void add(java.lang.Object); 
    Code:
       0: aload_0
       1: getfield      #4                  // Field instance:Ljava/util/ArrayList;
       4: aload_1
       5: invokevirtual #6                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
       8: pop
       9: return

// aweld: here we just have a checkcast for "object array"
  public T[] materialize();
    Code:
       0: aload_0
       1: getfield      #4                  // Field instance:Ljava/util/ArrayList;
       4: invokevirtual #7                  // Method java/util/ArrayList.toArray:()[Ljava/lang/Object;
       7: checkcast     #8                  // class "[Ljava/lang/Object;"
      10: areturn

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment