1.泛型概述
1.1 什么是泛型
泛型是 JDK5 出现的新特性。泛型的本质是类型参数化,即在不创建新的类型的情况下通过泛型指定不同类型来控制形参具体限制的类型。
1.2 为什么引入泛型
- 解决元素存储安全性问题 (指定泛型的具体类型之后,如果存储的元素类型不正确那么编译器就会在编译期报错)
- 避免类型转换异常 (泛型出现之前对集合元素进行遍历的时候元素类型会被自动转换成Objecet类型,当我们再次强转后运行时可能会报错)
- 提升代码可读性。从编码阶段就显式地知道泛型集合、泛型方法等处理的对象类型是什么
- 提高代码复用率,增加程序的通用灵活性 (比如ArrayList里可以存储各种对象,但是我们没有必要为每一种对象对应的类实现所有方法,只需要声明为泛型,应用时指定类型即可)
2. 集合中使用泛型
- 泛型的类型必须是类(包括自定义类),不能是基本数据类型。若需要用到基本数据类型应该使用对应的包装类
- 如果实例化时没有指明泛型的类型。默认类型为Object类型。
HashMap<String, Integer> hashMap=new HashMap<>();
hashMap.put("Tom", 20);
hashMap.put("Bob", 22);
hashMap.put("Sam", 24);
Set<Entry<String, Integer>> entrySet = hashMap.entrySet();
Iterator<Entry<String, Integer>> iterator = entrySet.iterator();
while(iterator.hasNext())
{
Entry<String, Integer> next = iterator.next();
String key=next.getKey();
int value=next.getValue();
System.out.println("Name:"+key+" Age:"+value);
}
Name:Tom Age:20
Name:Bob Age:22
Name:Sam Age:24
3. 自定义泛型类
class Operate<T>
{
String name;
int age;
T no; //可以自定义泛型变量
public Operate(String name, int age,T no)//注意:构造函数并不需要标识泛型
{
this.name = name;
this.age= age;
this.no=no;
}
public T setT(T anotherNo) //注意:此方法并不是泛型方法,只是返回类型是泛型
{
return no=anotherNo;
}
}
Operate<String> operate = new Operate("Tom", 15, "2020");
String setT = operate.setT("2021");
System.out.println(setT); //2021
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
- 泛型实例化后,操作原来泛型位置的结构必须与实例化的泛型类型一致 (例如上述Operate类将T实例化为String,那么属性no也应该是String)
- 泛型不同的引用不能相互赋值
- 静态方法中不能使用类的泛型。(静态方法是随着类的加载而加载,可以直接通过类调用静态方法。但是泛型类需要实例化的时候指明泛型类型,由此产生矛盾)
- 异常类不能声明为泛型
- 不能使用new E[ ] (因为编译器在编译期认为E[ ]是Object[ ],所以若可以声明泛型数组,那么就可以在泛型数组中加入任何元素,这违背了泛型设计的初衷)
- 子类继承的父类是泛型时的情况
class Father<T1, T2> { }
class Son1 extends Father { 等价于class Son1 extends Father<Object,Object>{} }
class Son2 extends Father<Integer, String> { 等价于class Son2<Integer,String> extends Father<Integer, String>}
class Son3<T1, T2> extends Father<T1, T2> { }
class Son4<T2> extends Father<Integer, T2> { }
class Son5<A, B> extends Father{ }
class Son6<A, B> extends Father<Integer, String> { }
class Son7<T1, T2, A, B> extends Father<T1, T2> { }
class Son8<T2, A, B> extends Father<Integer, T2> { }
4. 自定义泛型方法
泛型方法与泛型类并无直接联系。不管泛型方法所在的类是不是泛型类,都可以定义泛型方法。
- 泛型方法可以定义为静态,因为泛型参数是在调用方法时确定的
class GeneMethod
{
public static <E> ArrayList<E> copy(E []arr)
{
ArrayList<E> arrayList = new ArrayList<>();
for(E ele:arr)
{
arrayList.add(ele);
}
return arrayList;
}
}
ArrayList<Integer> AL=GeneMethod.copy(new Integer[] {1,5,9});
ystem.out.println(AL); //[1, 5, 9]
5. 通配符
- 泛型在继承上的体现
类A是类B的父类,G< A > 、G< B > 不具备子父类关系,而是并列关系,但是A< G > 是 B< G > 的父类
比如:Object类是String类的父类
ArrayList< Object > 并不是ArrayList< String > 的父类,Object< List >是String< List >的父类 - 为何引入通配符?
当我们需要一个遍历函数,但是不知道传入的参数时我们可能会这样做
public void print(ArrayList<Object> a)
{
for(Object o:a)
System.out.println(o);
}
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("a");
operate.print(arrayList);//报错
报错原因:我们已经知道 若类A是类B的父类,G< A > 、G< B > 不具备子父类关系。所以上述调用方法不符合多态
- 引入通配符( ?)
类型通配符 ( ? ) 是各种泛型的父类。List<?>表示传入的元素类型未知
public void print(ArrayList<?> a)
{
for(Object o:a)
System.out.println(o);
}
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("a");
operate.print(arrayList);//输出 a
为何改成这样就可以正常使用了呢?
因为此时ArrayList<?>是ArrayList<String>的父类
- 含有通配符的读写操作
1.不能往含有<?>引用的对象里写入数据 (除了NULL)
ArrayList<?> aList=new ArrayList<String>();
aList.add("aa");//报错
//原因是显而易见的,因为此时aList无法确定集合中元素类型(即不知道指向谁)
aList.add(NULL);//正确
//因为add()的一定是对象,不管add()的对象是什么都可以赋值为NULL
2.可以随意读取
ArrayList<String> aList=new ArrayList<>();
aList.add("a");
aList.add("b");
ArrayList<?> unList=aList;
Object o=unList.get(1);
System.out.println(o);//b
因为不管里面是什么,一定是object类的子类对象,所以一定可以读取
- 有限制的通配符
(1) <? extends Number>
只允许泛型为Number及Number子类的引用调用
(2) <? super Number>
只允许泛型为Number及Number父类的引用调用
(3) <? extends Comparable>
只允许泛型为实现Comparable接口的实现类的引用调用
已知Student是Person的子类
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;//ok
list1 = list4;//ok
list1 = list5;
list2 = list3;//wrong
list2 = list4;//ok
list2 = list5;//ok