简介

泛型:参数化类型,把类型当做一个参数传递到类中,这样做可以增加代码的灵活性。java从1.5开始提供了泛型

泛型的作用:

  • 任意化类型和编译时类型检查:任意化类型,是指一套代码可以应用在不同的类型上,例如,一个排序算法,它即可以对int类型的数据排序,也可以对long类型的数据排序,还可以对String类型的数据排序。在没有泛型之前,如果想要实现任意化类型,需要使用Object类,但是它需要强制类型转换,而且这种转换编译器无法判断正确与否,容易造成一些未知的错误。使用泛型,所有的类型转换全部由泛型完成,并且泛型可以提供类型检查,保证编译时类型安全。使用泛型最多的地方是各种用于存储数据的容器类,通过泛型,容器类中可以存储任何类型的数据,并且保证容器中数据类型的正确。
  • 代码复用:面向对象思想通过从上而下的继承关系实现了纵向维度的代码复用,泛型编程思想通过将平行关系的类联系起来实现了横向维度的代码复用

泛型的特点:

  • 不支持基本数据类型:泛型是使用Object类型来实现的,一个泛型在没有指定具体的数据类型之前,就是一个Object类型。基本数据类型和Object类型之间没有继承关系,Object类不可以强转为基本数据类型,所以泛型不支持基本数据类型,只支持引用数据类型。对于基本数据类型,可以使用它们的包装类来代替。
  • 只在编译阶段有效:泛型只在编译阶段有效,在编译结束后,会将泛型的相关信息擦除,本质上讲,这是一种伪泛型,Java之所以如此设计,主要是为了在二进制层面上的兼容之前的代码。同一个泛型类根据不同的数据类型创建的对象,本质上同一个类型。
  • 不支持数组:不允许创建泛型数组。数组和泛型的运行机制不兼容,数组在运行时元素的类型信息是明确的,而泛型存在类型擦除,在运行时都是Object类型,这和数组的运行机制是相冲突的。

使用方式

泛型可以使用在类、接口、方法上,它们分别称作:泛型类、泛型接口、泛型方法。

泛型标识:常用的有T、E、K、V,这些标识 是编码时的一种约定,

  • T:表示一个具体的类型
  • E:表示一个元素
  • K:表示一个键类型
  • V:表示一个值类型

泛型类

使用泛型的类,可以持有任意类型的属性,这增加了类的灵活性。

声明:[public] class 类名称<泛型标识[, ...]>{ // 类体 }

实例化:类名称<泛型类型> 对象名 = new 类名称<[泛型类型]>([实参列表]);。在实例化泛型类时,需要指明泛型类中的类型参数。Java1.7之后,后面的尖括号中的具体的数据类型可以省略不写。

泛型类中使用泛型:

  • 在泛型类中,泛型不可以被实例化。因为不清楚传入的泛型实参是否可以被实例化
  • 在泛型类中,以泛型为数据类型的属性不可以是静态的,因为泛型实参是在类实例化时被传入的,静态属性的生命周期在实例化之前

案例1:一个使用泛型的容器类

  • 泛型类:
import java.util.Arrays;

public class Box<T> {
    // 使用Object作为数组的类型,因为数组和泛型的运行机制不兼容
    private Object[] elements = new Object[4];
    private int size;

    public T get(int i) {
        return (T) elements[i]; // 告警信息:Unchecked cast: 'java.lang.Object' to 'T'
    }

    public void add(T ele) {
        if (size >= elements.length) {
            // 扩容
            resize();
        }
        elements[size++] = ele;
    }

    public int size() {
        return size;
    }

    private void resize() {
        int len = elements.length;
        Object[] newElements = new Object[len + 8];
        System.arraycopy(elements, 0, newElements, 0, elements.length);
        elements = newElements;
    }

    @Override
    public String toString() {
        return "Box{" +
                "elements=" + Arrays.toString(elements) +
                '}';
    }
}
  • 使用案例:
public class BoxTest {
    // 测试Box类
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>();
        stringBox.add("aaa");
        stringBox.add("bbb");
        stringBox.add("ccc");
        stringBox.add("ddd");
        stringBox.add("eee");
        stringBox.add("fff");
        System.out.println(stringBox.get(1)); // bbb
        System.out.println("stringBox = " + stringBox); // Box{elements=[aaa, bbb, ccc, ddd, eee, fff, ...
        System.out.println("stringBox.size() = " + stringBox.size()); // 6
    }
}

案例2:泛型类中的泛型不可以和静态属性一起使用

public class Box<T> {
    // 编译时会报错:'Box.this' cannot be referenced from a static context,
    // 因为泛型是传递给实例的,需要通过一个实例来访问,在静态上下文中无法访问
    public static T boxEle; 
}

案例3:泛型类中的泛型不可以被实例化

public class Box<T> {
    // 编译时报错:Type parameter 'T' cannot be instantiated directly,泛型参数T不可以被直接实例化,
    // 因为不清楚Box实例化时传入的泛型实参是否可以被实例化
    private T e = new T();
    private T[] elements = new T[4];
}

泛型类的继承

继承规则:

  • 如果子类也是泛型类,子类和父类的泛型类型要一致;
  • 如果子类不是泛型类,子类在继承父类时,要指定父类的泛型实参

案例1:子类是泛型类

public class SpecialBox<E, F, G> extends Box<E> { } // 子类和父类的泛型类型要一致,注意,子类的泛型名称,T、E之类的,可以和父类不一致,但是子类中的声明必须一致,例如,在当前案例中,泛型E将会被传递给父类

案例2:子类不是泛型类

public class SpecificBox extends Box<Integer> { }  // 子类不是泛型类,继承时需要明确父类的泛型类型

泛型接口

