Java——泛型使用

738 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

资料来源:

Java泛型T、E、K、V、N、?和Object区别和含义

深入理解Java泛型

1. 泛型

1. 概述

泛型可以把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型 。

相当于把数据类型作为参数来进行传递。

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

2. 泛型类&泛型接口的区别

  • 泛型类和泛型接口的用都相同,下面我们以泛型类为例进行讲解。
  • 泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来 。

3. 泛型标记符

字母含义
EElement 集合元素
TType Java类
KKey 键
VValue 值
NNumber 数值类型
?表示不确定的Java类型

这些标记并不是限定只有对应的类型才能使用,即使你统一使用A-Z英文字母的其中一个,编译器也不会报错。之所以又不同的标记符,这是一种约定。 在开发中很多规则都是一种约定,它能提高我们代码的可读性,方便团队见的合作开发

4. 泛型类

泛型类的声明与非泛型类几乎相同,唯一的不同在于类名的后面添加了参数声明部分

① 定义泛型类

在类名后加<>,在<>中定义泛型,<>中的内容相当于泛型的名字,可以随便写。 在泛型类中我们可以把这个泛型的名字当做一个数据类型来使用。

public class TestClass<T> {
    //...
}

② 使用泛型类

在泛型类中可以使用在类名后面定义的泛型。

public class TestClass<T> {
    public void test(T t){
       
    }
}

泛型的确定

①创建对象时确定

在创建泛型类对象的时候确定之前定义的泛型代表什么数据类型。在定义泛型类对象的时候,在类名的后加<>,在其中写一个具体的数据类型。

    public static void main(String[] args) {
        TestClass<String>  t = new TestClass();//指定了该对象的泛型T是String类型
        t.test("三更草堂");//所以test方法的参数类型应该也是String类型
    }

②定义子类时确定

在定义子类的时候可以确定泛型。具体用法如下:

public class SubClass extends TestClass<String> {
    @Override
    public void test(String s) {
        
    }
}

这样在子类SubClass中泛型就确定为String类型了。

注意:我们在定义子类时也可以选择不确定泛型,让其在创建对象的时候在确定。写法如下

public class SubClass<T> extends TestClass<T> {
    @Override
    public void test(T t) {
        super.test(t);
    }
}

5. 泛型方法

你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

① 定义泛型方法

在方法返回值类型的前面加<>,在<>中定义泛型,<>中的内容相当于泛型的名字,可以随便写。 在该泛型方法中我们可以把这个泛型的名字当做一个数据类型来使用。

    
    public static  <T> T test(T t){
        return t;
    }

语法规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前

比如说这是一个用来打印数组的泛型方法:

private static <E> void printArray(E[] inputArray)
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。

比如这个方法

private static <E,T> void printArray(E[] inputArray, T data)
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符
  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(int double char等)

② 使用泛型方法

在泛型方法中可以使用定义的泛型。并且我们一般是在参数列表中或者是返回值类型上使用到这个泛型。

    public static <T> T test(T t){
        return t;
    }

泛型的确定

调用泛型方法的时候才真正确定之前定义的泛型代表什么数据类型。在调用泛型方法的时候,程序会根据你的调用自动推导泛型的具体类型。

    public static void main(String[] args) {
        Integer test = test(1);
        String s = test("三更草堂");
    }

6. 类型通配符

① ?

我们一般可以使用?来承接所有的引用类型,搬运一个菜鸟上的例子:

public class GenericTest {
     
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();
        
        name.add("icon");
        age.add(18);
        number.add(314);
 
        getData(name);
        getData(age);
        getData(number);
       
   }
 
   public static void getData(List<?> data) {
      System.out.println("data :" + data.get(0));
   }
}

运行结果:

data :icon
data :18
data :314

② ? extends T

这是泛型上边界: 只有T对象的子类可以被传入

如果是? extends C,那么只有D和E允许被传入,否则会编译报错

image-20220308194033852

③ ? super T

这是泛型下边界: 只有T对象的父类可以被传入

如果是? super D,那么只有C和A允许被传入,否则会编译报错

image-20220308194428111

T 和 ?的区别

  • T一般作为泛型参数
  • ? 是更多是用来一个不确定的引用类型

image-20220726160300737

7. 泛型上限&泛型下限

泛型限定的概念

我们在使用确定泛型的时候可以使用任意的引用数据类型去确定。但是在某些场景下我们要求这个泛型必须是某个类的子类或者是某个类的父类。这种情况下我们就需要用到泛型上限和泛型上限来限制泛型的范围。

泛型上限

限制泛型必须是某个类或者是其子类。

格式:

  <? extends 具体的类型>

例如:

public static void test(List<? extends Person> t){
​
}
​
//等价于
    
public static <T extends Person> void test(List<T> t){
    
}

这样我们再调用test方法的时候只能存入泛型为Person或者是Person子类的List集合对象。

(因为不做这样的限定是无法使用person的一些独有的方法的,所以要加这样的限定)

泛型下限

限制泛型必须是某个类或者是其父类。

格式:

<? super 具体的类型> 

例如:

public static void test(List<? super Student> t){
​
}
​
// 等价于public static <T super Student> void test(List<T> t){
    
}

这样我们再调用test方法的时候只能存入泛型为Student或者是Student父类的List集合对象。

注意事项

  1. 泛型上限可以在定义泛型类和方法参数上使用

    public class Box<E extends Person> {
        E e;
    }
    
  2. 泛型下限主要在方法参数上使用。

8. 泛型实战

摸鱼三天!我写了一个通用的组建树TreeUtil工具