泛型方法
泛型方法定义
- 泛型类是说明这个类有类型变量,在创建这个类对象时需要给类型变量赋值。泛型方法是说明这个方法有类型变量,在调用这个方法时需要给类型变量赋值。
//返回值为T类型的集合
public <T> T get(T[] ts, int index) {
return ts[index];
}
- get()方法是一个泛型方法,它有一个类型变量T,这说明在调用get()方法时需要给get()方法的T赋值。
如果要定义一个有意义的泛型方法,那么:
- 参数需要使用类型变量;
- 返回值需要使用类型变量。
所以,通常在调用泛型方法时,只需要传递参数就可以了,例如:
String[] strs = …
String s = o.get(strs, 0);
上面代码中给get()方法的类型变量T赋值为String,因为传递的参数为String数组,所以就是给T赋值为String。当然,也可以显示给出类型变量的值:o.<String>get(strs,0)
,在点后面,方法名前面给出类型值,但一般人不会这么打代码。
泛型的边界
- 编译期状态:编译期状态,例如内部类!内部类就是只有编译器知道,而JVM不知道什么叫内部类!
泛型的擦除
- 泛型其实是编译期状态,即JVM不知道什么是泛型,在JVM那里所有类都是正常的类。没有类型变量。一切的一切都是编译器干的好事儿!
- 其实List的get()方法返回值还是Object,只是编译器帮我们添加了强转的语句。但这个强转一定不会出现问题。因为本来add()方法添加元素已经限制过了,那么在get()时,一定不会出现强转的问题。也就是说,在ArrayList类中持有的还是Object类型,而不是我们指定的类型。当然,就算是JVM没有泛型,但编译器会帮我们完成这些问题,我们就可以当泛型真的存在。
泛型边界限定的类型值的范围
- 通常我们看到的泛型类都没有边界限定,也就是说可以给泛型类的类型变量赋任意类型的值(当然基本类型是不可以的)。
- java允许给类型变量指定边界,这样用户在给类型变量赋值时就必须在边界之内。
public class A<T extends Number> {}
表示用户可以给T赋值为Number或Number的子类型。例如:new A<Integer>()
这是可以的,但new A<String>()
是不可以的。
通配符
通配符的作用
Object[] objs = new String[10];
objs[0] = new Integer(100);
- 上面代码编译是可以通过的,但在运行时会出现ArrayStoreException。因为objs数组真实的身份是String[],向String[]数组中存放Integer对象当然是不行的。
ArrayList<Object> list = new ArrayList<String>();
list.add(new Integer(100);
- 上面代码在第一行位置编译失败,因为泛型根本就不让把
ArrayList<String>
赋值给ArrayList<Object>
,对于ArrayList<Object>
而言,只能赋值ArrayList<Object>
,其他的什么都不能赋值。
也说明一个问题:
public static void printList(List<Object> list) {…}
- 调用printList()方法只能传递给它List类型的参数,而不能传递List,或者List,这说明我们的printList()方法有很多的限制,不够通用!!!你可能会想我再重载几次printList()方法吧,但这是行不通的!
public static void printList(List<Object> list) {…}
public static void printList(List<String> list) {…}
- 因为JVM不知道什么是泛型,这两个方法在到了JVM那里时都是会把泛型参数擦除,这两个方法就是相同的方法了,擦除之后即:
public static void printList(List list) {…}
public static void printList(List list) {…}
- 当然JVM不可能看到这样的代码,因为编译器不能让你编译通过!处理这个问题需要使用通配符!
子类通配符
public static void printList(List<? extends Person> list) {…}
- 这回可以传递给printList()方法
List<Student>
,以及List<Teacher>
参数了。只要类型参数为Person,或者是Person子类型就都可以。 - 你可以这样来理解通配符,通配符表示“不知道”的意思。即一个问号!但子类型通配符还是知道一些信息的,它只知道用户转递的类型参数一定是Person的子类型。虽然使用了通配符之后printList()方法更加通用了,但是这也是要付出一些代价的。因为不知道List中类型参数的真实类型,所以就不能调用list的add()方法了。你可能会想add(new Student())应该是可以的,但如果List是
List<Teacher>
呢,你怎么向这样的List添加Student呢?再想一想,add()方法已经作废了,什么都传递不了。
父类型通配符
public static void printList(List<? super Student> list) {…}
- 可以传递给printList()方法
List<Student>
,以及List<Person>
,甚至List<Object>
也是可以的。只要传递给printList()方法的List类型参数为Student,或者Student的父类型就是可以的。 - 你现在可以向list中添加Student类型的参数,例如:list.add(new Student())。因为用户提供的参数
List<Student>
、List<Person>
、List<Object>
,无论哪一种类型,对于list.add(new Student())都是合法的。但是,现在我们不知道list的get()方法返回的是什么了!因为用户传递的可能是List<Student>
、List<Person>
、List<Object>
,如果我们用Student s = list.get(0),那么如果list其实是一个List<Person>
岂不是出错了!没错,只能使用Object来接收list.get(0)的返回值了。
无界通配符
- 所谓无界通配符,即List<?>,对通配符没有限定。你可以给List<?>赋任意的值,但是,你能使用这样的list干什么呢?也不能add(),也只能使用Object来接收get()方法返回值。
- 所以,通常List<?>只能表示你在使用泛型而已!编译器会认为List<?>比List更加优雅一些!你可能也看出来了,泛型还真是很垃圾!!!这也是没有办法中的办法,现在的泛型是迁移性兼容的一种版本而已!Java设计者不敢让JVM知道泛型的存在,原因是为了兼容1.4之前的版本。当所有人都在使用1.5以上版本的JDK后,Java的泛型可能就不会再是编译期状态了。