java泛型的特殊之处

微信扫一扫,分享到朋友圈

java泛型的特殊之处

本文通过3个小例子,探索一下Java泛型和C++泛型的区别,说明Java泛型的一些特殊之处。

我们不探究语言背后的晦涩原理,而是从实际使用角度去理解它们。

代码地址: https://github.com/owenliang/java-generic-demo2

例子1

public class Main {
    private static <T> void func1(T val) {
        // 错误:java泛型和c++很不一样,运行时java完全不知道T是啥,所以这个代码根本不让编译
        // T copy = new T();
    }

上述new T()的操作将无法编译通过,因为从java泛型实现原理来说,运行时T是什么类型已经不明确了,所以构造一个T类型对象就无从谈起。

而在C++模板则会在编译时直接生成模板类代码,其类型信息是明确的,不存在JVM这样运行时不知道T类型的情况:

template<class T>
void func1(T v) {
        T *a = new T();
}
 
int main() {
        func1(123);
        return 0;
}

上述代码完全正确,C++编译器为int类型的T生成了一份func1函数,因此如何new T()也是明确的,比java泛型要强大的多。

例子2

    private static <T> List<T> func2(T ...vals) {
        List<T> ret = new ArrayList<T>();
        // 正确:只是把T类型的对象挪来挪去,毫无问题
        for (T v : vals) {
            ret.add(v);
        }
        return ret;
    }

调用一下:

        List<Integer> l = Main.func2(7,8,9);

虽然T类型在JVM运行时丢失了类型信息,但是如果我们只是把T类型的对象挪来挪去,不访问对象内部的方法属性,那么编译运行是完全没问题的。

例子3

    private static <T extends List<? extends Number>> void func3(T l) {
        // 正确:T是List<? extends Number>的子类,所以一定有List的方法
        System.out.println("长度=" + l.size());
 
        // 正确:列表元素一定是Number的子类,所以一定有Number的方法
        Number elem1 = l.get(0);
        System.out.println("第一个元素=" + elem1.doubleValue());
    }

调用一下(把例子2的List<Integer> l传入):

        List<Integer> l = Main.func2(7,8,9);
        Main.func3(l);

函数func3对T类型进行了约束,要求它继承自List<>,并且List中的元素要继承自Number,说白了就是T必须是一个存放数字的列表。

相比于一个没有任何约束的T类型,这些约束其实给了JAVA编译器很多类型信息,因此我们可以直接调用size()方法,因为T一定继承自List;同时,我们从List<? extends Number>取出来的元素也一定继承自Number,所以可以调用Number类的doubleValue方法获取浮点数表示。

总结,java泛型因为其实现原理问题有一些奇奇怪怪的表现,相比于C++变态的模板编程来说其实算很简单的东西了。

我不建议为了用泛型而去java泛型背后的实现原理,从实用角度出发多尝试遇到问题再解决就好,这样生活会比较愉快。

如果文章帮助了你,请帮我点击1次谷歌广告,或者微信赞助1元钱,感谢!

知识星球有更多干货内容,对我认可欢迎加入:

微信放大招!这波改版又有人要「赚翻」了?

上一篇

苹果供应商国巨10月营收达16.6亿元 同比增长123.4%

下一篇

你也可能喜欢

java泛型的特殊之处

长按储存图像,分享给朋友