1.什么是泛型?

在定义类时,不会给 类中成员: ( 属性 方法的返回值 方法的参数 )定义数据类型,而在类对象创建时为其指定相应的数据类型

泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。

泛型的格式:<数据类型>

常见的泛型-------集合中的泛型 :  List <E>

注意:泛型只能支持引用数据类型

2.为什么使用泛型

举个栗子:定义一个姓名 和年龄

package com.demo.demo1;

public class student {
    private String name;
    private int age;


    public student() {
    }

    public student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "student{name = " + name + ", age = " + age + "}";
    }
}

 当添加时,没有给集合限制类型,所有默认是Object类型,因为Object是所有类的父类

--自动向上转型

添加的时候,可以添加任意类型

package com.demo.demo1;

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

public class Demo {
    public static void main(String[] args) {
        //没有泛型的时候,集合如何储存数据

        //1.创建集合的对象
        ArrayList list= new ArrayList<>();

        //2.添加数据
        list.add(113);
        list.add("小张");
        list.add(new student("小丽",18));

        //3.遍历集合获取每一个元素
        Iterator it = list.iterator();
        while (it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}

迭代器遍历打印的结果

 而我们想用string的length时候会报错,多态的弊端是不能使用子类的特有功能,强行使用会报错

试一下强转为String

package com.demo.demo1;

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

public class Demo {
    public static void main(String[] args) {
        //没有泛型的时候,集合如何储存数据

        //1.创建集合的对象
        ArrayList list= new ArrayList<>();

        //2.添加数据
        list.add(113);
        list.add("小张");
        list.add(new student("小丽",18));

        //3.遍历集合获取每一个元素
        Iterator it = list.iterator();
        while (it.hasNext()){
            String str = (String) it.next();
            //多态的弊端是不能访问子类的特有功能 比如现在获取字符串的长度就会报错
            //obj.length();
            str.length();
            System.out.println(str);
        }
    }
}

而强转后也会报错,类型转换异常,Integer 不能强转为String类型  数据类型不安全问题

而强转为student 对象 字符和整数也都不能用了

 

集合加上String类型,也会报错

添加的数据要么全是String类型,要么全是Integer类型

package com.demo.a1_fanxing;

import java.util.Arrays;

/*
*
*    当我在编写一个类的时候,如果不确定类型,那么这个类就可以定义为泛型类
* */
public class MyArrayList <E>{
    Object[]obj=new Object[10];
    int size;
    /*
      添加的方法里不知道存什么数据可以使用E
      E: 表示是不确定的类型,该类型在类名后面已经定义过了.
      e:形参的名字,变量名
    * */
    public boolean add(E e){
      obj[size]=e;
      size++;
      return true;
    }
    /**
     * 返回值类型不能写object对象 ,表示返回值也要写不确定的类型
     * @param index
     * @return
     */
    public E get(int index){
        return (E)obj[index];
    }
    @Override
    public String toString(){
        return Arrays.toString(obj);
    }
}

结论:

如果我们没有给集合指定类型,默认认为所有的数据类型都是object类型

此时可以往集合添加任意的数据类型

带来一个坏处:我们在获取数据的时候,无法使用他的特有行为。

添加的数据要么全是String类型,要么全是Integer类型...等类型

所以java推出了泛型,可以在添加数据的时候就可以把类型进行统一,而且我们在获取数据的时候,也省的强转了,非常的方便

好处:

统一数据类型

把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来

扩展知识:

java中的泛型是伪泛型,只在编译器有效,假如有一个泛型的集合是String类型,只要是String类型都可以添加集合当中,当数据添加的时候仅仅是在门口去检查是否符合String类型,符合就添加进去,当添加到里面的时候,集合还是会这些数据当成Object类型来处理,当往外获取的时候,集合的底层会把这些Object按着泛型类型去强转为对应的类型,编写的时候存在真正泛型,编译成字节码文件的时候,泛型就会消失,在java中有个专业名字,叫做"泛型的擦除"

因为在老版本的时候是没有泛型的,在jdk5才出来的,在jdk5以前都是当做Object类型,大家在使用的时候非常的不方便,又不可能去改以前的屎山源码,在原本的代码不动之上,在代码的门口加个限定,这样就解决了又不用修改代码,又统一了类型,两全其美,美滋滋

泛型的细节:

泛型中不能写基本数据类型,因为写了也没办法转成Object类型,比如int只能用它的父类Integer才能转成Object

指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型

如果不写泛型,类型默认是Object

3.如何定义泛型类?

泛型可以在很多地方进行定义

写在类后面称为泛型类,写在方法上面称为泛型方法,写在接口后面称为泛型接口

泛型类使用场景:当一个类中.某个变量的数据类型不确定时,就可以定义带有泛型的类

举个栗子:这里的<E>就表示不确定的类型,当创建该类对象时,E就确定了类型,此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成任意的:T丶E丶K丶V等

package com.demo.a1_fanxing;

import java.util.Arrays;

/*
*
*    当我在编写一个类的时候,如果不确定类型,那么这个类就可以定义为泛型类
* */
public class MyArrayList <E>{
    Object[]obj=new Object[10];
    int size;

    /*
      添加的方法里不知道存什么数据可以使用E
      E: 表示是不确定的类型,该类型在类名后面已经定义过了.
      e:形参的名字,变量名
    * */
    public boolean add(E e){
      obj[size]=e;
      size++;
      return true;
    }

    /**
     * 返回值类型不能写object对象 ,返回值也要写不确定的类型
     * @param index
     * @return
     */
    public E get(int index){
        return (E)obj[index];
    }
    @Override
    public String toString(){
        return Arrays.toString(obj);
    }
}

 注意:如果上边定义的<E>没有,那么方法里的E会报错,需要在类后面定义泛型

返回值的类型也要写成不确定的类型,不能写Object类型

创建对象

package com.demo.demo1;

public class Demo2 {
    public static void main(String[] args) {
       MyArrayList<String> list=new MyArrayList<>();

    }
}

当添加时候只能添加字符串,形参变成了字符串

 放入Integer类型就会报错

 

 输出打印

 如果类型Integer类型,那么就不能添加String类型

添加的时候显示只能添加Integer类型

 使用get方法也是Integer类型

 输出打印

4.如何定义泛型方法?

如果方法形参不确定有两种方案:

方案一:

使用类后面定义的泛型, 所有方法都能用

方案二:

在方法申明上定义自己的泛型,只有本方法能用

格式:

举例:此处T可以理解为变量,但是不是用来记录数据的,而是记录类型的,可以写成:T、E、K、V等,调用该方法时,T就确定类型

泛型方法的简单定义

[访问权限]   <泛型标识>   泛型标识   方法名称  (泛型标识 参数名称) {

}

方法中形参类型不确定时.可以使用类名后面定义的泛型<E>

但是如果只有类中只有一个方法的形参不确定,就没必要在类上加上泛型,只需要在方法上加上泛型,称为泛型方法,但是其他的方法就不能用了,想用也加上与其对应的<E>

但是其他的方法就不能用了,想用也加上与其对应的<E> ,或者在类名后加上<E>

举个栗子:

定义一个工具类:ListUtil

类中定义一个静态方法allAll,用来添加多个集合的元素

package com.demo.demo1;

import java.util.ArrayList;

public class ListUtil {
    private ListUtil(){}
    //类中定义一个静态方法allAll,用来添加多个集合的元素

    /*
    *
    *   参数一:集合
    *   参数二~最后:要添加的元素
    * */
    public  static<E> void addAll(ArrayList<E>list,E e1,E e2,E e3){
      list.add(e1);
      list.add(e2);
      list.add(e3);
    }
    public void show(){
        System.out.println("哦吼");
    }
}

注意:

如果不确定集合添加的元素要加泛型<E>,但是不能直接写

要把泛型E写在方法上面,要写在修饰符后面,public static是两个要写在sttatic后面

  

没有调用,类型是Object类型

方法被调用,类型就能确认下来

 Integer类型

想一直添加又不确定长度可以用可变参数

 public  static<E> void addAll(ArrayList<E>list,E ...e){
        for (E element:e) {
          list.add(element);
        }
    }

package com.demo.demo1;


import java.util.ArrayList;

/*
     定义一个工具类:ListUtil

     类中定义一个静态方法allAll,用来添加多个集合的元素
* */
public class Demo3 {
    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<>();

        ListUtil.addAll(list, "aaa", "bbb", "ccc");

        System.out.println(list);

        ArrayList<Integer> list2 = new ArrayList<>();
        ListUtil.addAll(list2,1,2,3);
        System.out.println(list2);


        ArrayList<Integer> list3 = new ArrayList<>();
        ListUtil.addAll(list3,1,2,3,4,5,5,5,5,5,,5,5,5,5,5,5,5);
        System.out.println(list3);
    }
}

小细节:

泛型方法的定义与其所在的类是否是泛型类是没有任何关系的,所在的类可以是泛型类,也可以不是泛型类。

5.如何定义泛型接口

当一个接口中类型不确定可以使用泛型接口

重点:如何使用一个带泛型的接口

