泛型¶
泛型
泛型就是把类型Type作为参数使用,就是类型参数。可以理解为参数化的类型Type(Class,Interface等等)
1.为什么要用到泛型?¶
泛型是为了解决什么问题出现的?
为了了解这个,下面我们举个箱子(Box)的例子:
1 2 3 4 5 6 7 8 9 10 11 | public class Box{ private Object obj; public Box(){ } public void add(Object obj){ this.obj = obj; } public Object get(){ return obj; } } |
这是单纯表示箱子的一个类。
Add方法接受到什么对象,就在内容保存什么对象。当get方法调用时,再把那个对象返回。
我们在想象一下调用的情况:
1 2 3 4 5 6 | public static void main(String[] args){ Box b = new Box(); b.add(123); Integer i = (Integer) b.get(); System.out.println(i); } |
往Box的Add方法里传一个数字123,再通过get方法取出来。
强制转换为Integer,作为Integer的变量输出。
1 2 3 4 5 6 | public static void main(String[] args){ Box b = new Box(); b.add("Hello"); String s = (String) b.get(); System.out.println(s); } |
直到现在基本上没有什么问题。
但我们再看看下面的例子。
1 2 3 4 5 6 | public static void main(String[] args){ Box b = new Box(); b.add(123); String s = (String) b.get(); System.out.println(s); } |
同样编译成功,但执行结果呢?
抛出了下面的异常:

理由应该是显而易见的。即使有不对的地方,编译还是通过了但执行时却失败了。
这样想写出稳定的代码是非常难的
还有各种各样的类型・・・
结局就是,Add方法设了什么值进去,get方法就要取什么类型的值
那为了安全起见,如果Integer用的箱子,我们写一个叫IntegerBox类
String用的箱子,我们写一个叫StringBox类
Long用的箱子,我们写一个叫LongBox类
Boolean用的箱子,我们写一个叫BooleanBox类等等等等・・・
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | public class IntegerBox{ private int obj; public Box(){ } public void add(int obj){ this.obj = obj; } public int get(){ return obj; } } public class StringBox{ private String obj; public Box(){ } public void add(String obj){ this.obj = obj; } public String get(){ return obj; } } public class LongBox{ private long obj; public Box(){ } public void add(long obj){ this.obj = obj; } public long get(){ return obj; } } |
按照上面的例子,是可以解决可以编译但不能执行的不稳定代码的问题,
但每种类型都要去写一个这个箱子,是不是太麻烦?也不太符合现实中的情况?
2.泛型登场¶

把Object类型,用T置换,再在类名后加入<T>,就变成了class Box<T>。
可以理解为这个Box类是针对类型T的类定义的类。
可以代替[IntegerBox、 StringBox・・・]的多会重复定义。
使用泛型定义Box,要传入类型时,可以是:Box

往Int的Box里传入String的这种错误,在编译时就可以直接检查出来。
同样取值的时候,也没有必要做强制转换了。
泛型是类型安全的。

