【数据结构】泛型的简单入门

目录

1、泛型类

2、泛型方法

3、泛型接口

4、泛型数组

补充:泛型中的通配符


为什么会出现泛型?

         首先我们要知道没有泛型的时候,集合是怎么存储数据的?

       我们希望集合接收任意类型的数据,可以使用Object类型进行所有类型参数的设置(因为它是所有类型的父类),可以往集合中添加任意的数据类型。但是这样有一个问题:就是我们在使用get获取该Object类型的值时,需要强制转换为具体的类型,且不能使用它的特有行为。因此出现了泛型,可以在添加数据的时候对类型进行统一,而且获取数据的时候不需要类型的强转,非常的方便。可以参考下面的代码理解: 

泛型的测试类:

public class Genericity {
    public static void main(String[] args) {
        /**
         * 问题:在没有泛型的时候,我们是怎么在集合中添加数据的?
         */
        //(1)定义一个没有泛型的集合:此时默认存储的数据类型是Object类型
        ArrayList list = new ArrayList<>();
        //(2)在集合中添加数据:可以是任意的数据类型
        list.add(1);
        list.add("bbb");
        list.add(new Student("zhangSan",12));//自定义的类
        //(3)遍历集合,获取集合中的每一个元素——>使用迭代器的方式
        Iterator it = list.iterator();
        while (it.hasNext()){
//            Object obj = it.next();//获取当前数据并移动指针
            //多态的方式存在一个问题:就是不能使用子类的特有方法
            //比如:我要使用String的.length()方法是不可以的;如果要使用,则要进行数据的强转
//            obj.length();//报错
            String str = (String)it.next();
            str.length();
            System.out.println(str);
        }
        /**
         * 泛型的出现:。
         * 好处:进行数据类型的统一,就是在编译阶段进行数据类型的检查,其他的类型就会报错
         * 而且获取数据的时候因为是指定的类型,所以就不需要进行数据类型的强转。
         */
        ArrayList<String> list1 = new ArrayList<>();
        ArrayList<Animal> list2 = new ArrayList<>();
        list1.add("aaa");
//        list1.add(1);//添加其他类型的数据就会在编译阶段报错
        list2.add(new Animal());
        list2.add(new Cat());//泛型还可以添加自定义类型的子类

    }
}

上面的测试类中使用到的类:Student类,Animal父类和继承Animal的Cat类。 

Student类:

//Student类
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 + "}";
    }
}

 Animal类:

//Animal类
public class Animal {
    private String name;
    private int age;

    public Animal() {
    }

    public Animal(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 "Animal{name = " + name + ", age = " + age + "}";
    }
}

 Cat类:

//Cat类
public class Cat extends Animal{
    public Cat() {
    }

    public Cat(String name, int age) {
        super(name, age);
    }

}

知道了泛型为什么出现,接下来我们看一下泛型是什么~ 

泛型是什么?

         ——> 在定义具体的类或者方法的时候,参数的类型暂定,只有在具体使用的时候才明确具体类型。

泛型的格式:

             <数据类型>

注意:

  • 泛型只能支持引用数据类型,如果是基本数据类型,要使用它对应的包装类。
  • 指定泛型的具体类型后,传递数据时,可以传递该类类型或者是其子类类型
  • 如果不写泛型,那么默认类型是Object类型

泛型的好处:统一数据类型 +将运行阶段的问题提前到编译阶段,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来。 


泛型主要分为泛型类,泛型方法,泛型接口和泛型数组

1、泛型类

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

 格式:         修饰符 class 类名<类型>{

                              }

 举例:        public class ArrayList<E>{

                    }

 具体使用: 类名称<具体的参数类型> 类引用 = new 类名称<>();   

 举例:          ArrayList<String> list = new ArrayList<>();

public class GClassUse<T,E> {
    //T,E表示类型参数:因为这里我希望x和y设置为不同的类型
    //一般的类型参数用T,E,S,(K,V一般表示键值对)表示
    private T x;
    private E y;

    public GClassUse() {
    }

    public GClassUse(T x, E y) {
        this.x = x;
        this.y = y;
    }

    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }

    public E getY() {
        return y;
    }

    public void setY(E y) {
        this.y = y;
    }

    public static void main(String[] args) {
        //创建类,在具体使用的时候确定x类型为Integer,y的类型为String类型
        GClassUse<Integer,String> gClassUse = new GClassUse<>();
        gClassUse.setX(10);//设置x属性值必须是整型
//        gClassUse.setX(10.2);//传入其他类型报错
        gClassUse.getX();//直接get获取
        gClassUse.setY("fighting!");//设置y的属性值类型为String
        gClassUse.getY();//get直接获取y值        
    }
}

