11

Consider the following:

 public class GenericTest {
    static void print(int x) {
        System.out.println("Int: " + x);
    }
    static void print(String x) {
        System.out.println("String: " + x);
    }

    static void print(Object x) {
        System.out.println("Object: " + x);
    }

    static <T> void printWithClass(T t) {
        print(t);
    }
    public static void main(String argsp[]) {
        printWithClass("abc");
    }
}

It prints Object: abc. Why doesn't it print String: abc?

sgowd
  • 2,064
  • 20
  • 28
Shmoopy
  • 4,798
  • 3
  • 28
  • 67
  • see this question on getting the type of T - [Get Generic Type of Class at Runtime](http://stackoverflow.com/questions/3403909/get-generic-type-of-class-at-runtime) – csturtz Feb 16 '12 at 14:39

8 Answers8

10

This is because of Java type erasure: your

static <T> void printWithClass(T t) {
    print(t);
}

is actually a syntactic sugar on top of

static void printWithClass(Object t) {
    print(t);
}

To be fair, that "syntactic sugar" lets the compiler do some very nice and important checking, but at run-time there is only one copy of the printWithClass method, and it uses java.lang.Object as the type of your variable t.

If you have experienced generics in other languages (C#, C++ templates, Ada) type erasure would come in contrast to what you know, but this is how it works under the cover.

Sergey Kalinichenko
  • 675,664
  • 71
  • 998
  • 1,399
  • It's not about type erasure, it's about the generic bounds that are available _at compile time_. If we use ``, the output is `String: abc`. – Daniel Lubarov Feb 16 '12 at 14:47
  • 1
    @Daniel absolutely - because the method would become `static void printWithClass(String t)` then. But it would remain a single method, with a single type, bound at compile time to call a single overload of the static `print` method. – Sergey Kalinichenko Feb 16 '12 at 14:49
4

Java supports method overriding (dynamic type binding), but not what you are trying to achieve (overloading is static polymorphism and not dynamic).

In order to achieve what you want to achieve in Java, you need double dispatch.

Visitor Pattern should be your friend here.

I have written you a code sample.

public class Test {

    public static void main(String argsp[]) {
        PrintTypeImpl typeImpl = new PrintTypeImpl(new StringType(), new IntType(), new ObjectType());
        typeImpl.accept(new PrintVisitor());
    }

    static final class PrintVisitor implements TypeVisitor {
        public void visit(IntType x) {
            System.out.println("Int: ");
        }

        public void visit(StringType x) {
            System.out.println("String: ");
        }

        public void visit(ObjectType x) {
            System.out.println("Object: ");
        }
    }

    interface TypeVisitor {
        void visit(IntType i);

        void visit(StringType str);

        void visit(ObjectType obj);
    }

    interface PrintType {
        void accept(TypeVisitor visitor);
    }

    static class StringType implements PrintType {
        @Override
        public void accept(TypeVisitor visitor) {
            visitor.visit(this);
        }
    }

    static class ObjectType implements PrintType {
        @Override
        public void accept(TypeVisitor visitor) {
            visitor.visit(this);
        }
    }

    static class IntType implements PrintType {
        @Override
        public void accept(TypeVisitor visitor) {
            visitor.visit(this);
        }
    }

    static final class PrintTypeImpl implements PrintType {

        PrintType[] type;

        private PrintTypeImpl(PrintType... types) {
            type = types;
        }

        @Override
        public void accept(TypeVisitor visitor) {
            for (int i = 0; i < type.length; i++) {
                type[i].accept(visitor);
            }
        }
    }

}
Scorpion
  • 3,882
  • 21
  • 37
4

It's not about type erasure, it's a compilation issue and the same thing would happen if the JVM stored method generics at runtime. It's also not about type inference -- the compiler infers <String> as you would expect.

The issue is that when the compiler is generating code for printWithClass, it needs a specific method signature to associate with the print call. Java has no multiple dispatch, so it can't put a vague signature in the method table and decide what to invoke at runtime. The only upper bound on T is Object, so the only method that matches is print(Object).

Daniel Lubarov
  • 7,464
  • 1
  • 34
  • 53
0

Because java generics aren't generics the way you think they are. When generic java code gets compiled all type information actually gets stripped away, and only the base known type remains. In this case that type is Object.

Generics in java is really only compiler trickery, where the compiler removes the casts that would otherwise be necessary and induces a compile time restraint. In the end, all that is left is the base type when this actually gets compiled into byte code.

This process is called type erasure. This previous question is helpful to understand what actually goes on.

Community
  • 1
  • 1
Dervall
  • 5,691
  • 2
  • 23
  • 46
0

Because it can know that only on run time, but in reality, since java is a compiled language and not a scripted one, it's being decided on compilation time.

The java generics allow "a type or method to operate on objects of various types while providing compile-time type safety."

You can of course try something like:

static <T extends String> void printWithClass(T t) {
    print(t);
}

though it's not what you're after, which is not possible since the compiler is calling the shots.

Nitzan Tomer
  • 120,901
  • 33
  • 273
  • 264
0
static <T> void printWithClass(T t) {
    print(t);
}

will be comipled to

static void printWithClass(Object t) {
    print(t);
}
Farmor
  • 8,604
  • 7
  • 34
  • 59
0

Generics are interpreted by the compiler and they enforce additional type check to avoid any runtime casting issues. The Generic Type information is lost at runtime. So at runtime what printWithClass receives is just object and not String and hence your result.

Santosh
  • 16,973
  • 4
  • 50
  • 75
0

Extra example to clarify:

public class OverloadingWithGenerics {

    static void print(Integer x) {
        System.out.println("Integer: " + x);
    }

    static void print(Double x) {
        System.out.println("Double: " + x);
    }

    static void print(Number x) {
        System.out.println("Number: " + x);
    }

    static <T extends Number> void printWithClass(T t) {
        print(t);
    }

    public static void main(String argsp[]) {
        printWithClass(new Integer(1234));
    }
}

This prints:

Number: 1234
Adriaan Koster
  • 14,226
  • 4
  • 41
  • 56