theme: channing-cyan highlight: a11y-light
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情
1.为什么要有泛型
泛型可以理解为标签,比如药店里会在某一类药品处贴上标签方便寻找。
定义:把元素的类型设计成一个参数,这个类型参数叫做泛型。
比如List<String>
这表明该List只能保存字符串类型的对象
那么使用或不使用泛型有什么区别呢?看下面的代码
@Test
public void test(){
ArrayList list = new ArrayList();
//需求:存放学生的成绩
list.add(78);
list.add(60);
list.add(30);
list.add(99);
//问题1:类型不安全
list.add("Tom");
for (Object score : list){
//问题2:强转时,可能出现ClassCastException
int stuScore = (Integer) score;
System.out.println(stuScore);
}
}
我只想在list里存放int类型的数据,但是它可以存放其他类型数据,而且编译运行都不报错,在强转时,非int类型数据就会报ClassCastException的错误,这就是不使用泛型的缺点。
2.在集合中使用泛型
- 集合接口或集合类在JDK5.0时都修改为带泛型的结构。
- 在实例化集合类时,可以指明具体的泛型类型
- 指明完后,在集合类或接口中凡是定义类或接口时,内部结构(方法,构造器,属性等)使用到类的泛型的位置,都指定为实例化时的泛型类型
比如:add(E e) --->实例化以后:add(Integer e)
- 泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置拿包装类替换
- 如果实例化时,没有指明泛型的类型,默认为Object类型
2.1 以ArrayList为例
ArrayList<Integer> list = new ArrayList<Integer>();
这样就可以在编译时,进行类型检查,保证数据的安全
上面的代码中问题2的解决更方便
for (Integer score : list){
//避免了强转操作
int stuScore = score;
System.out.println(stuScore);
}
2.2 以HashMap为例
@Test
public void test(){
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("Tom",66);
map.put("Jerry",56);
map.put("Jo",76);
//泛型的嵌套
Set<Map.Entry<String, Integer>> entry = map.entrySet();
}
3.自定义泛型结构
自定义泛型结构有:泛型类、泛型接口;泛型方法
3.1 泛型类和接口
比如我现在定义一个泛型类叫Order,格式即下方代码所示
public class Order<T> {}
我可以在这个类里面定义一个T类型的属性,比如T orderT;
但这并不是说有一个类叫T,我们会在类的实例化时指定说明这个T到底是什么类型。就算到时候不指明,也会默认为Object类型。
比如Order<String> order1 = new Order<String>
,就表示T是String类型,不打算指明就把两边的<String>
去掉。
另外一个是泛型类子类的问题,比如我现在创建一个类叫SubOrder继承于Order,那么我可以在继承时就指明T类型。如下
public class SubOrder extends Order<Integer>{
}
注意:此时实例化子类SubOrder时不再需要写泛型,直接实例化。比如
SubOrder sub = new SubOrder();
那么肯定SubOrder里也只能加Integer类型
不过这里的SubOrder只能是一个普通的类,而不是泛型类。那么如何让子类也是泛型类呢?只要继承时父类继续不指明类型就行。
比如:
public class SubOrder<T> extends Order<T> {}
🎈下面是总结自定义泛型类和接口时需要注意的点:
- 泛型类可以有多个参数,多个参数都放在同一个<>中,例如
<E1,E2,E3>
- 泛型类的构造器不带<>
- 实例化时记得带<>
- 泛型不同的引用不能相互赋值,比如
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = null;
虽然都是ArrayList,但list1和list2不能再相互赋值,即
list1 = list2;
会编译报错
- 泛型要使用的话就一直用,要不用,就一直不用(针对同一程序中)
- 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
- JDK1.7,泛型可以简化操作:
Order<String> order1 = new Order<>();
即自动类型推断 - 不可使用基本数据类型,要用时用包装类代替
- 异常类不可使用泛型,比如
public class SubOrder<T> extends Exception{
}
- 静态方法中不可使用类的泛型;因为泛型是创建类的对象时指定的,静态方法要早于类对象的创建。比如上面的自定义Order泛型类中,我声明如下的静态方法就会报错
public static void show(T orderT){
System.out.println(orderT);
}
- 打算造一个T类型(T为暂时不指明的类型)的数组,不可以
T[] arr = new T[10];
这么写。要写只能T[] arr = (T[])new Object[10];
这么写。 - 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型
- 子类不保留父类的泛型:按需实现;
- 没有类型,擦除
- 具体类型
- 子类保留父类的泛型:泛型子类
- 全部保留
- 部分保留
3.2 泛型方法
在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。也就是说,泛型方法所属的类是不是泛型类都没关系。
泛型方法可以是static静态的,因为泛型方法中的泛型和类中的泛型无关。
比如我在一个泛型类Order里加一个泛型方法
public <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e :arr){
list.add(e);
}
return list;
}
List< E >是返回值类型,泛型方法要在返回类型前加<>。下面简单地调用一下
@Test
public void test(){
Order<String> order = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4};
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);
}
可以看到,泛型类Order中的泛型类型和泛型方法中的泛型类型一点关系都没有。
4.泛型在继承上的体现
类A是类B的父类,但G< A>和G< B>二者不具备子父类的关系,二者是并列关系。但他们有个共同的父类叫G< ?>,在下面通配符的时候就会讲述。
举个例子👇
Object obj = null;
String str = null;
obj = str;
我们可以看出String作为Object的子类,String类型的变量是可以赋值给Object类型的变量的,这就是继承体现。
但下面的代码就不行
List<Object> l1 = null;
List<String> l2 = null;
l1 = l2;
这属于并列关系,这两个类都在泛型里
补充:类A是类B的父类,A< G>是B< G>的父类
5.通配符的使用
通配符就是英文格式下的 "?" 问号
@Test
public void test1(){
List<Object> list1 = null;
List<String> list2 = null;
List<?> list3 = null;
list3 = list1;
list3 = list2;
}
比如我现在要打印list1和list2,但我又不想造两次打印函数,因为<>里的类型不一样。此时我就可以用通配符去造一次函数,把list1和list2扔进去打印就行了。
public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
打印list1可以直接print(list1);
就行了。
5.1通配符中数据的读取和写入要求
添加时,对于List< ?>就不能向其内部添加数据。
比如:
@Test
public void test1(){
List<?> list = null;
List<String> list1 = new ArrayList<>();
list1.add("aa");
list1.add("bb");
list = list1;
//当对list进行添加操作时就会报错
list.add("cc");
}
当然有个例外,添加null就不回报错。
读取数据时可以进行正常get操作,读取的类型为Object
Object o = list.get(0);
5.2有限制的通配符使用
比如我新建两个类,一个Person一个Student,其中Student类继承于Person类。
下面就是研究两个问题:即对于? extends Person
和 ? super Person
的理解
public void test(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = null;
List<Person> list4 = null;
List<Object> list5 = null;
list1 = list3;
list1 = list4;
//list1 = list5;
//list2 = list3;
list2 = list4;
list2 = list5;
}
上方代码中注释掉的就表示编译不通过
? 可以看做负无穷到正无穷
extends可以看做小于等于,Student类又是继承于Person的,所以
list1 = list3;
list1 = list4;
的编译是通过的super可以看做大于等于,所以Student的list3赋值给list2时编译不通过。
总结:
- ? extends Person:可以作为G< A>和G< B>的父类,其中B是A的子类
- ? super Person:可以作为G< A>和G< B>的父类,其中B是A的父类