请考虑以下事项:
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。 为什么不打印字符串:abc?
答案 0 :(得分:10)
这是因为Java type erasure:你的
static <T> void printWithClass(T t) {
print(t);
}
实际上是
之上的语法糖static void printWithClass(Object t) {
print(t);
}
公平地说,“语法糖”让编译器做了一些非常好的和重要的检查,但在运行时只有printWithClass
方法的一个副本,它使用java.lang.Object
作为变量t
的类型。
如果您使用其他语言(C#,C ++模板,Ada)中的泛型,那么类型擦除将与您所知道的相反,但这就是它在封面下的工作原理。
答案 1 :(得分: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);
}
}
}
}
答案 2 :(得分:4)
这不是类型擦除,它是一个编译问题,如果JVM存储方法在运行时泛型,也会发生同样的事情。它也不是类型推断 - 编译器推断<String>
正如您所期望的那样。
问题在于,当编译器为printWithClass
生成代码时,它需要特定的方法签名才能与print
调用关联。 Java没有多个调度,因此它不能在方法表中添加模糊签名并决定在运行时调用什么。 T
的唯一上限是Object
,因此匹配的唯一方法是print(Object)
。
答案 3 :(得分:0)
因为java泛型不是你认为它们的泛型。当编译通用java代码时,实际上所有类型信息都被剥离,并且只保留基本已知类型。在这种情况下,该类型为Object
。
java中的泛型实际上只是编译器技巧,编译器会删除原本需要的强制转换并引发编译时间限制。最后,剩下的就是基本类型,当它实际编译成字节代码时。
此过程称为类型擦除。这个previous question有助于了解实际情况。
答案 4 :(得分:0)
因为它只能在运行时知道,但实际上,因为java是一种编译语言而不是脚本语言,所以它是在编译时决定的。
java泛型允许“在提供编译时类型安全性的同时对各种类型的对象进行操作的类型或方法。”
您当然可以尝试以下方式:
static <T extends String> void printWithClass(T t) {
print(t);
}
虽然这不是你所追求的,但由于编译器正在调用镜头,这是不可能的。
答案 5 :(得分:0)
static <T> void printWithClass(T t) {
print(t);
}
将被加入
static void printWithClass(Object t) {
print(t);
}
答案 6 :(得分:0)
泛型由编译器解释,它们强制执行其他类型检查以避免任何运行时转换问题。通用类型信息为lost at runtime。因此,在运行时,printWithClass接收的只是对象而不是String,因此也就是结果。
答案 7 :(得分: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