 格式:

泛型接口的两种使用方式:

方案一:

使用类后给出具体类型

方案二:

实线类持续泛型,创建对象时再确定

 

方案一:

给出具体类型

创建一个实现类实现List接口 ,发现报错是因为List有泛型,Ctrl+B键跟进

需要加上类型

当实现接口定义类型的时候,那么它的添加方法,全都是与之对应的类型,

实现的方法有很多,只看它的add方法就会发现是String类型,

创建对象的时候就不用自己加泛型了

调用add方法时候,显示的是String

给其他类型就会报错

 

 方案二:

实线类持续泛型,当实现类也没有明确是什么类型的时候可以使用此方法延续下来,创建对象时再确定

相当于创建了泛型类实现了带有泛型接口

 此时发现方法都已经不确定了

 

创建的时候必须给其类型,,不给类型添加的时候就是Object类型

给其String类型,添加的时候也是String类型

package com.demo.demo1;

public class Demo4 {
    public static void main(String[] args) {
        /*
         泛型接口的两种使用方法:
                1.实现类给出具体的类型
                2.实现类延续泛型,创建实现类对象时再确定类型
         * */

        MyArrayList2 list=new MyArrayList2();
        list.add("123");

        MyArrayList3<String>list2=new MyArrayList3<>();
        list2.add("123");
    }
}

结论:

当你实现类可以确定类型的时候,可以使用第一种,

当你实现类不确定类型的时候可以使用第二种

注意:

第二种调用的时候必须给其类型

6.泛型的继承和通配符

泛型本身不具备继承性,但是数据具备继承性

package com.demo.demo1;

import java.util.ArrayList;

public class Demo5 {
    public static void main(String[] args) {
        /*
        *   泛型不具备继承性,但是数据具有继承性
        * */

        //创建集合的对象
        ArrayList<Ye>list1=new ArrayList<>();
        ArrayList<Fu>list2=new ArrayList<>();
        ArrayList<Zi>list3=new ArrayList<>();

        //调用method方法
        method(list1);
//        method(list2);
//        method(list3);

        
    }

