Генерирование Java - вывод типа

Рассмотрим следующее:

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

Он печатает Object: abc. Почему он не печатает String: abc?

Ответ 1

Java поддерживает переопределение методов (привязка динамического типа), но не то, что вы пытаетесь достичь (перегрузка - это статический полиморфизм, а не динамический).

Чтобы добиться того, чего вы хотите достичь на Java, вам потребуется двойная отправка.

Шаблон посетителя должен быть вашим другом здесь.

Я написал вам образец кода.

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

}

Ответ 2

Это из-за стирание стилей Java: ваш

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

- фактически синтаксический сахар поверх

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

Справедливости ради следует, что этот "синтаксический сахар" позволяет компилятору выполнять очень приятную и важную проверку, но во время выполнения существует только одна копия метода printWithClass, и она использует java.lang.Object как тип ваша переменная t.

Если у вас есть опытные образцы в других языках (стирание С#, С++ templates, Ada), это будет контрастировать с тем, что вы знаете, но так оно работает под обложкой.

Ответ 3

Это не о стирании типа, это проблема компиляции, и то же самое произойдет, если JVM хранит универсальные методы во время выполнения. Это также не касается вывода типа - компилятор сообщает <String>, как и следовало ожидать.

Проблема заключается в том, что когда компилятор генерирует код для printWithClass, ему нужна специальная сигнатура метода для связи с вызовом print. Java не имеет множественной отправки, поэтому не может поставить неопределенную подпись в таблице методов и решить, что вызывать во время выполнения. Единственная верхняя граница на T равна Object, поэтому единственный метод, который соответствует, равен print(Object).

Ответ 4

Потому что дженерики java не являются дженериками, как вы думаете. Когда обобщенный код Java компилируется, вся информация о типе фактически удаляется, и остается только базовый известный тип. В этом случае этот тип Object.

Обобщения в java - это действительно только обман компилятора, когда компилятор удаляет приведения, которые в противном случае были бы необходимы, и вызывая сдерживание времени компиляции. В конце концов, все, что осталось, это базовый тип, когда он фактически скомпилирован в байтовый код.

Этот процесс называется стиранием типа . Этот предыдущий вопрос полезен для понимания того, что на самом деле происходит.

Ответ 5

Потому что он может знать, что только во время выполнения, но на самом деле, поскольку java - это скомпилированный язык, а не сценарий, он определяется во время компиляции.

Дженерики java позволяют "тип или метод работать на объектах разных типов при обеспечении безопасности времени компиляции.

Вы можете, конечно, попробовать что-то вроде:

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

хотя это не то, что вам нужно, что невозможно, поскольку компилятор вызывает снимки.

Ответ 6

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

будет объединен с

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

Ответ 7

Обобщения интерпретируются компилятором, и они обеспечивают дополнительную проверку типов, чтобы избежать проблем с кастомным запуском. Информация об общем типе потеряна во время выполнения. Таким образом, во время выполнения, который получает printWithClass, это просто объект, а не String и, следовательно, ваш результат.

Ответ 8

Дополнительный пример для пояснения:

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

Отпечатки:

Number: 1234