2、泛型方法

       当方法中的形参类型不确定时,就可以使用泛型类,将该方法写在泛型类里面;但是这样存在一个问题:就是该泛型类中所有的方法都要使用泛型。但是如果我只是有一个或少数几个方法的类型参数不确定,那么就没有必要使用泛型类,此时就出现了泛型方法,我们直接将该方法定义为泛型方法即可。

格式: 访问修饰符<参数类型> 方法名称( 类型 变量名){

            }

           public [static]  <E> void fun(E e){

           }

           调用该方法时,E就会确定类型。

特点:

  • 看是不是泛型方法主要看的是有没有<>;
  • 泛型方法和泛型类是独立存在的,也就是说泛型方法中的类可以不是泛型类;
  • 泛型在调用时明确自己的参数类型,泛型类则是实例化类时明确自己的参数类型

总结: 当方法中的形参类型不确定时:

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

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

public class GMethodUse<T> {
    private T x;
    //2、区分:这不是泛型方法,这是定义了参数类型T的普通类
    public T getX() {
        return x;
    }
    public void setX(T x) {
        this.x = x;
    }

    //1、泛型方法的定义
    public static <E> void fun(E e){
        System.out.println(e);
    }

    public static void main(String[] args) {
        GMethodUse<String> gMethodUse = new GMethodUse<>();
        fun(20.2);//3、泛型方法调用时的参数类型是double
        gMethodUse.fun("hello");//泛型方法调用时的参数类型是String
    }
}

 举例:要求定义一个工具类ListUtil,在类中定义一个静态方法addAll,用来添加多个元素。

工具类ListUtil:

public class ListUtil {
    /**
     * 定义工具类,我们一般私有化它的构造方法,防止外界调用
     */
    public ListUtil(){}

    /**
     * 要求:静态方法addAll,用来添加多个元素
     * 考虑:需要传递哪些参?——>将元素添加到哪里?因此要传递一个集合和所添加的元素
     * 注意:现在不知道传递的集合类型和添加元素的数据类型,所以定义该方法为泛型方法
     * 泛型方法:只有在调用addAll方法的时候才能确定具体传入的数据类型是什么
    */
    public static<E> void addAll(ArrayList<E> list,E e1, E e2,E e3, E e4){
        list.add(e1);
        list.add(e2);
        list.add(e3);
        list.add(e4);
    }

    public static<E> void addAll3(ArrayList<E> list, E...e){
        /**
         * 采用遍历的方式添加数据:比如增强for
         */
        for (E element : e) {
            System.out.println(element);
        }
    }
}

测试类 :ListUtilTest

public class ListUtilTest {
    public static void main(String[] args) {
        //创建一个String类型的集合
        ArrayList<String> list1 = new ArrayList<>();
        //在调用泛型方法的时候在确定具体的数据类型
        ListUtil.addAll(list1,"111","222","333","444");
        System.out.println(list1);

        //创建一个Integer类型的集合
        ArrayList<Integer> list2 = new ArrayList<>();
        //在调用泛型方法的时候在确定具体的数据类型
        ListUtil.addAll(list2,111,222,333,444);
        System.out.println(list2);

        /**
         * 注意:如果此时不确定要往集合中添加的元素个数是多少,可以使用  可变参数
         * 在addAll3方法中传入参数时:写为E...e表示不确定元素个数的多少
         */
        //创建一个Integer类型的集合
        ArrayList<Integer> list3 = new ArrayList<>();
        //在调用泛型方法的时候在确定具体的数据类型
        ListUtil.addAll3(list3,000,111,222,333,444,555,666,777,888,999,123);
        System.out.println(list3);
    }
}

3、泛型接口

格式:泛型接口

      interface IMessage<T> {

             void printFun(T t );//抽象方法

      }

特点:

  • 情况1:当子类实现泛型接口时:子类仍然是泛型类,在创建对象时再确定具体类型
  • 情况2:当子类实现泛型接口时,子类就已经明确了具体类型,不需要再给出具体的数据类型
