简体   繁体   中英

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

Does the output of this code make sense to you? It gives this output:

class java.lang.String
class java.lang.Integer
class java.lang.String

So it seems that the type is assigned to Integer when the test object is initiated, yet the type of member is changing during runtime. Why does this occur?

public class Test {
    public static void main(String[] args) {
        MyType<Integer> test = new MyType<Integer>("hello", 1, "world");
        }

    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());
        }
    }
}

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.

It does not change, you cast the object to another type, but the reference is the same. Your code is wrong in the sense that you upcast an object to the wrong type. But since the referance is to the original object, getClass will return the type of the real object. If you use those variables, you might crash the app.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM