简体   繁体   English

Java泛型 - 类型推导

[英]Java generics - type deducing

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. 它打印Object:abc。 Why doesn't it print String: abc? 为什么不打印字符串:abc?

This is because of Java type erasure : your 这是因为Java类型擦除 :你的

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 . 公平地说,“语法糖”让编译器做了一些非常好的和重要的检查,但是在运行时只有printWithClass方法的一个副本,它使用java.lang.Object作为变量的类型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. 如果您使用其他语言(C#,C ++模板,Ada)中的泛型,那么类型擦除将与您所知道的相反,但这就是它在封面下的工作方式。

Java supports method overriding (dynamic type binding), but not what you are trying to achieve (overloading is static polymorphism and not dynamic). Java支持方法覆盖(动态类型绑定),但不支持你想要实现的东西(重载是静态多态而不是动态)。

In order to achieve what you want to achieve in Java, you need double dispatch. 为了实现您希望在Java中实现的目标,您需要双重调度。

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

}

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. 它不是类型擦除,它是一个编译问题,如果JVM存储方法在运行时泛型,也会发生同样的事情。 It's also not about type inference -- the compiler infers <String> as you would expect. 它也与类型推断无关 - 编译器按预期推断<String>

The issue is that when the compiler is generating code for printWithClass , it needs a specific method signature to associate with the print call. 问题是当编译器为printWithClass生成代码时,它需要一个特定的方法签名来与print调用相关联。 Java has no multiple dispatch, so it can't put a vague signature in the method table and decide what to invoke at runtime. Java没有多个调度,因此它不能在方法表中添加模糊签名并决定在运行时调用什么。 The only upper bound on T is Object , so the only method that matches is print(Object) . T上唯一的上限是Object ,因此唯一匹配的方法是print(Object)

Because java generics aren't generics the way you think they are. 因为java泛型不是你认为它们的泛型。 When generic java code gets compiled all type information actually gets stripped away, and only the base known type remains. 当编译通用java代码时,实际上所有类型信息都被剥离,并且只保留基本已知类型。 In this case that type is Object . 在这种情况下,该类型是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. java中的泛型实际上只是编译器技巧,编译器会删除原本需要的强制转换并引发编译时间限制。 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. 之前的问题有助于了解实际情况。

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. 因为它只能在运行时知道,但实际上,因为java是一种编译语言而不是脚本语言,所以它是在编译时决定的。

The java generics allow "a type or method to operate on objects of various types while providing compile-time type safety." java泛型允许“在提供编译时类型安全性的同时对各种类型的对象进行操作的类型或方法”。

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. 虽然它不是你所追求的,但这是不可能的,因为编译器正在调用。

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

will be comipled to 将被加入到

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

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. 所以在运行时printWithClass接收的只是对象而不是String,因此也就是你的结果。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM