Java集合之泛型类和泛型接口

1. 为什么要有泛型

1.1 不使用泛型的问题

当不使用泛型时,集合中是可以存放任意类型对象的,只要把对象存储集合后,那么他们都会被提升为Object类型。当我们在取出每一个对象,并且进行相应的操作,需要进行类型转换。

观察下面代码:

public static void main(String[] args) {
    Collection coll = new ArrayList();
    coll.add("abc");
    coll.add("def");
    coll.add(5);//由于集合没有做任何限定,任何类型都可以存放
    Iterator it = coll.iterator();
    while(it.hasNext()){
        //需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
        String str = (String) it.next();
        System.out.println(str.length());
    }
}

程序在运行时发生了问题 java.lang.ClassCastException。为什么会发生类型转换异常呢?
由于集合没有使用泛型,什么类型的元素都可以存储,导致取出时强转引发运行时 ClassCastException。怎么来解决这个问题呢?
Collection虽然可以存储各种类型对象,但一般只存储同一类型对象,例如都是字符串对象。因此在JDK5之后,新增了泛型(Generic)语法,让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。

1.2 泛型概念

泛型参数化类型,也就是说要操作的数据一开始不确定是什么类型,在使用的时候,才能确定是什么类型,把要操作的数据类型指定为一个参数,在使用时传入具体的类型,实现代码的模板化。

//比如ArrayList集合(含有泛型),部分源代码如下(可以理解为模板)
public class ArrayList<E> {
    public E get(int index) {...}
    public E set(int index, E element) {...}
    public boolean add(E e) {...}
    public E remove(int index) {...}
}

//创建ArrayList集合对象指定类型为String时
List<String> list1 = new ArrayList<>();
//模板代码相当于变成如下形式
public class ArrayList<String> {
    public String get(int index) {...}
    public String set(int index, String element) {...}
    public boolean add(String e) {...}
    public String remove(int index) {...}
}
//所以使用list1集合对象,调用以上四个方法时,所有E的位置都变为了String


//创建ArrayList集合对象指定类型为Integer时
List<Integer> list2 = new ArrayList<>();
//模板代码相当于变成如下形式
public class ArrayList<Person> {
    public Integer get(int index) {...}
    public Integer set(int index, Integer element) {...}
    public boolean add(Integer e) {...}
    public Integer remove(int index) {...}
}
//所以使用list2集合对象,调用以上四个方法时,所有E的位置都变为了Integer

jdk1.7之后,泛型的简化操作:ArrayList flist = new ArrayList<>(); 可省略右侧中的数据类型。

1.3 使用泛型的好处

  • 将运行时期的ClassCastException,提前到编译时期变成了编译失败

  • 避免了类型强转的麻烦。

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class GenericDemo {
    public static void main(String[] args) {
        show02();
    }

    /**
     * 创建集合对象,使用泛型
     * 好处:
     *     1.想使用元素特有的方法,避免了类型转换的麻烦
     *     2.把运行时异常,提升到编译时期
     * 弊端:
     *     一旦确定数据类型,就只能存储该类型数据
     */
    private static void show02() {
        Collection<String> list = new ArrayList<>();
        list.add("abc");
        list.add("def");
        // list.add(5);//当集合明确类型后,存放类型不一致就会编译报错
        // 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String str = it.next();
            //当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
            System.out.println(str.length());
        }
    }

    /**
     * 创建集合对象,不使用泛型
     * 好处:
     *     集合的类型默认为Object类型,可以存储任意的对象
     * 弊端:
     *     不安全,
     *     不能使用元素特有的方法
     */
    private static void show01() {
        ArrayList list = new ArrayList();
        list.add("abc");
        list.add(1);
        //使用迭代器遍历集合
        Iterator it = list.iterator();
        while(it.hasNext()){
            //取出集合中元素,Object类型
            Object obj = it.next();
            System.out.println(obj);

            /*
               想使用字符串特有的方法length
               不能使用,需要向下转型
               Object obj = "abc";多态
             */
            String s = (String)obj;
            System.out.println(s.length());
        }

    }
}

2. 泛型类

2.1 定义格式:

//其中类型参数,一般用一个大写字母表示,比如: T,E,K,V
//如果要定义多个泛型,多个类型参数之间用逗号,隔开,例如:public class HashMap<K,V> {...}
权限修饰符 class 类名<类型形参> {  }

例如,API中的ArrayList集合:

泛型在定义的时候不具体,使用的时候才变得具体。在使用的时候确定泛型的具体数据类型。

public class ArrayList<E>{ 
    public boolean add(E e){ }

    public E get(int index){ }
   	....
}

2.2 使用泛型: 即什么时候确定泛型。

在创建对象的时候指定类型

例如,ArrayList<String> list = new ArrayList<>();

此时,变量E的值就是String类型,那么我们的类型就可以理解为:

class ArrayList<String>{ 
     public boolean add(String e){ }

     public String get(int index){  }
     ...
}

再例如,ArrayList<Integer> list = new ArrayList<>();

此时,变量E的值就是Integer类型,那么我们的类型就可以理解为:

class ArrayList<Integer> { 
     public boolean add(Integer e) { }

     public Integer get(int index) {  }
     ...
}

类、接口上声明的泛型,在本类、本接口中即代表某种数据类型,可以作为非静态成员变量的类型、非静态方法的形参类型、非静态方法的返回值类型、局部变量的类型等,但不能用于静态成员上

自定义泛型类:

public class MyGenericClass<T> {
    // 没有T类型,在这里代表 未知的一种数据类型 未来传递什么就是什么类型
    private T t; // 用于成员变量的数据类型

    public MyGenericClass() {
    }

    public MyGenericClass(T t) { // 用于方法的形参的数据类型
        this.t = t;
    }

    public T getT() { // 用于方法的返回值的数据类型
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
    
    public void test() { // 用于局部变量的数据类型
        T t;
    }
}

使用:

public class GenericClassDemo {
    public static void main(String[] args) {
        // 创建一个泛型为String的类
        MyGenericClass<String> gc1 = new MyGenericClass<>();
        // 调用setT
        gc1.setT("abc");
        // 调用getT
        String str = gc1.getT();
        System.out.println(str);
        //创建一个泛型为Integer的类
        MyGenericClass<Integer> gc2 = new MyGenericClass<>();
        gc2.setT(123);
        Integer num = gc2.getT();
        System.out.println(num);
    }
}

注意:

类型实参必须是一个类类型,不能用基本数据类型填充,但可以使用包装类。

继承中的使用:

父类:

public class MyGenericFuClass<T> {
    public void print(T t) {
        System.out.println(t);
    }
}
  • 定义子类继承泛型父类的同时指定泛型的类型
public class MyGenericZiClassA extends MyGenericFuClass<Integer> {
    @Override
    public void print(Integer num) {
        System.out.println(num);
    }
}
public static void main(String[] args) {
    // MyGenericZiClassA类已经没有泛型了
    MyGenericZiClassA gzA = new MyGenericZiClassA();
    gzA.print(100);
}
  • 定义子类继承泛型父类,若不指定的父类泛型类型,子类也必须声明为泛型类,创建对象时指定泛型的类型
public class MyGenericZiClassB<T> extends MyGenericFuClass<T> {
    @Override
    public void print(T t) {
        System.out.println(t);
    }
}
public static void main(String[] args) {
    //创建对象指定泛型类型为String
    MyGenericZiClassB<String> gzB = new MyGenericZiClassB<>();
    gzB.print("abc");
    //gzB.print(100);//错误,只能传递String类型的参数
    
    //创建对象指定泛型类型为Integer
    MyGenericZiClassB<Integer> gzB2 = new MyGenericZiClassB<>();
    gzB2.print(100);
}

3. 泛型接口

3.1 定义格式:

权限修饰符 interface 接口名<类型形参> {...}

例如,

public interface MyGenericInterface<T> {
    //抽象方法
    public abstract void show(T t);
}

3.2 使用方式:

1、定义实现类的同时指定泛型接口上的的具体数据类型

public class MyGenericInterfaceImplA implements MyGenericInterface<String> {
    @Override
    public void show(String s) {
        System.out.println(s);
    }
}
public class MyGenericInterfaceDemo1 {
    public static void main(String[] args) {
        MyGenericInterfaceImplA mi = new MyGenericInterfaceImplA();
        // 已指定String类型,只能传字符串
        mi.show("abc");
    }
}

2、定义实现类时,若不指定接口上的泛型的具体数据类型,实现类也必须指定为泛型类,创建对象时,确定泛型的类型

public class MyGenericInterfaceImplB<T> implements MyGenericInterface<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}
public class MyGenericInterfaceDemo2 {
    public static void main(String[] args) {
        //创建对象指定泛型类型为String
        MyGenericInterfaceImplB<String> mi = new MyGenericInterfaceImplB<>();
        mi.show("abc");

        //创建对象指定泛型类型为Integer
        MyGenericInterfaceImplB<Integer> mi2 = new MyGenericInterfaceImplB<>();
        mi2.show(100);
    }
}

4. 泛型类、泛型接口小结:

  • 语法格式:

    • 在创建泛型类、泛型接口的对象时,为泛型形参指定具体类型

    • 在继承泛型类或实现泛型接口时,为泛型形参指定具体类型

  • 异常类不能是泛型的

  • 不能使用new E[]。但是可以:E[] array = (E[])new Object[size]; E[] array = (E[])new Object[]{…};等

  • 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

    • 子类不保留父类的泛型:按需传参
      • 没有类型 擦除
      • 具体类型
    • 子类保留父类的泛型:泛型子类
      • 全部保留
      • 部分保留
    • 结论:子类除了指定或保留父类的泛型,还可以增加自己的泛型(可类推到接口继承、接口实现)
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}

增加自己的泛型:

class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son<A, B> extends Father{//等价于class Son extends Father<Object, Object>{
}
// 2)具体类型
class Son2<A, B> extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2, A, B> extends Father<Integer, T2> {
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值