声明:[public] interface 接口名<泛型标识 [, ...]>{ // interface body }

泛型接口的实现和泛型类的继承是一样的。

案例:以java中的List接口为例,写两个实现类,一个是泛型类、一个不是泛型类

// 1. List接口的声明
public interface List<E> extends Collection<E> { }

// 2. 实现类是泛型类,实现类中,实现类和接口的泛型类型要一致
public class MyList2<T> implements List<T> {
    // 实现接口中的方法时,使用了泛型
    @Override
    public List<T> subList(int fromIndex, int toIndex) {
        return Collections.emptyList();
    }
}

// 3. 实现类不是泛型类,接口的泛型要明确数据类型
public class MyList implements List<Integer> {
    // 实现接口中的方法时,使用具体类
    @Override
    public ListIterator<Integer> listIterator(int index) {
        return null;
    }
}

泛型方法

方法支持泛型。在调用泛型方法的时候指明泛型的具体类型,它可以不依附于泛型类而存在,方法可以是成员方法也可以是静态方法。

声明:[访问权限修饰符] [static] [final] <泛型参数列表> 返回值类型 方法名(形参列表)

调用方式:和调用普通方法一样,通过实参的类型来指定泛型的类型。泛型类是在实例化类的时候指明泛型的具体类型。泛型方法是在调用方法的时候指明泛型的具体类型

案例:静态泛型方法和成员泛型方法

import java.util.Random;

public class GenericTest {
    public static void main(String[] args) {
        // 测试静态泛型方法
        genericMethod(new Object(), "aa", new Random());
      
        // 测试成员泛型方法
        new GenericTest().genericMethod2(new Object(), "bb", new Random());
    }

    // 静态泛型方法
    public static <T, E, K> void genericMethod(T t, E e, K k) {
        System.out.println("t = " + t);
        System.out.println("e = " + e);
        System.out.println("k = " + k);
    }

    // 成员泛型方法
    public <T, E, K> void genericMethod2(T t, E e, K k) {
        System.out.println("t = " + t);
        System.out.println("e = " + e);
        System.out.println("k = " + k);
    }
}

类型通配符

通配符:用一个问号表示,表示任意类型,当需要指定泛型实参但是又不知道具体是什么实参的时候,就使用通配符。无界通配符和Object不一样,无界通配符表示任意类型,Object是一个具体的类型,只能说无界通配符在运行时都是按照Object来处理的,但是普通的泛型也是这样。

案例:

import java.util.ArrayList;
import java.util.List;

public class GenericTest3 {
    // 测试
    public static void main(String[] args) throws ClassNotFoundException {
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        printList(list);  // list.toString() = [aaa, bbb, ccc]

        List<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        list2.add(3);
        printList(list2);  // list.toString() = [1, 2, 3]
    }

    // 无界通配符,接收任意类型的list,如List<String>、List<Integer>
    public static void printList(List<?> list) {
        System.out.println("list.toString() = " + list.toString());  
    }
}

案例2:

// 这里使用一个无界通配符,因为此时编译器并不知道Class的具体类型
Class<?> strClass = Class.forName("java.lang.String");
System.out.println("strClass.getSimpleName() = " + strClass.getSimpleName());  // String

// Class的泛型实参是String,这在编译时就确定了
Class<String> stringClass = String.class;
System.out.println("stringClass.getSimpleName() = " + stringClass.getSimpleName());  // String

泛型的上界和下界

上界和下界:

  • 上界:? extends 实参类型,要求该泛型的类型,只能是实参类型或实参类型的子类。用于读取数据时,确保数据的类型范围。这种通配符允许用户从泛型集合中读取数据,但不允许用户向集合中添加数据,因为编译器无法确定具体的类型。

  • 下界:? super 实参类型,要求该泛型的类型,只能是实参类型或实参类型的父类。用于写入数据时,确保容器的类型范围。

泛型的上界和下界更加细致的限定了类型

案例1:上界

public static void processList(List<? extends Son> list) {
    // 读取元素
    Son son = list.get(0);
    
    // 添加元素
    // 错误:添加元素时报类型不匹配,要求的元素是 capture of ? extends Son,实际提供的元素Son。
    // 泛型的上界,? extends Son,是一个不确定的类型,只知道它是Son的子类,编译器无法确定list的具体类型,
    // 因此不允许用户向其中添加 Son 类型的对象。
    // list.add(new Son());
}

案例2:下界

// 下界
public static void processList2(List<? super Son> list) {
    Object o = list.get(0); // 正确:可以从集合中读取数组,但是无法获取具体的数据类型
    list.add(new Son());    // 正确:可以向集合中添加数据,只要它是Son类即可
    // list.add(new Father()); // 错误,编译器只允许添加Son类型,不能添加Son的父类,因为集合中如果有两种类型的数据,会出现类型安全问题
}

为什么上界不允许向集合中添加数据,但是下界却可以,例如,

  • 上界:? extends Son,不可以向集合中添加数据,
  • 下界:? super Son,可以向集合中添加Son类型的数据。

上界可以确保元素是Son或它的子类,编译器不清楚具体是哪个子类,所以无法添加元素。
下界可以确保元素一定是Son类型或它的父类,添加Son类型的元素,不会出现类型安全问题,但是添加Son的父类,如果集合是Son类型,就会出现类型安全问题,所以编译器规定只可以添加Son类型
总之,核心原因是,编译器规定集合中只能存储一种类型的元素。

案例3:泛型的上界,正确的使用方式

import java.util.ArrayList;
import java.util.List;

public class GenericTest4 {
    // 测试
    public static void main(String[] args) {
        List<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        list2.add(3);
        printNumbers(list2);   // 1 2 3 
    }

    // 泛型的上界,此时这里只能处理Number类型和其子类,集合中只能读取,不能写入
    public static void printNumbers(List<? extends Number> list) {
        for (Number number : list) {
            System.out.print(number);
            System.out.print(' ');
        }
    }
}

案例4:泛型的下界,此时集合可以被写入

import java.util.ArrayList;
import java.util.List;

public class GenericTest5 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        addNumbers(list);
        System.out.println("list = " + list); // [1, 2, 3]

        List<Number> list2 = new ArrayList<>();
        addNumbers(list2);
        System.out.println("list2 = " + list2); // [1, 2, 3]

        List<Object> list3 = new ArrayList<>();
        addNumbers(list3);
        System.out.println("list3 = " + list3); // [1, 2, 3]
    }

    // 泛型的下界,此时,方法可以接收Integer和它的父类作为泛型实参
    public static void addNumbers(List<? super Integer> list) {
        list.add(1);  // 只能向集合中写入Integer类型的数据
        list.add(2);
        list.add(3);
        // list.add(new Object());  // 这里编译时会报错,需要 capture of ? super Integer,提供了Object
    }
}

