Java泛型

前言:

泛型(Generics)是 Java 编程语言中的一个重要特性,它允许我们在定义类、接口和方法时使用一个或多个类型参数,从而使得代码具有更高的复用性和类型安全性。

为什么要使用泛型?

1.从使用层面上来说:
  统一数据类型,防止将来的数据类型转换异常
2.从定义层面上来看:
  定义带泛型的类,方法等,将来使用的时候给泛型确定什么类型,泛型就会变成什么类型,凡是涉及到泛型的都会变成确定的类型,代码更灵活

 举一个例子:

Java中的ArrayList

package a_genericity;

import java.util.ArrayList;

public class Test02 {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("aaa");
        list.add(1);
        list.add(true);
        //获取元素中字符串的长度
        for (Object o : list) {
            String str = (String) o;
            System.out.println(str);
        }
    }
}

这段代码逻辑:首先我定义了一个 ArrayList 并且我没有指定类型(默认就是Object)

我往这个列表中存了三个元素 : aaa  1  true

现在我的需求是需要知道这三个元素的长度

这三个元素不全是字符串,没有直接获取长度的方法

所以我想着转成字符串进行获取长度,然后就报错了ClassCastException 类型转化异常

出现这个问题的原因就是我们没有统一数据类型

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法,下面将正式介绍泛型的相关知识。


泛型类:

定义:

 public class 类名<E>{
       
   }

我们常见的ArrayList就是一个泛型类

 下面模拟一个ArrayList,自己造一个MyArrayList

package a_genericity;

import java.util.Arrays;

public class MyArrayLista<E> {
    Object[] objects = new Object[10];
    int size = 0;

    public boolean add(E e){
        objects[size++] = e;
        return true;
    }
    public E get(int index){
        return (E)objects[index];
    }

    @Override
    public String toString() {
        return "MyArrayLista{" +
                "objects=" + Arrays.toString(objects) +
                '}';
    }
}


package a_genericity;

import java.util.ArrayList;

public class Test01 {
    public static void main(String[] args) {
        MyArrayLista<String> myArrayLista = new MyArrayLista<>();
        myArrayLista.add("a");
        myArrayLista.add("b");
        myArrayLista.add("c");
        System.out.println(myArrayLista.get(0));
        System.out.println(myArrayLista);
    }
}

在MyArrayLista中创建了一个Object的数组来模拟底层数组,一个size变量来模拟数组的大小

并且写了三个方法:add  get和toString

在测试类中模拟使用ArrayList

泛型方法:

package a_genericity;

import java.util.ArrayList;

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

package a_genericity;

import java.util.ArrayList;

public class Test01 {
    public static void main(String[] args) {
        MyArrayLista<String> myArrayLista = new MyArrayLista<>();
        myArrayLista.add("a");
        myArrayLista.add("b");
        myArrayLista.add("c");
        System.out.println(myArrayLista.get(0));
        System.out.println(myArrayLista);
        System.out.println("==============================");
        ArrayList<Integer> integerArrayList = new ArrayList<>();
        MyArrayListb.addAll(integerArrayList,1,2,3);
        System.out.println(integerArrayList);
    }
}
注意:

public static <E> addAll(ArrayList<E> list,E...e){}这里的<E>是声明这个方法是个泛型方法不代表返回值类型,所以我们自己还得定义返回值类型


可变参数语法糖:

我们注意到这个方法后面出现了E...e,这个代码:

在 Java 中,可变参数(Varargs)是一种方便的语法糖

  1. 当我们声明一个方法使用可变参数时,编译器会将可变参数转换为一个数组。
  2. 在调用该方法时,传递给可变参数的参数将被封装成一个数组对象。
  3. 在方法内部,可以将这个数组当作一个普通的数组进行操作。


泛型接口:

public interface MyList <E>{
    public boolean add(E e);
}
public class MyArrayList1<E> implements MyList<E>{
    //定义一个数组,充当ArrayList底层的数组,长度直接规定为10
    Object[] obj = new Object[10];
    //定义size,代表集合元素个数
    int size;