     /*
     * 此时,泛型里面写的是什么类型,那么只能传递什么类型的数据
     * */
    public  static  void method(ArrayList<Ye> list){

    }
}

class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}

当调用method方法的时候,list2和list3报错

因为泛型本身不具备继承性,泛型里面写的是什么类型,那么只能传递什么类型的数据 

但是数据具有继承性


    

package com.demo.a1_fanxing;

import java.util.ArrayList;

public class Demo5 {
    public static void main(String[] args) {
        /*
        *   泛型不具备继承性,但是数据具有继承性
        * */

        //创建集合的对象
        ArrayList<Ye>list1=new ArrayList<>();
        ArrayList<Fu>list2=new ArrayList<>();
        ArrayList<Zi>list3=new ArrayList<>();

         //调用method方法
        //method(list1);
        //method(list2);
        //method(list3);

        list1.add(new Ye());
        list1.add(new Fu());
        list1.add(new Zi());
    }
    /*
     * 此时,泛型里面写的是什么类型,那么只能传递什么类型的数据
     * */
    public  static  void method(ArrayList<Ye> list){

    }
}

class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}

下面举个详细例子
       希望:本方法虽然不确定类型,但是以后我希望只能传递a b c 

   弊端:
        利用泛型方法有一个小弊端,此时他还可以接受任意的数据类型