案例5:上界和下界一起使用,实现集合复制功能

import java.util.ArrayList;
import java.util.List;

public class GenericTest6 {
    public static void main(String[] args) {
        List<Integer> sourceList = new ArrayList<>();
        sourceList.add(1);
        sourceList.add(2);
        sourceList.add(3);

        List<Object> targetList = new ArrayList<>();
        copyList(sourceList, targetList);
        System.out.println("targetList = " + targetList);  // [1, 2, 3]
    }

    // 集合复制,原集合的泛型可以是指定类型和它的子类,目标集合的泛型是指定类型和它的父类
    public static <T> void copyList(List<? extends T> sourceList, List<? super T> targetList) {
        for (T ele : sourceList) {
            targetList.add(ele);
        }
    }
}

限定多重边界

使用 & 符合可以对泛型限定多重边界,如 <T extends E1 & E2>,只支持限定上界,不支持限定下界,

为什么不支持限定多重下界:因为这会违反单继承的原则。例如, ? super Integer & String 中,不会有某个类可以同时继承Integer和String,因为这违反了单继承原则。

案例:数字比较

import java.util.ArrayList;
import java.util.List;

public class GenericTest7 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(4);
        list.add(3);

        Integer i = maxNum(list.toArray(new Integer[0]));
        System.out.println("i = " + i); // 4
    }

    // 限定多重上界
    public static <T extends Number & Comparable<T>> T maxNum(T ... arr) {
        T max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (max.compareTo(arr[i]) < 0) {
                max = arr[i];
            }
        }
        return max;
    }
}

泛型的底层机制:泛型擦除

泛型信息只存在于代码编译阶段,在运行之前,与泛型相关的信息将会被擦除

类型擦除的分类:

  • 无限制类型擦除:使用Object类型代替泛型标识符
  • 有限制类型擦除:泛型如果指定上限,泛型标识被替换为上限类型

泛型的使用

泛型不能和数组一起使用

数组是Java中的原生数据类型,它在运行时依然保留了元素的类型信息,而泛型信息在运行之前会被擦除,这和数组的特点是冲突的,所以泛型和数组不可以一起使用。

案例:不可创建泛型数组,如果一个类带有泛型信息,以它为数据类型的数组该如何创建?

// 错误的做法:下面这行代码会报错:Generic array creation not allowed,不允许创建泛型数组
// 因为不允许在创建数组的同时声明泛型类的实参类型
// Class<String>[] sClassARR = new Class<String>[10];

// 正确的做法:创建数组时不带泛型信息,然后进行类型转换,然后阻止编译器抛异常
@SuppressWarnings("unchecked")
Class<String>[] sClassArr = (Class<String>[]) new Class[10];

// 另一种写法,使用泛型通配符,Java允许在数组创建时使用通配符
public static void main(String[] args) {
    Node<?, ?>[] nodes = new Node<?, ?>[10];
}
private static class Node<K, V> { }

泛型不能和静态属性一起使用

因为泛型实参是在类实例化时被指定的,静态属性的生命周期在类实例化之前,所以静态属性无法获取到泛型实参,也就无法使用泛型了

泛型和内部类

成员内部类会继承外部类的泛型信息,静态内部类则不会,因为成员内部类依赖外部类的实例,但是静态内部类不依赖外部类的实例。

案例:成员内部类,如果继承了外部类的泛型信息,在外部类中,不允许创建内部类类型的数组

public class HashTable<K, V> {
    private Node[] table;

    public HashTable() {
        // 这行代码会报错,Generic array creation not allowed,不允许创建泛型数组,因为内部类带有泛型信息,
        // 而且泛型信息是继承自外部类的,成员内部类会受外部类的影响
        // table = new Node[10];
    }

    // 继承了外部类中的泛型信息
    private class Node {
        private K key;
        private V value;
        private int hash;
        private Node next;

        public Node() { }

        public Node(K key, V value, int hash, Node next) {
            this.key = key;
            this.value = value;
            this.hash = hash;
            this.next = next;
        }
    }
}

案例:静态内部类

public class HashTable2<K, V> {
    private Node<K, V>[] table;
    
    @SuppressWarnings("unchecked")
    public HashTable2() {
        // 正确的做法,静态内部类带有泛型信息,但是泛型信息是自己声明的,实例化数组时不加泛型信息,随后进行类型强转
        table = (Node<K, V>[]) new Node[10];
    }

    // 没有继承外部类的泛型信息
    private static class Node<K, V> {
        private K key;
        private V value;
        private int hash;
        private Node next;

        public Node() { }

        public Node(K key, V value, int hash, Node<K, V> next) {
            this.key = key;
            this.value = value;
            this.hash = hash;
            this.next = next;
        }
    }
}

在泛型类中无法访问一个泛型的类对象

案例:

public class GenericClass<T> {
    public void doSomething() {
        // 试图获取泛型参数的类对象
        // Class<T> clazz = T.class; // 编译错误: 泛型类型'T'的类型未知。
    }
}

结果:编译器会报错,因为 T 的实际类型在运行时是未知的。当编译器看到 T.class 时,由于类型擦除,它实际上看不到任何具体类型,从而无法解析 T。

原因:在 Java 中,泛型类或方法不能直接访问其泛型参数的类对象(即 Class<T>)。这是因为 Java 泛型在编译时会进行类型擦除,即在字节码中不保留任何关于泛型类型参数的信息。这使得在运行时无法知道泛型参数的实际类型。

类型擦除的目的是为了与非泛型代码保持兼容。例如,对于 List<T>,编译器会将泛型参数 T 擦除为 Object,所以在运行时,所有的 List<String>、List<Integer> 等都会被视为 List<Object>。

获取类所实现的所有接口的泛型信息

案例:类实现了一个带泛型信息的接口,获取接口上的泛型实参

// 接口1:带泛型的接口
public interface MyInf<T> { }  // T 是泛型形参
// 接口2:不带泛型的接口
public interface MyInf2 { }
// 实现类,注意,接口上声明的 T 是泛型形参,这里实现接口后指定的是泛型实参
public class MyObj implements MyInf<List<String>>, MyInf2 {  }

获取类所实现的所有接口的泛型信息

public static void main(String[] args) {
    // 1. 获取类对象
    Class<MyObj> myObjClass = MyObj.class;

    // 2. 获取类实现的所有接口
    Type[] genericInterfaces = myObjClass.getGenericInterfaces();
    for (Type genericInterface : genericInterfaces) {
        // 3. 判断接口有没有带泛型信息
        if (genericInterface instanceof ParameterizedType) {
            // 4. 获取接口的原始类型和泛型实参
            ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
            Type rawType = parameterizedType.getRawType();  // 去掉泛型信息后的原始类型
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); // 接口的泛型实参
            System.out.println("ParameterizedType = " + rawType + " " + Arrays.toString(actualTypeArguments));
        } else {
            System.out.println("Type = " + genericInterface);
        }
    }
}

结果:

ParameterizedType = interface org.example.generic.MyInf [java.util.List<java.lang.String>]
Type = interface org.wyj.generic.MyInf2

获取父类的所有泛型信息

案例:类继承了一个泛型类,并且类本身不是一个泛型类,获取父类上的泛型实参

// 父类,同时也是一个泛型类
public class AbstractListener<T> { }

// 子类,指定泛型实参
public class MyListener extends AbstractListener<List<MyEvent>> { }

获取父类上的泛型实参

public static void main(String[] args) {
    Class<MyListener> aClass = MyListener.class;
    
    Type genericSuperclass = aClass.getGenericSuperclass();
    if (genericSuperclass instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
        // 获取泛型实参
        Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0];
        System.out.println("actualTypeArgument = " + actualTypeArgument); // java.util.List<org.example.event.MyEvent>
    }
}

结果:

java.util.List<org.example.event.MyEvent>

泛型相关的源码

这一部分主要是反射相关的,因为只有在通过反射,获取一个类的泛型信息时,才会用到。

