Java泛型边界允许使用原始类型吗?其确切含义是什么?
嘿,这几个泛型问题问到点子上了,都是日常写代码容易踩坑的点,我来给你逐个掰明白:
问题1:Java泛型边界中是否允许使用原始类型?其确切含义是什么?
答案是允许,但这是个“不推荐但合法”的写法。比如你写class Bar<T extends Foo>,这里的Foo就是原始类型——也就是把Foo<T>的泛型信息完全擦除后的类型。
它的确切含义是:T可以是任何继承(或实现)了原始类型Foo的类型。换句话说,不管Foo原本的泛型参数是什么,只要是Foo的子类(包括Foo<String>、Foo<Integer>,甚至原始类型Foo本身),都能作为T的候选类型。
但这里有个大问题:用原始类型会绕过泛型的类型检查,编译器没法帮你验证泛型参数的一致性,很容易在运行时抛出ClassCastException。比如你如果在Bar里调用Foo的泛型方法,编译器不会检查参数类型是否匹配,全靠你自己保证,风险很高。
问题2:class Bar<T extends Foo>{} 能否视为 class Bar<U, T extends Foo<U>>{} 的等价形式?
完全不能,这俩在语义和类型安全上有本质区别:
- 先看
Bar<T extends Foo>:这里的Foo是原始类型,T只需要是Foo的子类就行,但Bar类完全拿不到Foo的泛型参数信息。比如你用Bar<Foo<String>>的时候,Bar里没法知道这个Foo的泛型参数是String,所有泛型相关的操作都会被擦除,和直接用原始类型没啥区别。 - 再看
Bar<U, T extends Foo<U>>:这个写法明确绑定了T必须是Foo<U>(或其子类),而且Bar类可以直接使用U这个类型参数,保留了完整的泛型类型信息。比如你实例化Bar<String, Foo<String>>,Bar里就能明确知道Foo的泛型参数是String,编译器会帮你做所有类型检查,是完全类型安全的。
后者虽然看起来繁琐,但它是正确的泛型写法;前者图省事用了原始类型,本质是放弃了泛型的类型安全保障,绝对不能视为等价。
问题3:class Bar<T extends Foo<?>>{} 是否为第一个Bar定义的类型安全版本?
可以这么认为,但它是有局限性的类型安全:
首先,Foo<?>是通配符类型,和原始类型Foo的核心区别是:它仍然是一个泛型类型,编译器会对它进行基本的类型检查。比如如果Foo是个容器类,你不能往Foo<?>里添加除了null之外的元素,编译器会直接报错,避免了原始类型那种完全无约束的情况。
Bar<T extends Foo<?>>要求T必须是某个具体的Foo<X>(X可以是任意类型),但Bar类同样没法获取X的具体类型信息——不过它至少保证了T是一个合法的泛型Foo实例,而不是原始类型Foo,相比第一个Bar的写法,确实避免了原始类型带来的无检查风险,是更安全的版本。
但如果你需要在Bar里使用Foo的泛型参数X的具体类型(比如要调用Foo的方法并传入X类型的参数),那这个写法还是不够,得用第二个带U参数的Bar定义才行。
内容的提问来源于stack exchange,提问作者Edoardo Vacchi

