java泛型(Generic)超详细

目录

1.为什么要有泛型(Generic)?

2.泛型的设计背景

2.1那么为什么要有泛型呢,直接Object不是也可以存储数据吗?

3.在集合中使用泛型

4.自定义泛型结构

4.1自定义泛型结构:泛型类、泛型接口

4.2自定义泛型结构:泛型方法

5.泛型在继承上的体现

6.通配符的使用

6.1使用类型通配符:?

6.2有限制的通配符:


1.为什么要有泛型(Generic)?

泛型:标签

举几个例子:

1 中药店,每个抽屉外面贴着标签

2.超市购物架上很多瓶子,每个瓶子装的是什么,有标签

3.垃圾箱分类,在垃圾箱上标明垃圾的分类形式。

实际上就是说,你具体要使用什么,根据标签提示,放进去,如果不对应是放不进去的。相当于加了一个限制的条件。这是在编译过程中所表现出来的,如果不对就会报错。

2.泛型的设计背景

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的 对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来 解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于 这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个 参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList<E> 这个就 是类型参数,即泛型。

2.1那么为什么要有泛型呢,直接Object不是也可以存储数据吗?

1. 解决元素存储的安全性问题,好比商品、药品标签,不会弄错。

2. 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药 品都要辨别。

我们之前用的数组定义的类型只能是相同数据类型,就是单一的数据类型,集合(Object obj)类型,各种类型都能存入到里边,不够严格,引入泛型后表明是单一的数据类型。

举例说明一下:

在没有使用泛型之前重写compareto()方法需要: if (o instanceof Employee)判断是不是Employee数据类型。

 

@Override
    public int compareTo(Object o) {
        if (o instanceof Employee){
            Employee e= (Employee) o;
            return this.name.compareTo(e.name);//从小到大
        }

        throw new RuntimeException("传入数据不一致");
    }

如果我们直接就知道这个数据类型相同指定泛型直接这样写:

使用泛型

@Override
    public int compareTo(Employee o) {
        //不用判断数据类型,直接都是Employee类型的

            return this.name.compareTo(o.name);//从小到大

    }

3.在集合中使用泛型

泛型的使用
* 一.jdk5.0之后增加的特性
* 二.在集合中使用泛型
* 总结:
* 1.集合接口 或集合类 jdk5.0后都修改为带泛型的结构
* 2.在实例化集合类时,可以指明具体的泛型类型
* 3.指明完后,在集合类或接口中,凡是定义类和接口时,
* 内部结构使用到类的泛型的位置,都指定为实例化时的泛型类型
* 比如:add(E e)---->实例化后:add(Integer e) 看集合上边实例化指明的数据类型
*4.注意:我们泛型类型必须是类,不能是基本数据类型,需要用到包装类的位置,就拿包装类来进行替换
*5.如果实例化时,没有用泛型,默认类型为Object 的类型
* 三.如何自定义泛型类,泛型结构:泛型类,泛型接口,泛型方法
举例说明一下:

1.类型不安全性,我们有时候需要用某个具体的类型就容易出错。我也想到了可以用数组解决,但是数组的方法操作比较少,相对于集合用泛型解决了这个问题。

定义一个集合用来存放学生成绩,最终我们要的数据类型应该都是相同的,那么问题来了,在集合中我们可以存放各种数据类型即Object类型,我们在遍历时想要统一类型,进行强转,就出错。

@Test
    public void test1(){
        ArrayList list = new ArrayList();
        //存放学生成绩 存放一个别的数据类型
        list.add(78);
        list.add(88);
        list.add(98);
        list.add(100);
        //问题1:类型不安全,没有限制 都是Object 类型数据
       //list.add("aa");//不能转为int型,报异常 所以说不可以在list中不能添加别的数据类型的数据
        for (Object score:list){
            //向下强转时有可能出现ClassCastExceprion
            int stuscore= (Integer) score;
            System.out.println(stuscore);
        }
    }

2.解决类型不安全性,引入泛型之后在编译的时候直接爆红给出警告,添加不进去,就避免了各种类型鱼龙混杂的问题,也避免了要去来回的进行数据的强转问题:

//在集合中使用泛型
    //以ArrayList举例
    @Test
    public void test2(){
        ArrayList<Integer> list = new ArrayList<Integer>();//泛型不能使用基本的数据类型,只能使用包装类的形式
        //这就限制了数据类型
        list.add(78);
        list.add(88);
        list.add(98);
        list.add(100);
        //解决问题一:编译时进行类型检查,保证数据的安全
        //list.add("aa");//这时候就报异常了编译的时候报异常
        //遍历方式一
        for (Integer score:list){
            //避免了强转操作,强转操作容易出现问题。 其实还是用了泛型的优势
            int stuScore=score;
            System.out.println(stuScore);
        }
        //方式二 //定义结构的时候就使用了泛型
        //public interface Iterator<E>
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            Integer stuScore = iterator.next();
            System.out.println(stuScore);
        }

    }
//集合中使用泛型之前情况 HashMap()为例
    @Test
    public void test3(){
        //jdk新特性  后边这个new hashmap<>泛型可省略 默认推断
        //Map<String, Integer> map = new HashMap<String, Integer>();
        Map<String, Integer> map = new HashMap<>();
        map.put("Tom",89);
        map.put("meme",87);
        map.put("didi",69);
        map.put("Jack",100);
//        map.put(hhd,12);//有泛型报错 不满足形参的要求
        Set<Map.Entry<String,Integer>>entry=map.entrySet();// Set<E> Entry<K,V>嵌套一块
        Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Integer> e = iterator.next();
            String key = e.getKey();
            Integer value = e.getValue();
            System.out.println(key+"-->"+value);
        }
    }

4.自定义泛型结构

1. 自定义泛型类

2. 自定义泛型接口

3. 自定义泛型方法

 如何自定义泛型类,泛型结构:泛型类,泛型接口,泛型方法
* 1.关于自定义泛型类 接口这两个基本是一会事,只是接口和类的区别
* 2.泛型要么用,要么别用 一般都要用
* 如果泛型结构是一个接口或抽象类 ,测不可以创建对象
* 测试

4.1自定义泛型结构:泛型类、泛型接口

1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>

可行的

 

2. 泛型类的构造器如下:public GenericClass(){}。

而下面是错误的:public GenericClass<E>(){}

爆红 

3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。

4. 泛型不同的引用不能相互赋值。 尽管在编译时ArrayLis<String>t和ArrayList<Inetger>是两种类型,但是,在运行时只有 一个ArrayList会被加载到JVM中。

5. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。

 @Test
    public void test1(){
        //如果定义了泛型,实例化时没有指明类的泛型,则认为次泛型类型为object类型的
        //如果定义类是泛型的,那么建议实例化时要指明类的泛型
        Order order = new Order();
        order.setOrderT(123);
        order.setOrderT("sdf");

        //建议带上泛型在实例化时指明泛型类型

        Order<String> order1 = new Order<String>("tom",1001,"aa");

        order1.setOrderT("tom:hello");



    }

6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。

7. jdk1.7,泛型的简化操作:ArrayList <String>list = new ArrayList<>();

 

8. 泛型的指定中不能使用基本数据类型,如果要使用基本数据类型可以使用包装类替换。

//使用基本数据类型,必须使用包装类来替换,因为泛型也是一个类的对象

 

9. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态 属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。

原因://静态方法中不能使用类的泛型
//因为泛型时在创建对象实例化之前创建的
//而静态方法是在创建对象之前就已经执行了

 

10. 异常类不能是泛型的

异常类不能使用泛型:因为异常类 Exception这个API中没有使用泛型

 

11. 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity]; 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。

 

 

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

1. 子类不保留父类的泛型:按需实现  没有类型 擦除  具体类型  

2.子类保留父类的泛型:泛型子类  全部保留  部分保留

结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型

 

4.2自定义泛型结构:泛型方法

 方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。

 泛型方法的格式: [访问权限] 返回类型 方法名([泛型标识 参数名称]) 抛出的异常

