1、泛型简介
1.1、泛型的概念
- 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返 回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、 创建对象时确定(即传入实际的类型参数,也称为类型实参)。
- 从JDK 5.0以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。
- JDK 5.0改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
1.2、泛型的引入背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。
1.3、为什么需要泛型
早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全
首先,我们来试想一下:没有泛型,集合会怎么样
- Collection、Map集合对元素的类型是没有任何限制的。本来我的Collection集合装载的是全部的Dog对象,但是外边把Cat对象存储到集合中,是没有任何语法错误的。
- 把对象扔进集合中,集合是不知道元素的类型是什么的,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换
有了泛型以后:
- 代码更加简洁【不用强制转换】
- 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】
- 可读性和稳定性【在编写集合的时候,就限定了类型】
2、泛型的使用
2.1、在集合中使用泛型之前的情况
@Test
public void test(){
ArrayList arrayList = new ArrayList();
//需求: 存放学生的成绩
arrayList.add(75);
arrayList.add(89);
arrayList.add(95);
//问题一:类型不安全
arrayList.add("Jack");
for (Object score:arrayList){
//问题二: 强转时,可能出现ClassCastException
int stuScore = (int) score;
System.out.println(stuScore);
}
}
2.2、在集合中使用泛型的情况:以ArrayList
@Test
public void test2(){
ArrayList<Integer> list = new ArrayList<>();
list.add(78);
list.add(12);
list.add(65);
//编译时,就会进行类型检查,保证数据的安全
// list.add("Tom");
for (Integer integer : list) {
//避免了强转操作
int stuScore = integer;
System.out.println(stuScore);
}
}
2.3、在集合中使用泛型的情况:以HashMap
@Test
public void test3(){
HashMap<String, Integer> map = new HashMap<>();
map.put("Tom",54);
map.put("Jack",78);
map.put("Aimi",64);
//只能按照泛型的类型填写
// map.put(12,"asd");
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
2.4、在集合中使用泛型:总结
jdk 5.0新增的特性
- 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
- 在实例化集合类时,可以执行具体的泛型类型
- 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。 比如:add(E e) —>实例化以后:add(Integer e)
- 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
- 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
3、自定义泛型
泛型结构
泛型类、泛型接口、泛型方法
泛型的声明
- interface List 和 class GenTest<K,V>其中,T,K,V,不代表值,而是表示类型。这里使用任意字母都可以。
- 常用T表示,是Type的缩写。
注意点
1.泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如<E1,E2,E3>
2.泛型类的构造器如下: public GenericClass(){}
而下面是错误的: public GenericClass{}
3.实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
4.泛型不同的引用不能相互赋值。
尽管在编译时 ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
5.泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
建议:泛型要使用一路都用。要不用,一路都不要用。
6.如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
7.JDK 7.0,泛型的简化操作: ArrayListfirst= new ArrayList<>();(类型推断)
8.泛型的指定中不能使用基本数据类型,可以使用包装类替换。
9.在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
10.异常类不能是泛型的。
11.不能使用new E[]。但是可以:E[] elements= (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
3.1、泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来…这样的话,用户明确了什么类型,该类就代表着什么类型…用户在使用的时候就不用担心强转的问题,运行时转换异常的问题了。
//自定义泛型类
public class Persion<T> {
T name;
public Persion() {
}
public Persion(T name) {
this.name = name;
}
}
@Test
public void test(){
Persion<String> stringPersion = new Persion<>();
//因为泛型已经定义String类型了,所以泛型的name只能是String类型
stringPersion.name = "张三";
}
3.2、泛型方法
现在呢,我们可能就仅仅在某一个方法上需要使用泛型…外界仅仅是关心该方法,不关心类其他的属性…这样的话,我们在整个类上定义泛型,未免就有些大题小作了。
- 定义泛型方法…泛型是先定义后使用的
//定义泛型方法
public <T> void show(T t){
System.out.println(t);
}
- 测试代码:
用户传递进来的是什么类型,返回值就是什么类型了
@Test
public void test(){
Persion persion = new Persion();
//调用方法,传入的参数是什么类型,返回值就是什么类型
persion.show("张三");
}
3.3、泛型类派生出的子类
泛型类是拥有泛型这个特性的类,它本质上还是一个Java类,那么它就可以被继承
那它是怎么被继承的呢??这里分两种情况
- 子类明确泛型类的类型参数变量
- 子类不明确泛型类的类型参数变量
子类明确泛型类的类型参数变量
- 泛型接口
/*
把泛型定义在接口上
*/
public interface Inter<T> {
public abstract void show(T t);
}
- 实现泛型接口的类…
/**
* 子类明确泛型类的类型参数变量:
*/
public class InterImpl implements Inter<String> {
@Override
public void show(String s) {
System.out.println(s);
}
}
子类不明确泛型类的类型参数变量
- 当子类不明确泛型类的类型参数变量时,外界使用子类的时候,也需要传递类型参数变量进来,在实现类上需要定义出类型参数变量
/**
* 子类不明确泛型类的类型参数变量:
* 实现类也要定义出<T>类型的
*
*/
public class InterImpl<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
测试代码:
public static void main(String[] args) {
//测试第一种情况
//Inter<String> i = new InterImpl();
//i.show("hello");
//第二种情况测试
Inter<String> ii = new InterImpl<>();
ii.show("100");
}
值得注意的是:
-
实现类的要是重写父类的方法,返回值的类型是要和父类一样的!
-
类上声明的泛形只对非静态成员有效
4、泛型在继承方面的体现
泛型在继承方面的体现:
虽然类A是类B的父类,但是G 和G二者不具备子父类关系,二者是并列关系。
补充:类A是类B的父类,A
//代码示例
@Test
public void test1(){
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
//编译不通过
// Date date = new Date();
// str = date;
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
//此时的list1和list2的类型不具子父类关系
//编译不通过
// list1 = list2;
/*
反证法:
假设list1 = list2;
list1.add(123);导致混入非String的数据。出错。
*/
show(list1);
show1(list2);
}
public void show1(List<String> list){
}
public void show(List<Object> list){
}
@Test
public void test2(){
AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;
list1 = list3;
list2 = list3;
List<String> list4 = new ArrayList<>();
}
5、通配符
为什么需要类型通配符????我们来看一个需求…
现在有个需求:方法接收一个集合参数,遍历集合并把集合元素打印出来,怎么办?
- 按照我们没有学习泛型之前,我们可能会这样做:
public void test(List list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
上面的代码是正确的,只不过在编译的时候会出现警告,说没有确定集合元素的类型…这样是不优雅的…
- 那我们学习了泛型了,现在要怎么做呢??有的人可能会这样做:
public void test(List<Object> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
这样做语法是没毛病的,但是这里十分值得注意的是:该test()方法只能遍历装载着Object的集合!!!
强调:泛型中的<Object>
并不是像以前那样有继承关系的,也就是说List<Object>
和List<String>
是毫无关系的!!!!
那现在咋办???我们是不清楚List集合装载的元素是什么类型的,List<Objcet>
这样是行不通的…于是Java泛型提供了类型通配符 ?
所以代码应该改成这样:
public void test(List<?> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
?号通配符表示可以匹配任意类型,任意的Java类都可以匹配…
现在非常值得注意的是,当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。
记住,只能调用与对象无关的方法,不能调用对象与类型有关的方法。因为直到外界使用才知道具体的类型是什么。也就是说,在上面的List集合,我是不能使用add()方法的。因为add()方法是把对象丢进集合中,而现在我是不知道对象的类型是什么。
注意点
//注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){
}
//注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{
}
//注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<> list2 new ArrayList<?>();
5.1、有限制的通配符
- <?>:允许所有泛型的引用调用
-
通配符指定上限
上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
-
通配符指定下限
下限super:使用时指定的类型不能小于操作的类,即>=
-
举例:
- <?extends Number>(无穷小, Number]
只允许泛型为Number及Number子类的引用调用
- <?super Number>[Number,无穷大)
只允许泛型为Number及Number父类的引用调用
- <? extends Comparable>
只允许泛型为实现 Comparable接口的实现类的引用调用
- <?extends Number>(无穷小, Number]
@Test
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<Student>();
List<Person> list4 = new ArrayList<Person>();
List<Object> list5 = new ArrayList<Object>();
list1 = list3;
list1 = list4;
// list1 = list5;
// list2 = list3;
list2 = list4;
list2 = list5;
//读取数据:
list1 = list3;
Person p = list1.get(0);
//编译不通过
//Student s = list1.get(0);
list2 = list4;
Object obj = list2.get(0);
编译不通过
// Person obj = list2.get(0);
//写入数据:
//编译不通过
// list1.add(new Student());
//编译通过
list2.add(new Person());
list2.add(new Student());
}