Java 泛型 - 类型推断

10

请考虑以下内容:

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

它打印出了对象:abc。 为什么不打印字符串:abc?

获取泛型类型的问题,请参见此问题 - Get Generic Type of Class at Runtime - csturtz
8个回答

10
这是因为 Java 类型擦除:你的
static <T> void printWithClass(T t) {
    print(t);
}

实际上是在...的基础上进行的语法糖

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

要公正的说,这种“语法糖”让编译器进行了一些非常好的和重要的检查,但在运行时,只有一个printWithClass方法的副本,并且它使用java.lang.Object作为变量t的类型。如果您已经在其他语言中(如C#、C++模板、Ada)体验过泛型,那么类型擦除就与您所知道的相反,但这就是它在内部工作的方式。

这不是关于类型擦除,而是关于编译时可用的通用边界。如果我们使用 <T extends String>,输出将为 String: abc - Daniel Lubarov
1
@Daniel 绝对没错 - 因为该方法将变成 static void printWithClass(String t)。但它仍然是一个单一的方法,具有单一的类型,在编译时绑定到调用静态 print 方法的单一重载。 - Sergey Kalinichenko

4

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

}

3

这不是关于类型擦除的问题,而是编译问题,如果JVM在运行时存储方法泛型,同样会发生这种情况。也不是关于类型推断 - 编译器像你预期的那样推断出<String>

问题在于,当编译器为printWithClass生成代码时,它需要一个特定的方法签名与print调用相关联。Java没有多重分派,所以它不能在方法表中放置一个模糊的签名并在运行时决定要调用什么。 T的唯一上限是Object,因此唯一匹配的方法是print(Object)


0

因为只有在运行时才能知道,但实际上,由于Java是一种编译语言而不是脚本语言,它是在编译时决定的。

Java泛型允许“类型或方法在提供编译时类型安全的同时操作各种类型的对象。”

当然,您可以尝试类似以下的内容:

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

虽然这不是你想要的,但由于编译器掌控了局面,这是不可能的。


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

将被编译为

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

0

额外的例子以澄清:

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

0

因为Java泛型并不是你想象中的那种泛型。在泛型Java代码编译时,所有类型信息都会被剥离,只留下基本已知类型。在这种情况下,该类型是 Object

Java中的泛型实际上只是编译器的技巧,在这种技巧中编译器消除了本应该必要的强制转换,并引入了编译时约束。最终,当此代码被编译成字节码时,留下的只是基本类型。

这个过程称为类型擦除。这篇之前的问题有助于理解实际发生了什么。


0

泛型由编译器解释,并强制执行额外的类型检查,以避免任何运行时转换问题。泛型类型信息在运行时丢失。因此,在运行时,printWithClass接收到的只是对象而不是字符串,这就是你的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接