//泛型方法 在方法中出现了泛型的结构,泛型的参数与类的泛型参数没有任何关系
//换句话说,泛型方法所属的类是不是泛型类都没有关系。
//认为这个类是E 而这个E是一个变量
//而在前边加个<E>这时候它知道了这是个变量,如果不在访问权限 前加<E>,系统默认是一个名字为E的一个类
//这个方法是泛型方法
//这个E是在调用的时候用的
//泛型方法可以声明为静态的,主要原因是泛型参数是在调用的时候确定的,并非在实例化才可以调用
 public static <E> List<E> copyFromArrayToList(E[] arr){
        ArrayList<E> list = new ArrayList<>();
        for (E e:arr){
            list.add(e);
        }
      return list;
    }

 测试://泛型方法在调用时,指明泛型参数的类型

@Test
    public void test4(){
        Order<String > order=new Order<>();
        Integer[] arr=new Integer[]{1,2,3,3};
        //泛型方法在调用时,指明泛型参数的类型
        List<Integer> list = order.copyFromArrayToList(arr);
        System.out.println(list);


    }

结果:

 

5.泛型在继承上的体现

如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的 类或接口,G并不是G的子类型。

比如:String是Object的子类,但是List<String>并不是List<Object>的子类。

 

反证法:
假设 list1=list2,
   list1.add(123);
  导致list2中混入非string中的数据,这个泛型时不对的,编译不通过 不具备并列关系,

 

 /*
    1.泛型在继承方面的体现
    虽然类A是类B的父类,但是G<A> 与G<B>没有父子类关系,二者是并列的关系
    补充:类A是类B的父类 A<G> B<G> 他们之间是什么关系

     */
    @Test
    public void test1(){

        Object obj=null;
        String str=null;

        obj=str;
        Object[] arr=null;
        String[] arr2=null;
        arr=arr2;//多态的 对象与对象之间的使用
        List<Object>list1=new ArrayList<Object>();
        List<String>list2=new ArrayList<String>();
        //此时的list1与list2不具有子分类关系。
        //编译不通过
       // list1=list2;

        /*
        反证法:
        假设list1=list2,
        list1.add(123);
        导致list2中混入非string中的数据,这个泛型时不对的,编译不通过 不具备并列关系


         */
    }

6.通配符的使用

6.1使用类型通配符:

比如:List <?>,Map<?,?>

List<?>是List<String>、List<Integer>等各种泛型List的父类。

2.读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型 是什么,它包含的都是Object。

3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。

 唯一的例外是null,它是所有类型的成员.

//写入的过程
//添加 对于list用了通配符之后就不可以加数据了
//限制添加数据,除了添加null之外。
//所有类类对象都是可以加null的

将任意元素加入到其中不是类型安全的:这样不又回到了之前,那还要泛型干什么!

Collection c = new ArrayList();

c.add(new Object()); // 编译时错误

因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集 合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知 道那是什么类型,所以我们无法传任何东西进去。

 唯一的例外的是null,它是所有类型的成员。

另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object。

 @Test
    public void test3(){
        List<Object> list1=null;
        List<String> list2=null;

        List<?> list=null;
        list=list1;
        list=list2;//表现为list<?>是父类
        //print(list);
       // print(list2);
        //
        ArrayList<String> list3 = new ArrayList<>();
        list3.add("ss");
        list3.add("bb");
        list3.add("cc");
        list3.add("dd");
        list=list3;
        //写入的过程
        //添加 对于list用了通配符之后就不可以加数据了
        //限制添加数据,除了添加null之外。
        //所有类类对象都是可以加null的
        list.add(null);
        //获取 允许读取数据,读取的数据类型是object的类型。以多态的方式进行读的
        Object o = list.get(0);
        System.out.println(o);


    }

使用通配符要注意以下几点:

6.2有限制的通配符:

3.有限制条件的统配符的使用

* ?extends A

* G<? ectends A> 可以作为G<A> 和G<B>的父类,其中B是A的子类

* ?super A

* G<? ectends A> 可以作为G<A> 和G<B>的父类,其中B是A的父类

代码中有详细注释:

@Test
    public void test4(){
        List<? extends Person>list1=null;//把extends看成小于等于 负无穷到person有上界
        List<? super Person> list2=null;//把super看成大于等于,有下届没有上界
        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=list4;
        Person person = list1.get(0);
        list1=list3;
        Person person1 = list1.get(0);
        list2=list4;
        Object object = list2.get(0);

        //写入数据
        //编译不通过
       // list1.add(new Student())
        //编译通过。
        list2.add(new Person());
        list2.add(new Student());
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值