反射:https://blog.csdn.net/qq_44818304/article/details/146989208?spm=1001.2014.3001.5502

泛型相关的源码:

  • Type:所有类型的顶层接口,包括原始类型、带泛型的类型、数组类型等
  • Class:代表一个类的类对象,间接实现了Type接口
  • TypeVariable:代表泛型形参,如果java检测到解析的泛型是一个泛型形参,例如,public class List,此时T就是一个泛型形参,java会使用TypeVariable来封装解析好的结果
  • ParameterizedType:代表泛型实参,如果java检测到解析的泛型是一个泛型实参,例如,List,此时String就是一个泛型实参,java会使用ParameterizedType来封装解析好的结果

实战案例

案例1:解析json字符串

在解析json字符串的时候,大量用到了泛型相关的知识。

json解析工具对比:https://blog.csdn.net/qq_44818304/article/details/148187709?spm=1001.2014.3001.5502。

解析json的工具类:这里使用fastjson框架

public class FastJsonUtil {
    // 基本使用,通过JSON对象来使用fastjson
    public static String toJson(Object obj) {
        return JSON.toJSONString(obj);
    }

    public static <T> T toObj(String jsonStr, Class<T> clazz) {
        return JSON.parseObject(jsonStr, clazz);
    }

    public static <T> T toObj(String jsonStr, TypeReference<T> type) {
        return JSON.parseObject(jsonStr, type);
    }

    public static <T> List<T> toList(String jsonStr, Class<T> clazz) {
        return JSON.parseArray(jsonStr, clazz);
    }
}

在这个案例中,TypeReference是框架独有的API,其它json框架也提供了类似的API,它封装了一个Type类型,可以表示像 List<String> 这样的泛型类型。

工具类的使用:

@Test
public void test2() {
    String userJson = "{\"name\": \"zs\"}";
    User obj = FastJsonUtil.toObj(userJson, User.class);
    System.out.println("obj = " + obj);
}

@Test
public void test3() {
    String userListJson = "[{\"name\": \"zs\"}]";
    List<User> userList = FastJsonUtil.toObj(userListJson, new TypeReference<List<User>>() {});
    System.out.println("obj = " + userList);
}

这里,比较复杂的是test3,它依赖框架内部的能力,但基本原理是,要想获取 List<User> 这个信息,把它作为解析后的数据类型,直接传类对象是不行的,只有通过在子类中获取父类上的泛型信息的方式,所以这里传一个TypeReference的匿名内部类。具体可以参考反射一节。

案例2

子类获取父类上的泛型信息。

需求:一个简单的监听器框架。监听器接收到的数据是json字符串,每个监听器都需要把这些字符串转换为对应的类型,现在抽取出一个基础监听器,由它来进行类型转换,然后每个具体的监听器处理自己对应的数据即可。

第一步:基础监听器

import com.alibaba.fastjson2.JSON;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public abstract class BaseListener<T> {

    private Type type;

    public BaseListener() {
        // 子类实例化时,获取传递给父类的泛型实参
        Class<? extends BaseListener> aClass = getClass();
        Type genericSuperclass = aClass.getGenericSuperclass();
        if (genericSuperclass instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
            this.type = parameterizedType.getActualTypeArguments()[0];
        }
        if (type == null) {
            throw new RuntimeException(this.getClass().getName() + "解析泛型实参失败");
        }
    }

    public void process(String json) {
        @SuppressWarnings("unchecked")
        T t = (T) JSON.parseObject(json, type);
        processMessage(t);
    }


    // 处理消息
    public abstract void processMessage(T message);
}

第二步:具体的监听器

// 监听器1
import java.util.List;

// 指定监听器要处理的数据类型,也就是它传递给父类的泛型实参
public class DemoListener extends BaseListener<List<String>> {
    @Override
    public void processMessage(List<String> message) {
        System.out.println("message = " + message);
    }
}

// 监听器2
public class Demo2Listener extends BaseListener<Person> {
    @Override
    public void processMessage(Person message) {
        System.out.println("message = " + message);
    }
}

它只需要在声明时指定自己要处理的数据的类型,然后重写父类的proessMessage方法即可,由父类来完成json数据解析。

