2

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());
        }
    }
}
weston
  • 51,132
  • 20
  • 132
  • 192
shredding
  • 131
  • 9
  • What output would you expect? – weston Feb 22 '17 at 03:11
  • Also, why do you ask "why can we change the type at runtime?" you gave the method a `String`, an `Integer` and another `String`, so where has the type "changed". That's exactly the output. – weston Feb 22 '17 at 03:21
  • Would be more maintainable and elegant to make the method generic and reduce the overhead of casting back and forth. – Ousmane D. Feb 22 '17 at 03:24
  • 2
    I think you're just missing the concept of type erasure: http://stackoverflow.com/questions/339699/java-generics-type-erasure-when-and-what-happens – weston Feb 22 '17 at 03:31
  • 2
    If you turn on all compiler warnings, you will see that your casts are syntactically permitted but are not actually valid. – VGR Feb 22 '17 at 04:19

2 Answers2

4

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.

Community
  • 1
  • 1
  • Earlier i also tried to make "member" public and access it using test.member = "hello". Which raised a compile time error as you described. If I understand this correctly, the generic is only going to be type checked when a property is called outside the class but not inside the class (inside a constructor or a method belong to the "MyType" class) – shredding Feb 22 '17 at 07:32
  • I think this quote further confirms it :The type of a constructor, instance method, or non-static field of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C. – shredding Feb 22 '17 at 08:07
-1

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.

Dragos Pop
  • 398
  • 2
  • 8
  • "If you use those variables, you might crash the app." But they are using them. Why don't you provide a modified example of this that will crash? – weston Feb 22 '17 at 03:29
  • I cannot, not only because i don't have java but also because the ctash random. Basicaly you force the compiler to accept the fact that a string is an int – Dragos Pop Feb 22 '17 at 03:34
  • You don't have Java? Here you go, don't say I never give you anything: http://ideone.com/ukOSpr – weston Feb 22 '17 at 03:35
  • And a type crash isn't going to be random. If you can edit that snippet I gave you to cause it, then it's going to be 100% reproducible. – weston Feb 22 '17 at 03:36
  • Brandon Wright explained it, you are right, they are not random. Thanks Brandon Wright for the detailed answer. – Dragos Pop Feb 22 '17 at 09:00