public class GInterfaceUse {
    interface IMessage<T>{//泛型接口
        public abstract void printFun(T t);//抽象方法
    }
    //注意两种情况的写法格式~~
    //情况1:当子类实现泛型接口时,子类也是个泛型类
    class Fun1<T> implements IMessage<T>{
        @Override//重写接口中的抽象方法
        public void printFun(T t) {
            System.out.println(t);//
        }
    }
    //情况2:当子类实现泛型接口时,子类已经明确了具体的数据类型
    class Fun2 implements IMessage<String>{
        @Override
        public void printFun(String s) {
            System.out.println(s);
        }
    }
    //测试
    public static void main(String[] args) {
        GInterfaceUse.Fun1 fun1 = new GInterfaceUse().new Fun1();
        fun1.printFun(111);//传任意类型
        GInterfaceUse.Fun2 fun2 = new GInterfaceUse().new Fun2();
        fun2.printFun("hello");//只能传字符串
    }
}

4、泛型数组

特点:

  • Java中可以声明类型参数的数组,但是不能new出来参数类型的数组;
  • java中没有泛型数组,如果要使用“泛型数组”,要使用Object类型的数组!
  • 关于该数组的所有增删改查操作通过方法来操作,其中方法使用类型参数。
public class GArrayUse<T> {
    //特点1:只能声明泛型数组,不能创建
//    T[] arr;//可以声明
//    T[] arr = new T[10];//报错:不能创建

    //特点2:要使用泛型数组,要使用Object类型的数组
    Object[] arr = new Object[10];//创建Object类型的数组
    int i;
    public void add(T t){// 在使用这个数组时,增删查改,使用方法来操作,方法使用类型参数~
        arr[i] = t;
        i++;
    }
    public void add1(Object obj){//对比: 在使用这个数组时,增删查改,使用方法来操作,方法使用Object类型~
        arr[i] = obj;
        i++;
    }

    public T getArr(int index) {
        return (T) arr[index];
    }

    public Object getArr1(int index) {
        return arr[index];
    }

    public static void main(String[] args) {
        GArrayUse<Integer> gArrayUse = new GArrayUse<>();//泛型类实例化时确定参数类型Integer
        //--------------对比add和add1的区别----------
        //--------------对比add:参数类型是泛型----------
        gArrayUse.add(11);//只能传入整数类型
        gArrayUse.add(18);
        int res1 = gArrayUse.getArr(0);//不需要类型转换,直接获取
        System.out.println(res1);
        //------------add1:Object类型的参数:需要类型的强转-------------
        gArrayUse.add1(12);
        int res2 = (int)gArrayUse.getArr1(0);
        System.out.println(res2);
    }
}

补充:泛型中的通配符

首先知道泛型的一个特点:泛型不具备继承性,但是数据具备继承性。

同时了解第二个知识点:泛型的通配符是什么?可以参考下面的代码及注释。

     * 泛型的通配符:
     * 希望:这个方法虽然不确定要传递的类型,但是希望只能传递Ye Fu Zi,Student不行,则可以使用泛型的通配符
     * ?表示不确定的类型
     * ? extends E:表示可以传递E或者E的所有子类
     * ? super E:表示可以传递E或者E的所有父类

public class MyGenericity {
    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方法里的类型参数为Ye,即便是Fu,Zi也不能传递,只能传递Ye类型
         */
        //调用method方法
        method1(list1);
//        method1(list2);//报错
//        method1(list3);//报错
        /**
         * 泛型特点:但是数据具备继承性
         */
        list1.add(new Ye());
        list1.add(new Fu());
        list1.add(new Zi());

        method2(list1);
        method2(list2);
        method2(list3);
        method2(list4);

        method3(list1);
        method3(list2);
        method3(list3);
//        method3(list4);//报错:只能传递Ye和Ye的子类

        method4(list1);
        method4(list2);
        method4(list3);
//        method4(list4);//报错:只能传递Zi的父类
    }
    /**
     * method1只能传递Ye类型
     */
    public static void method1(ArrayList<Ye> list){

    }
    /**
     * method2是一个泛型方,可以传递任意的数据类型,包括有继承关系的Ye Fu Zi,以及单独的没有任何关系的Student类
     */
    public static<E> void method2(ArrayList<E> list){

    }
    /**     
     * ? extends E:表示可以传递E或者E的所有子类
     * ? super E:表示可以传递E或者E的所有父类
     */
    public static void method3(ArrayList<? extends Ye> list){

    }

    public static void method4(ArrayList<? super Zi> list){

    }
}
//创建具有继承关系的三个类
class Ye{}
class Fu extends Ye {}
class Zi extends Fu {}
class Student{}

 好啦~ 今天终于到周六啦,明天再学~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值