    /**
     * 定义一个add方法,参数类型需要和泛型类型保持一致
     *
     * 数据类型为E  变量名随便取
     */
    public boolean add(E e){
        obj[size] = e;
        size++;
        return true;
    }

    /**
     * 定义一个get方法,根据索引获取元素
     */
    public E get(int index){
        return (E) obj[index];
    }

    @Override
    public String toString() {
        return Arrays.toString(obj);
    }
}

public class Demo04Genericity {
    public static void main(String[] args) {
        MyArrayList1<String> list1 = new MyArrayList1<>();
        list1.add("张三");
        list1.add("李四");
        System.out.println(list1.get(0));

    }
}

看到三种泛型的用法,我们需要考虑一个问题:

泛型的类型什么时候被定义?

对于泛型类来说:new对象的时候确定类型 

对于泛型方法来说:调用的时候确定类型

对于方法接口来说:

  a.在实现类的时候还没有确定类型,只能在new实现类的时候确定类型了 ->比如 ArrayList
  b.在实现类的时候直接确定类型了 -> 比如Scanner 


我们来一个项目中会用到泛型的案例来加深一下理解。



/**
 * 后端统一返回结果
 * @param <T>
 */
@Data
public class Result<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private T data; //数据


    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.data = object;
        result.code = 1;
        return result;
    }

}

这是项目中用于统一返回响应的Result类

我们重点看:

public static <T> Result<T> success(T object){}

这里有三个T 

第一个<T>:表示声明这个方法是一个泛型方法,谁调用这个方法,这个T就是什么类型

第二个<T>:首先我们需要观察到这个是一个构造方法,Result本身是个泛型类,所以第二个<T>表示这个Result这个泛型类。

第三个T:这个T不是<T>了,这是是说明传进来的对象的类型是不确定的。


泛型的高级应用:

泛型通配符 ?

public class Demo01Genericity {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("张三");
        list1.add("李四");

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        
        method(list1);
        method(list2);
    }
    
    public static void method(ArrayList<?> list){
        for (Object o : list) {
            System.out.println(o);
        }
    }

}

这段代码的逻辑:

首先我创建了两个列表,存储的元素类型分别是String和Integer

然后我创建了一个方法method 想要分别遍历这两个列表

就可以使用:ArrayList<?> list这种传入参数的办法

就不会导致类型冲突

不过其实也可以ArrayList list默认也行

泛型的上限下限

1.作用:可以规定泛型的范围
2.上限:
  a.格式:<? extends 类型>
  b.含义:?只能接收extends后面的本类类型以及子类类型    
3.下限:
  a.格式:<? super 类型>
  b.含义:?只能接收super后面的本类类型以及父类类型

我们来看一个案例马上就明白了:

/**
 * Integer -> Number -> Object
 * String -> Object
 */
public class Demo02Genericity {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();
        ArrayList<Number> list3 = new ArrayList<>();
        ArrayList<Object> list4 = new ArrayList<>();

        get1(list1);
        //get1(list2);错误
        get1(list3);
        //get1(list4);错误

        System.out.println("=================");

        //get2(list1);错误
        //get2(list2);错误
        get2(list3);
        get2(list4);
    }

    //上限  ?只能接收extends后面的本类类型以及子类类型
    public static void get1(Collection<? extends Number> collection){

    }

    //下限  ?只能接收super后面的本类类型以及父类类型
    public static void get2(Collection<? super Number> collection){

    }
}

首先我这里有四个列表,存储元素的类型分别是:Integer String Number Object

在我们讲这个案例之前,我们需要知道这四个类的继承关系:

Integer 继承 Number 继承 Object
String 继承 Object

知道了这个之后,我们就能发现,当我们在方法get12中限定了泛型的上下限

有的列表根本就不能当作参数传进去。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值