09-Java的泛型

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新增的特性

  1. 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
  2. 在实例化集合类时,可以执行具体的泛型类型
  3. 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。 比如:add(E e) —>实例化以后:add(Integer e)
  4. 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
  5. 如果实例化时,没有指明泛型的类型。默认类型为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类,那么它就可以被继承

那它是怎么被继承的呢??这里分两种情况

  1. 子类明确泛型类的类型参数变量
  2. 子类不明确泛型类的类型参数变量

子类明确泛型类的类型参数变量

  • 泛型接口
/*
    把泛型定义在接口上
 */
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 是 B 的父类

//代码示例
@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接口的实现类的引用调用

@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());

}

博客参考:https://segmentfault.com/a/1190000014120746

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值