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.