Are Generics Specialized During Compilation or They Are Just Like Java Generics Only for Compile Time Checks

Are generics specialized during compilation or they are just like java generics only for compile time checks?

Swift starts by compiling a single implementation that does dynamic type checking, but the optimizer can then choose to clone off specialized implementations for particular types when the speed vs code size tradeoffs make sense. Ideally, this gets 90% of the speedup of always cloning, without the code size and compilation time exploding.

Are Swift generics separately compiled?

It's a combination of the two, depending on what the optimizer decides will get better performance. They talk about it near the end of WWDC Session 404: Advanced Swift. The slide says:

Swift can run generic code directly

Optimizer can produce specialized versions of generic code at will

  • Separate compilation of generics
  • Faster compiles
  • Flexibility to trade code size for speed

Are generics removed by the compiler at compile time

The statement that you quoted is correct: the compiler uses the generic type information internally during the process of compilation, generating type-related errors as it processes the sources. Then, once the validation is done, the compiler generates type-erased byte code, with all references to generic types replaced with their respective type erasure.

This fact becomes evident when you look at the types through reflection: all interfaces, classes, and functions become non-generic, with all types tied to generic type parameters replaced with a non-generic type based on the generic type constraints specified in the source code. Although reflection API does have provisions for accessing some of the information related to generics* at runtime, the virtual machine is unable to check the exact generic type for compatibility when you access your classes through reflection.

For example, if you make a class member of type List<String> and try setting a List<Integer> into it, the compiler is going to complain. If you try to do the same through reflection, however, the compiler is not going to find out, and the code will fail at run-time in the same way that it would without generics:

class Test {
private List<String> myList;
public void setList(List<String> list) {
myList = list;
}
public void showLengths() {
for (String s : myList) {
System.out.println(s.length());
}
}
}

...

List<Integer> doesNotWork = new ArrayList<Integer>();
doesNotWork.add(1);
doesNotWork.add(2);
doesNotWork.add(3);
Test tst = new Test();
tst.setList(doesNotWork); // <<== Will not compile
Method setList = Test.class.getMethod("setList", List.class);
setList.invoke(tst, doesNotWork); // <<== This will work;
tst.showLengths(); // <<== However, this will produce a class cast exception

Demo on ideone.


* See this answer for details on getting information related to generic types at runtime.

Are the type of java generics decided at compile time? And why can we change the type at runtime?

The seemingly odd behavior is due to how java implements generics using type erasure. You can view this question for a more detailed explanation of type erasure but I can summarize it's effect in this scenario.

When calling

 new MyType<Integer>("hello", 1, "world");

it appears the constructor attempts to cast both the int type "1" and the String type "world" to the "member" instance variable, which would be of type Integer:

        member = (T) o2;
System.out.println(t.getClass());
System.out.println(member.getClass());
member = (T) o3;

However at run time, this isn't what is happening - with type erasure, at compilation, the types used in generics get "erased" and the inner class code gets compiled to:

public static class MyType {
private Object member;

public MyType(Object o, Object o2, Object o3) {
Object t = o;
member = o2;
System.out.println(t.getClass());
System.out.println(member.getClass());
member = o3;
System.out.println(member.getClass());
}
}

So at this point Object types are being assigned to Object types which will not cause any errors - although the actual referenced objects are of different types (o2 is an int, o3 is a String). That's why member.getClass() returns the actual class of the reference (in this case Integer and String).

So when does the generic parameter

<Integer>

actually come into play? When it is used. I made a minor modification to your code that attempts to access the member field and call an Integer method on it:

public class Test {
public static void main(String[] args) {
MyType<Integer> test = new MyType<Integer>("hello", 1, "world");
// attempting to use the member variable as an Integer
System.out.println(test.getMember().doubleValue());
}

public static class MyType<T> {
private T member;

public MyType(Object o, Object o2, Object o3) {
T t = (T) o;
member = (T) o2;
System.out.println(t.getClass());
System.out.println(member.getClass());
member = (T) o3;
System.out.println(member.getClass());
}

public T getMember() {
return member;
}
}
}

Attempting to run the code throws the following exception:

Exception in thread "main" java.lang.ClassCastException: 
java.lang.String cannot be cast to java.lang.Integer

When test.getMember().doubleValue() gets called the compiler then tries to cast it to your Integer generic parameter - but the argument you had set it to ("world") is a String so a ClassCastException gets thrown. The compiled call would look something like this:

