个人博客:https://blog.N0tExpectErr0r.cn
小专栏:https://xiaozhuanlan.com/N0tExpectErr0r
Java的泛型是JDK5带来的新特性,它有如下的优点:
但是,为了做到向下兼容,Java中的泛型仅仅是一个语法糖,并不是C++那样的真泛型。
如何证明呢?我们可以看看下面的例子
在这个例子中,我们定义了一个List
集合,我们可以调用add方法向其中加入Integer
类型的值。
如果我们像下面一样调用add方法向里面加入String
,当然会报错:
public static void main(String[] args) throws Exception {
List<Integer> list = new ArrayList<>();
list.add("str");
}
错误信息如下:
add (java.lang.Integer) in List cannot be applied to (java.lang.String)
显然,我们是没法直接向其中加入String类型的值的。
不过,我们可以另辟蹊径。我们尝试通过反射,间接地调用add方法,向这个List
中加入String
类型的值。
public static void main(String[] args) throws Exception {
List<Integer> list = new ArrayList<>();
list.add(1);
Method add = list.getClass().getMethod("add", Object.class);
add.invoke(list,"str");
System.out.println(list);
System.out.println(list.get(1));
}
运行发现,并没有报错,而且我们成功地打印了这个str!
[1, str]
str
由此可见,所谓的泛型确实是假泛型。原本只能装入Integer的List中,我们成功装入了一个String类型的值。
实际上,Java 的泛型仅仅在编译期有效,在运行期则会被擦除,也就是说所有的泛型参数类型在编译后都会被清除掉。这就是所谓的类型擦除。
我们看到下面的例子:
public class Test {
public void addList(List<String> stringList){
}
public void addList(List<Integer> intList) {
}
}
我们在这个类中定义了两个方法,传入List
和List
这两个不同类型的参数,看起来并没有什么问题。
可是,编译器却报出如下的错误:
Method addList(List) has the same erasure addList(List) as another method in type Test
也就是说,这两个方法的签名在进行了类型擦除后均为addList(List
下面是关于List的类型擦除的一些情况:
List
、List
擦除后的类型为List
。List[]
、List[]
擦除后的类型为 List[]
。List extends E>
、List super E>
擦除后的类型为List
。List
擦除后类型为 List
。C++的模板就是我们所谓的"真泛型",下面是一段C++使用模板的类的示例:
#include
using namespace std;
template<class T> class Test
{
private:
T obj;
public:
Test(T x){obj=x;}
void try(){obj.fun();}
};
class A
{
public:
void fun(){cout<<"A::fun()"<<endl;}
};
int main(int argc, char* argv[]){
A a;
Test<A> test(a);
test.try();
}
在类Test中存储了类型为T的对象。很有趣的一点是我们在Test::try()
方法中调用了obj
的fun()
方法。它怎么知道fun()
方法是T所包含的呢?
在我们调用Test的构造函数实例化这个模板时,编译器进行了检查,发现A类确实含有fun方法,因此编译可以通过。
如果A类并不含有fun方法,则编译不会通过。这样类型安全就得到了保障。
在 C++ 模板中,编译器使用提供的类型参数来扩充模板,因此为List
生成的 C++ 代码不同于为 List
生成的代码,List
和 List
实际上是两个不同的类。
虽然java中的泛型是“假”的,会有类型擦除的操作。但是不可否认,泛型的引入对Java语言影响仍然是非常大的。
因此在我们使用泛型的时候,应当尽量考虑到类型擦除这个特点,多考虑一下自己的封装是否类型安全。