3.命名规则¶
在此介绍一下,定义泛型类型是惯用的标记法。
定义泛型类时,为了和通常的变量明确区分出来,一般使用一个大写字母。
使用像下面这样的大写字母
| 文字 | 意义 |
|---|---|
| E | 元素(Java collection框架内广泛使用) |
| K | key |
| N | 数值 |
| T | 类型Type |
| V | 值 |
| S,U,V | 第二,第三个的类型 |
虽然无论用什么字母,都是可以编译的,
但请按照惯例遵守命名规则,这样可以写出容易理解的代码
4.子类型¶
再来了解一下子类型的问题。
所谓子类型,换言之就是子类或者派生类了。
请注意:泛型里面,和通常的对象的动作是不一样的
从Person类派生了一个SalesPerson类。
这是,SalesPerson对象(实例)可以很安全的转换为Person类,因为SalesPerson类是Person类派生出来的,
SalesPerson也是Person。
像这种向父类的类型转换时安全的。叫做向上转换。
那在泛型里面,会是什么情况呢?
1 2 | Box<String> sbox = new Box<String>(); Box<Object> obox = sbox; |
上面的例子中,把Box
因为String也是Object,那这种也是正确的吗?
但是实际上,这个代码是编译出错的。
如果通过Box
所有结论就是,虽然String是 Object 的子类,但是Box
5.泛型方法和构造函数¶
下例告诉我们怎么定义泛型方法。
1 2 3 4 5 6 7 8 9 10 | public class Box<T> { T o; public Box() { } public static <T> T getrMiddle(T[] a) { return a[a.length / 2]; } } |
getMiddle方法接受T类型的数组,并返回数据中间的元素。当然这个元素也是T类型的。
可以像下面这样调用泛型方法。
1 2 3 4 5 6 7 | public static void main(String[] args) { String[] cars = { "Toyota", "Nissan", "Honda" }; System.out.println(Box.<String> getMiddle(cars)); } //执行结果如下: Nissan |
此时,编译器可以根据传入参数判断出类型是什么,所以方法前的
下面代码和上面是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public static void main(String[] args) { String[] cars = { "Toyota", "Nissan", "Honda" }; System.out.println(Box.<String> getMiddle(cars)); System.out.println(Box.getMiddle(cars)); Integer[] carIds = { 1, 2, 3 }; System.out.println(Box.getMiddle(carIds)); } //执行结果如下: Nissan Nissan 2 |
6.边界类型(Bounded Types)¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class Person { String name; public Person(String name) { this.name = name; } public String getName() { return name; } } public class SalesPerson extends Person { int empID; public SalesPerson(int empID, String name) { super(name); this.empID = empID; } public int getEmpID() { return empID; } } |
再尝试执行下面的方法:
1 2 3 4 5 | public static <T extends Person> void showName(T[] a) { for(Person p : a) { System.out.println(p.getName()); } } |
接受T类型的数组,再遍历这个数组元素调用getName方法。
为了正确执行这个代码,传入的参数必须是Person类,或是它的子类。
因为Person定义了getName方法,所有的子类就都有了。
如果是String对象,就没有getName方法,会导致处理失败。
像这样想给作为泛型的类型加入限制的时候,我们就可以指定了。
下面方法的定义的红框。

<T extends Person>的表述中,T表示是Person派生的类。
像这样可以限制的类型,就叫边界类型(Bounded Types)。
7.通配符¶
1 2 3 4 5 6 7 8 9 10 | public static void main(String[] args) { SalesPerson p = new SalesPerson(123, "Keisuke"); Box<SalesPerson> b = new Box<SalesPerson>(); b.add(p); foo(b); } public static void foo(Box<SalesPerson> bp) { // do sth. } |
这个例子中foo方法只接受Box<SalesPerson>类型的参数。
如果要接受Person所有的派生类的话,要如下:
1 2 3 | public static void foo(Box<? extends Person> bp) { // do sth. } |
如果你想要可以传入这个类的任何子类,就要使用[?]作为通配符
8.Java类库中的泛型类¶
标准的Collection的接口,已经都泛型化了。
Collection<V>、List<V>、Set<V> 和 Map<K,V>
!!! Question
1. List<Object> 和 List<String> 之间,有何关系?
2. List<String>和ArrayList<String>之间,有何关系?
3. 下面的两个,正确吗?
1 2 3 4 5 | Integer[] intArray = new Integer[10]; Number[] numberArray = intArray; List<Integer> intList = new ArrayList<Integer>(); List<Number> numberList = intList; |
1 2 | public static void foo(Box<T extends Person> bp) { public static void foo(Box<? extends Person> bp) { |
総括¶
- 泛型,就是参数化的类,接口,方法等。
- 参数,只能是引用型,不能是基本型。
- 编译时可以安全检查,可以自动转换
Cast,提高代码重复利用率 - 参数不一样的泛型类,是没有关系的。
- 参数可以有多个。
- 参数的继承可以使用
<T extends superclass>:边界类型(Bounded Types) - 参数也支持通配符。如:
Class<?> classType = Class.forName("java.lang.String");