测试:

public class DemoListenerTest {
    // 测试监听器1
    @Test
    public void test1() {
        DemoListener demoListener = new DemoListener();
        demoListener.process("[\"aaa\", \"bbb\"]");  // message = [aaa, bbb]
    }

    // 测试监听器2
    @Test
    public void test2() {
        Person person = new Person(1L, "aaa", 20);
        String jsonString = JSON.toJSONString(person);

        Demo2Listener demo2Listener = new Demo2Listener();
        demo2Listener.process(jsonString);  // message = Person(id=1, name=aaa, age=20)
    }
}

总结:这里的原理是,子类在声明时,指定传递给父类的泛型实参,同时在父类的构造方法中,获取这个泛型实参,因为抽象类不可以实例化,所以是通过子类来调用父类的构造方法。这个泛型实参就是子类要处理的数据类型。通过这种方式,把json字符串解析这一步提取到父类中,子类只需要处理数据即可。

案例3

需求: 通过http服务,调用第三方API,它返回的响应数据是一个json字符串,解析后类似于 Response<DataResponse<T>> 这个样子,相当于把响应数据包了两层,第一层是应用层的信息,第二层是业务层的信息。我要写一个公共方法,解析这个字符串,用户只需要传入 T 这个泛型信息,同时只需要最内层的业务数据,其他逻辑都在公共方法中完成。

方案1:

1、json数据对应的实体类

@Data
public class Response<T> {
    private String code; // 200表示成功
    private String msg;  // 结果描述
    private T data;  // 结果数据
}
@Data
public class DataResponse<T> {
    private Integer code;  // 0表示成功,其他均为失败
    private String message;
    private T data;  // 数据
}

2、解析json数据的方法

public class JsonTest2 {

   // 解析json数据的方法:接收一个Type对象作为最内层data字段的泛型类型,使用fastjson2提供的API,组装泛型信息。java自带的API不支持这种泛型参数的传递
    private <T> T parseResponse(String str, Type type) {
        Type dataResponseType = new ParameterizedTypeImpl(
                new Type[]{type},    // 泛型类型
                null,                 // 用于有内部类的情况
                DataResponse.class);  // 原始类型,例如List<User>,User.class是参数1,List.class是参数3

        ParameterizedTypeImpl responseType = new ParameterizedTypeImpl(new Type[]{dataResponseType}, null, Response.class);

        Response<DataResponse<T>> response = JSON.parseObject(str, responseType);
        return response.getData().getData();
    }

private static final String JSON_STR = "{\n" +
            "    \"code\": \"200\",\n" +
            "    \"success\": true,\n" +
            "    \"msg\": \"操作成功\",\n" +
            "    \"data\": {\n" +
            "        \"code\": 0,\n" +
            "        \"success\": true,\n" +
            "        \"message\": \"成功\",\n" +
            "        \"data\": [{\n" +
            "            \"name\": \"11\",\n" +
            "            \"age\": 22\n" +
            "        }]\n" +
            "    }\n" +
            "}";

    @Test
    public void test5() {
        List<User> userList = parseResponse(JSON_STR, new TypeReference<List<User>>() {}.getType());
        System.out.println("user = " + userList);
    }
}

方案2:

@Data
public class Response<T> {
    private String code;
    private String msg;
    private DataResponse<T> data;
}
@Data
public class DataResponse<T> {
    private Integer code;
    private Boolean success;
    private String message;
    private T data;
}

解析json数据的方法:

public class JSONTest3 {
   // 解析json数据的方法:
    private <T> T parseResponse(String str, Type type) {
        Type dataResponseType = new ParameterizedTypeImpl(
                new Type[]{type}, 
                null, 
                Response.class);

        Response<DataResponse<T>> response = JSON.parseObject(str, dataResponseType);
        return (T) response.getData().getData();
    }


    @Test
    public void test5() {
        List<User> userList = parseResponse(JSON_STR, new TypeReference<List<User>>() {}.getType());
        System.out.println("user = " + userList);
    }
}

和方案1整体差别不大,只是把DataResponse写在了Response中,可以减少一层泛型参数的组装

更多推荐