package com.demo.demo1;

import java.util.ArrayList;

public class Demo6 {
    public static void main(String[] args) {
        /*
         *   需求:
         *        定义一个方法,形参是一个集合,但是集合中的数据类型不确定。
         * */

        //创建集合的对象
        ArrayList<Ye>list1=new ArrayList<>();
        ArrayList<Fu>list2=new ArrayList<>();
        ArrayList<Zi>list3=new ArrayList<>();
        ArrayList<Student>list4=new ArrayList<>();


        method(list1);
        method(list2);
        method(list3);
        method(list4);
    }

    /*
     * 此时,泛型里面写的是什么类型,那么只能传递什么类型的数据
     *
     * 弊端:
     *      利用泛型方法有一个小弊端,此时他还可以接受任意的数据类型
     *      Ye Fu  Zi Student
     *
     * 希望:
     * 本方法虽然不确定类型,但是以后我希望只能传递Ye Fu Zi
     *
     * 此时我们就可以使用泛型的通配符:
     *        ? 也代表不确定的类型
     *        它可以进行类型的限定
     *        ? extends E: 表示可以传递E或者E所有的子类类型
     *        ? super E: 表示可以传递E或者E所有的父类类型
     * */
    public  static<E>  void method(ArrayList<E> list){

    }
}
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}
class Student{}

  此时我们就可以使用泛型的通配符

    ? 也表示不确定的类型

     它可以进行类型的限定

    ? extends E: 表示可以传递E 或者 E所有的子类类型

    ?  super   E:表示可以传递E或者E所有的父类类型

[设置上限]
声明对象: 类名称<? extends 类> 对象名称;
定义类:  [访问权限] 类名称<泛型标识 extends 类>{}

[设置下限]
声明对象: 类名称<? super 类> 对象名称;
定义类:  [访问权限] 类名称<泛型标识 super 类>{}

使用?效果和E是一样的,使用?不用在修饰符后加<E>

? extends E: 表示可以传递E 或者 E所有的子类类型,此时的E就需要明确类型了

 

  ? super   E:表示可以传递E或者E所有的父类类型

 

package com.demo.a1_fanxing;

import java.util.ArrayList;

public class Demo7 {
    public static void main(String[] args) {
        /*
         *   需求:
         *        定义一个方法,形参是一个集合,但是集合中的数据类型不确定。
         * */

        //创建集合的对象
        ArrayList<a1>list1=new ArrayList<>();
        ArrayList<a2>list2=new ArrayList<>();
        ArrayList<a3>list3=new ArrayList<>();
        ArrayList<Student2>list4=new ArrayList<>();

        method(list1);
        method(list2);
//        method(list3);
//        method(list4);
    }

    /*
     * 希望:
     * 本方法虽然不确定类型,但是以后我希望只能传递a1 a2 a3
     *
     * 此时我们就可以使用泛型的通配符:
     *        ? 也代表不确定的类型
     *        他可以进行类型的限定
     *        ? extends E: 表示可以传递E或者E所有的子类类型
     *        ? super E: 表示可以传递E或者额E所有的父类类型
     * */
    public  static void method(ArrayList<? super a2> list){

    }
}
class a1{}
class a2 extends a1{}
class a3 extends a2{}
class Student2{}

结论:

1.如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。

2.如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以泛型的通配符泛型的通配符:
关键点:可以限定类型的范围。

不想限定范围就可以用之前的泛型类,泛型方法,泛型接口就可以了

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