本篇博文主要介绍两部分,为什么要有泛型以及泛型擦除这个概念,如果你想要了解泛型的具体使用,请查看相关书籍或者其他博客。
为什么要有泛型
来看看官方文档给的解释:
Code that uses generics has many benefits over non-generic code:
Stronger type checks at compile time.
A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.Elimination of casts.
The following code snippet without generics requires casting:
List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0);
When re-written to use generics, the code does not require casting:
List<String> list = new ArrayList<String>(); list.add("hello"); String s = list.get(0); // no cast
Enabling programmers to implement generic algorithms.
By using generics, programmers can implement generic algorithms that work on collections of different types, can be customized, and are type safe and easier to read.
文档中主要列举了三个使用泛型的理由。
-
首先,如果代码中有使用泛型的话,编译器就会进行类型检查,当代码中违反了类型安全的规范,那么编译就不会通过。在编译期间修改错误相对来说比在运行期间修改错误容易得多。
-
其次,泛型可以消除类型转换的错误。最常见的一个例子是在集合类的使用中,在没有使用泛型之前,集合类中可以添加任何的类型,当我们需要获取容器中的元素时,就要进行类型转换:
List list = new ArrayList(); list.add("hello"); String s = (String)list.get(0);
一般情况下,这种做法是没问题的。但是,当我们添加的元素既有 String 又有其他类型时,上面的代码在运行期间就很容易报错。使用泛型之后,我们就只能向容器中添加单一的元素,这样就可以很好地解决该问题,除此之外,获取元素时也可以不用进行类型的转换了,十分方便。
-
最后,我们可以使用泛型方法来提高方法的重用性。当我们有两个方法,一个方法返回 String,另一个方法返回 int,如果不使用泛型的话,我们需要写两个方法:
public String getString(String message){ return message; } public int getInt(int message){ return message; }
我们可以看到,上面两个方法体其实是一样的,分成两个方法写难免有点冗余,这时泛型方法就可以派上用场了。使用泛型方法,不仅使代码的冗余度降低了,对于返回类型我们也不用进行类型转换,一举两得,何乐而不为呢?
public <T> T getMessage(T message) { return message; }
泛型擦除
了解了为什么要使用泛型后,我们来看看泛型中另一个重要的概念 — 泛型擦除。首先我们来看一下这个例子:
public class ErasedTRypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
}
Class 类包含的是类信息,直观的来看,ArrayList 和 ArrayList 两者包含的类信息应该是不一样的,但是运行的结果却是 true。这是因为泛型只在编译期间才起作用,在运行期间,泛型信息都会被擦除成「原生」类型,上面的 ArrayList 和 ArrayList 会被擦除为 ArrayList。举个例子,当我们使用泛型时,在调用 ArrayList.add() 方法时,编译器会提醒我们需要添加的元素为 Integer 类型
但是在运行期间,其 add 方法可以添加的元素仍然是 Object 类型。怎么验证这一点呢?看下面这段代码:
public class EraseTest {
public static void main(String[] args) {
Apple<Integer> a = new Apple<>(6);
Field[] fs = a.getClass().getDeclaredFields();
for(Field f : fs) {
System.out.println(f.getType().getName());
}
}
}
class Apple<T>{
T size;
public Apple(T size) {
this.size = size;
}
}
我们为 Apple 指定的泛型是 Integer,按照常理,size 的类型也应该是 Integer,但是上面的代码却打印出 java.lang.Object
,由此可见,泛型只是存在于编译期间,在运行期间其类型会被擦除。有了泛型擦除这个概念,我们就可以更好的理解使用泛型过程中编译器的警告以及错误提示,对于泛型的一些特性也能够更好的理解。