Java泛型

0 阅读4分钟

泛型

基本原理

作用: 可以在编译阶段约束操作的数据类型,并进行检查。

格式: <数据类型>

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

没有泛型时,集合如何存储数据

若没有泛型时,我们可以给集合添加任意的数据类型,基本数据类型、引用数据类型、甚至是对象。

//1.创建集合对象
ArrayList list = new ArrayList();
​
//2.添加数据
list.add(123);
list.add("aaa");
list.add(new Student());

此时由于没有写泛型,遍历时所有的数据都会被提升为Object类型。

//3.遍历集合获取里面的每一个元素
Iterator it = list.iterator();
while(it.hasNext()){
    Object obj = it.next();
}
//多态的弊端出现了,不能访问子类的特有功能,也就是说此时不能去使用集合中的一些方法
//若是使用强转,其他的数据类型就无法正常使用了

为了解决这个问题,泛型出现在了现在的Java语言中。

好处

泛型的好处:

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

Java中的泛型是伪泛型。

在Java文件中,我们在集合中运用了泛型,但是等到了class文件时,泛型就会消失,这也被成为泛型的擦除

若是在Java文件中,我们添加进集合的数据不为字符串,则会直接报错异常。

细节

泛型的细节:

  • 泛型中不能写基本数据类型
  • 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
  • 如果不写泛型,类型默认为Object

泛型的使用

泛型类

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

格式:

修饰符 class 类名<类型>{
    
}

实例:

public class ArrayList<E>{
    
}

此时的E可以理解为变量,但是不是用来记录数据的,而是记录数据类型的。

我们可以用这个方式去定义一个泛型类。

public class MyArrayList<E>{
    Object[] obj = new Object[10];
    int size;
    
    //E:表示是不确定的类型,该类型在类名后面已经定义过了
    //e:形参的名字,变量名
    
    public boolean add(E e){
        obj[size] = e;
        size++;
        return  true;
    }
}

这时我们就定义了一个add方法,但是如果要使用这个方法,我们不能再用E了,而是用一个确切的数据类型来使用它,例如String。

泛型方法

使用场景:方法中形参类型不确定时,可以使用类名后面定义的泛型;

  • 方案一:使用类名后面定义的泛型(所有方法都能使用)
  • 方案二:在方法申明上定义自己的泛型(只有本方法能用)

格式:

修饰符 <类型> 返回值类型 方法名(类型 变量名){
    
}

实例:

public <T> void show(T t){
    
}

此处T可以理解为变量,记录类型的。

public class ListUtil{
    private ListUtil(){}
    
    //类中定义一个静态方法addAll,用来添加多个集合的元素
    
    //参数一:集合
    //参数二:要添加的元素
    
    //这里的括号中,不仅要定义list的数据类型,还要定义元素的数据类型,数量就是集合的长度
    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 addAll(ArrayList<E> list,E...e){
         for(E element : e){
             list.add(element);
         }
}
泛型接口

使用场景:接口中数据类型不确定时,可以使用泛型接口

格式:

修饰符 interface 接口名<类型>{
    
}

实例:

public interface List<E>{
    
}
  • 方式一:实现类给出具体类型
public class MyArrayList implements List<String>{
    
}

此时List的泛型是确定的,所以继承这个类型。

  • 方法二:实现类延续泛型,创建对象时再确定
public class MyArratList<E> implements List<E>{
    
}

这时实现类的泛型也是不确定的,我们可以直到调用时再去确定它的数据类型。

泛型的继承和通配符

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

也就是说,泛型里面写的是什么类型,那么只能传递什么类型的数据,不存在继承关系。

但有时候我们可能想去规定一个范围,例如一些数据类型可以传递,一些不可以传递,该怎么办呢?

这时候,我们可以使用泛型的通配符:?

?也表示不确定的类型,它可以进行类型的限定。

例如:

  1. ? extends E: 表示可以传递E或者E所有的子类类型
public static void method(ArrayList<? extends Person> list){
    
}
  1. super E: 表示可以传递E或者E所有的父类类型
public static void method(ArrayList<? super Student> list){
    
}

应用场景:

  • 如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。
  • 如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以使用泛型的通配符。

关键点: 可以限定类型的范围。