System.out.println(((Integer) test.getMember()).doubleValue());

Note that the same thing happens if "member" was public and I skipped using a getter (as in test.member.doubleValue()).

So essentially your ClassCastException was delayed because the types get erased. The Oracle docs on this were useful as a resource: https://docs.oracle.com/javase/tutorial/java/generics/genMethods.html. If anyone knows better, please correct me on my explanation of type erasure if necessary. Thanks.

Why does this use of Generics not throw a runtime or compile time exception?

This is because overload resolution resolved your println call to println(Object), since there is no println(Integer).

Keep in mind that Java's generics are erased at runtime. And casts like (E) "Foo" are removed, and are moved to call site. Sometimes this is not necessary, so things are casted to the right type only when needed.

In other words, no casts are performed inside getFoo. The language spec supports this:

Section 5.5.2 Checked Casts and Unchecked Casts

  • The cast is a completely unchecked cast.

    No run-time action is performed for such a cast.

After erasure, getFoo returns Object. And that gets passed into println(Object), which is perfectly fine.

If I call this method and pass foo.getFoo, I will get an error:

static void f(Integer i) {
System.out.println(i);
}
// ...
f(foo.getFoo()); // ClassCastException

because this time it needs to be casted.

Java Generics : Is any meta information about the generic type preserved at runtime as well?

Of course the information that a class is generic is supported.

In other words: when you decompile ArrayList.class you will find hints about the fact that this class allows for one generic type parameter. In other words: class files contain meta information. And using reflection it is possible to inspect this meta information at runtime.

But when you have another class that uses some List<Integer> object - then you do not find information about that "list uses an Integer" in the compiled class - unless you use some specific patterns, as outlined here for example.

So the answer is basically: for almost all use cases of practical relevance, "generics" are compile time only.

Example:

public class GenericsExample<T> {
private T member;
public T foo(T bar) {
return member;
}
}

Now run: javap -p -c GenericsExample

Compiled from "GenericsExample.java"
public class GenericsExample<T> {
private T member;

public GenericsExample();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public T foo(T);
Code:
0: aload_0
1: getfield #2 // Field member:Ljava/lang/Object;
4: areturn
}

As you can see the decompiler understands that the class uses that generic type T. For more details see here or there.

When does type checking happen in Java

It also happens during runtime because you can cast objects to their subtypes. You could manually force it to do type checking again at any point with instanceof.

You see Java is not a completely statically typed language. Whenever you cast an object from a type to a subtype, the JVM performs a dynamic (runtime) typecheck to check that the object really is an instance of the subtype. Using instanceof is another example of dynamic type checking.

from this answer

Java Compile-Time Type Checking For Polymorphic Collections

You can't do this.

Java does not support compile-time type checking for polymorphic generic collections. You can add anything to a Collection<?> but when retrieving you always get back Object and have to cast to the appropriate type, which will always involve a runtime check.

The compiler was trying to tell you this but you disabled the warnings with @SuppressWarnings("unchecked"), which is like putting black tape over the temperature warning light in your car and then being surprised when the engine overheats.

You say:

I can catch this problem by doing this.

map.put("special", 100);
// correct
int special = map.get("special", Integer.class);

// incorrect and caught at compile-time
boolean special = map.get("special", Integer.class);

Though I don't want to specify the second parameter I want to hide that. Is that possible?

Think this through. The put calls could (will) have happened far, far away (i.e. not in the current source file, possibly something compiled last year). The compiler has no idea what types are contained in the Map at runtime for any specific key. In fact on two different executions a given key could map to values of completely different types. How is the compiler, when it is compiling the source, supposed to know the value type associated with a key in the future? Or that the type will always be the same one?

From a comment by the OP:

Though making 100% type-safe collections using a map is possible. See here https://github.com/atomicint/aj8/tree/master/server/src/main/java/org/apollo/game/attribute

Notice in AttributeMap.java:

@SuppressWarnings("unchecked")  
public <T> T get(AttributeKey<T> key) {
...

All that code does is push the runtime check into AttributeMap<>#get(), and it also resorts to @SuppressWarnings("unchecked"). It just hides the runtime check so your code doesn't have to hide it. The runtime check and potential ClassCastException are still there, and this is most definitely NOT any more type safe.



Related Topics



Leave a reply



Submit