详细介绍了Java中的泛型的概念、原理和使用,比如上、下限泛型、泛型通配符等。
1 泛型
诞生的必要性:Java1.4或更早版本,原来List集合当中可以存放任何类型,当对集合当中的元素进行统一操作的时候,容易引发ClassCastException异常。当存在各种数据类型的时候,不利于对集合的元素进行统一操作。
解决思路:像数组一样,只能存放某种数据类型。
此时在JDK1.5 添加了一个新技术:泛型。
1.1 定义
泛型,即“参数化类型”。顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型变量),然后在使用/调用时传入具体的类型(类型实参)。这种类型变量可以用在类、接口和方法的创建中。当然最常见的就是用在集合中。
设定的语法:类名<类型变量> ,也叫参数化类型。
类型变量:使用大写的形式,只能是引用类型,不能使基本数据类型。Java中,用变量E表示集合的元素类型,K和V分别表示表的key与value类型,T表示“任意类型”。
用具体的类型替换类型变量就可以实例化泛型类型。
1.2 作用
- 将运行时期的异常提前到了编译时异常(classcastexception),方便了元素的统一操,优化了程序结构。
- 泛型参数一旦确定,就规定了在类使用的时候,只能存放某一种数据类型。此时集合还能够记住集内元素的类型,从而无需对集合元素进行强制转换,使得程序更加简洁。
1.3 泛型类
把泛型定义在类上。格式:public class 类名<泛型类型1,…>
注意:泛型类型必须是引用类型。
案例
在类上声明了一个不确定的类型(类型变量);在该类当中都可以使用该不确定的类型。
public class Genericity<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
使用泛型类
Genericity<String> tool = new Genericity<String>();
//使用String对T进行初始化。
tool.setObj("ggg");
1.4 泛型方法
泛型可以定义在方法上:泛型方法。泛型声明在方法的返回值之前,并且可以与类名上的泛型声明不一致!
class ss<Q> {
/**
* 泛型方法,使用自己的泛型T
*
* @param t T
* @param <T> 新类型<T>的声明
*/
public <T> void set(T t) {
}
/**
* 泛型方法,使用类上的泛型Q
*
* @param q Q
* @return Q 返回值的类型
*/
public Q getXxx(Q q) {
return q;
}
}
注意:
当在类名之后声明了泛型时。如果想在该类静态方法使用和类一样的泛型变量,即使在类上已经声明了泛型,还是要像自定义泛型方法一样,类下面的静态方法的返回之前一定还要重新声明一次。但是非静态方法就能直接使用同名泛型变量,不用再次声明!案例如下:
public class GenericDemo01<T> {
//普通方法使用与类同名泛型,不需要声明
//传递T类型参数,直接使用
public void get1(T t)
{
}
//返回T类型,可以直接使用
public T get2()
{
return null;
}
//静态方法使用同名泛型,和自定义泛型方法一样,必须声明
//想要返回T类型或传递T类型的参数,则必须在返回之前重新声明
public static <T> void get3(T t)
{
}
//想要返回T类型或传递T类型的参数,则必须在返回之前重新声明
public static <T> T get4()
{
return null;
}
}
使用细节:
List list=New ArrayList();
List list=New ArrayList();
List list=New ArrayList(); //泛型推断.JDK1.7
以上三种写法都可以。但是前后声明的泛型类型必须要相同.泛型只能存放引用类型,在JDK1.5之后建议使用泛型,否则会报警告。
1.5 泛型接口
1.5.1 泛型接口的定义
需求: 对学生进行增删改查操作:
interface StudentDao{
//增加:
void add(Student stu);
//删除:
boolean delete(String id);
//修改:
boolean update(Student stu);
//查询: 唯一性查询:
Student findById(String id);
}
设定一个具体的实现类: 对接口当中的方法进行具体的实现,那么针对一个对象就需要设定一套接口,并且设定一套实现类:
Teacher : 设定一套接口, 设定一套实现类: Person : 设定一套接口, 设定一套实现类:
这样就比较麻烦了,因此可以使用泛型接口。
1.5.1 泛型在接口上的使用
使用泛型接口可以减少Dao接口的设计数量。
interface Dao<T>{
void add(T t);//增加
boolean delete(String id); //删除
boolean update(T t);
T findById(String id);
}
1.6 泛型通配符以及上、下限泛型
Collection<?> ?:可以代表任意类型
Collection<?> c5 = new ArrayList<Object>();
Collection<?> c6 = new ArrayList<Animal>();
Collection<?> c7 = new ArrayList<Dog>();
Collection<?> c8 = new ArrayList<Cat>();
1.6.1 上限泛型
Collection<? extends E> ? 只能是E类型 或者是E 类型的子类(否则编译报错):这样的泛型称之受限泛型:上限泛型。
一般在存储元素时使用上限(? extends E)。因为一旦确定好类型,存入的就都是E或E的子类,取出时都按照上限类型E来运算,不会出现类型安全隐患
Collection<? extends Animal> c9 = new ArrayList<Object>();
Collection<? extends Animal> c10 = new ArrayList<Animal>();
Collection<? extends Animal> c11 = new ArrayList<Dog>();
Collection<? extends Animal> c12 = new ArrayList<Cat>();
1.6.1 下限泛型
Comparator<? super E> ? 只能是E类型或者是E类型的父类型(否则编译报错):受限泛型的下限泛型。(一般只有比较器用到)
//min比较器
min(Collection<? extends T> coll, Comparator<? super T> comp)
//sort排序比较器
sort(List<T> list, Comparator<? super T> c)
Collection<? super Animal> c13 = new ArrayList<Object>();
Collection<? super Animal> c14 = new ArrayList<Animal>();
Collection<? super Animal> c15 = new ArrayList<Dog>();
Collection<? super Animal> c16 = new ArrayList<Cat>();
1.7泛型擦除
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List和List等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。
类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
//将返回true
System.out.println(list1.getClass() == list2.getClass());