zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java泛型

2023-09-11 14:22:32 时间

1. 了解

未使用泛型的集合→集合可以装任意类型的数据,但是接收数据的时候很麻烦,因为你不知道你会收到什么类型的数据,时刻需要准备强转。

使用了泛型的集合→编译器会检测插入的数据类型,集合只能装指定类型的数据,其优势:

  • 编译期间检测类型(类型安全)
  • 减少强制转换

泛型概念:

  • JDK1.5新特性,提供编译时类型安全检测机制
  • 本质是参数化类型,即所操作的数据类型被指定为一个参数

2. 类与接口

2.1 类

定义格式:

/*
	常用的标识:
	T - Type(Java类)					V - Value(值)
	E - Element(在集合中使用)			N - Number(数值类型)
	K - Key(Key键)						? -  不确定的java类型
*/
class 类名 <泛型标识1,泛型标识2,...>{
	private 泛型标识 变量名;
	...
} 

泛型类定义:

package generics;

/**
 * 泛型类
 * @author 364.99
 * @version 1.0
 * @param <T>   泛型标识
 *           T 创建对象的时候指定具体的数据类型
 */
public class Demo1<T> {
    //T的类型由外部类使用此类对象时来指定
    private T item;

    public Demo1() {
    }

    public Demo1(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }

    @Override
    public String toString() {
        return "Demo1{" +
                "item=" + item +
                '}';
    }
}

泛型类使用格式:

类名<数据类型> 对象名 = new 类名<数据类型>();//JDK1.7之后,后一个数据类型可省略

泛型类的使用:

package generics;

public class Test1 {
    public static void main(String[] args) {
        Demo1<String> demo1 = new Demo1<>();
        demo1.setItem("张三");
        System.out.println(demo1);
    }
}

注意:

  • 泛型类在使用时,没有指定数据类型,按照Object类型接收数据
  • 泛型的数据类型只支持类类型(基本类型使用其对应的包装类实现)
  • 同一泛型类,根据不同的数据类型创建的对象都是同一个Class对象(.java→.class→只会生成一个Class类对象,详情见反射

子类

  • 子类与父类都是泛型→子类与父类泛型类型要一致
class 子类<T> extends 父类<T>
//子类的泛型标识可以有多个,但必须有一个与父类一致
class 子类<T,E,...> extends 父类<T>
  • 子类不是泛型类→父类要明确泛型的数据类型
class 子类 extends 父类<具体数据类型>

2.2 接口

定义格式:

interface 接口名 <泛型标识1,泛型标识2,...>{
	泛型标识 方法名();
	...
}

泛型接口

package generics;

/**
 * 泛型接口
 * @param <T>
 */
public interface Demo2 <T>{
    T print();
}

泛型接口的实现类

  • 实现类是泛型类,实现类和接口的泛型类型要一致
//必须保证实现类的泛型标识包含泛型类的泛型标识
public class Test2<T> implements Demo2<T>{
    @Override
    public T print() {
        return null;
    }
}
  • 实现类不是泛型类,接口要明确数据类型
//不标明→默认Object
public class Test2 implements Demo2{
    @Override
    public Object print() {
        return null;
    }
}

//标明
public class Test2 implements Demo2<String>{
    @Override
    public String print() {
        return null;
    }
}

2.3 方法

定义格式:

修饰符 <泛型标识1,泛型标识2,...> 返回值类型 方法名(参数列表){
	方法体
}

注意:

  • 只有声明了<泛型标识>的方法才是泛型方法
  • 只有声明了<T>的方法才可以在方法中使用泛型类型T
    /**
     * 单个泛型类型的泛型方法
     * @param list
     * @param <E>
     * @return
     */
    public static  <E> E getLast(LinkedList<E> list){
        return list.getLast();
    }

    /**
     * 多个泛型类型的泛型方法
     * @param t
     * @param e
     * @param k
     * @param <T>
     * @param <E>
     * @param <K>
     */
    public static  <T,E,K> void printType(T t,E e,K k){
        System.out.println(t + "\t" + t.getClass().getSimpleName());
        System.out.println(e + "\t" + e.getClass().getSimpleName());
        System.out.println(k + "\t" + k.getClass().getSimpleName());
    }

使用:

        LinkedList<String> list = new LinkedList<>();
        list.add("张三");
        list.add("李四");
        System.out.println(Demo1.getLast(list));
		
		Demo1.printType("AB",'c',4);

注意:

  • 泛型方法的泛型类型是独立于泛型类的泛型类型
  • 泛型类的成员方法(getter,setter)不能定义为static(泛型类需要在创建对象时指定类型,而静态方法是通过类名直接调用的,此时成员方法类型未被指定,会报错)

3. 类型通配符

为什么要是用类型通配符?

上案例:

定义一个泛型类

public class Box<T> {
    private T item;

    public Box() {
    }

    public Box(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

请添加图片描述
重载showItem方法依旧报错

遇到类似情况就需要引入类型通配符了

什么是类型通配符:

  • 类型通配符一般是用代替具体的类型实参
  • 类型通配符是类型实参,而不是类型形参

类型通配符的上限

要求该泛型的类型,只能是实参类型,或实参类型的子类类型

格式:

/接口<? extends 实参类型>

类型通配符的下限

要求该泛型的类型,只能是实参类型,或实参类型的父类类型

/接口<? super 实参类型>

4. 类型擦除

类型擦除:泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉

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

public class Test {
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();

        System.out.println(list1.getClass().getSimpleName());
        System.out.println(list2.getClass().getSimpleName());

    }
}

无限制类型擦除

        Box<Integer> box = new Box<>();
        //利用反射,获取Box类的字节码文件的Class类对象
        Class clazz = box.getClass();
        //获取box所有成员变量
        Field[] fields = clazz.getDeclaredFields();
        //打印所有成员变量的名称和类型
        for (Field field:fields) {
            System.out.println(field.getName() + ":" + field.getType().getSimpleName());
        }

有限制类型擦除

public class Box<T extends Number> {
        Box<Integer> box = new Box<>();
        //利用反射,获取Box类的字节码文件的Class类对象
        Class clazz = box.getClass();
        //获取box所有成员变量
        Field[] fields = clazz.getDeclaredFields();
        //打印所有成员变量的名称和类型
        for (Field field:fields) {
            System.out.println(field.getName() + ":" + field.getType().getSimpleName());
        }

下限是转换成Object

擦除方法中类型定义的参数


System.out.println(method.getName() + ":" + method.getReturnType().getSimpleName());

桥接方法

5. 泛型数组

泛型数组的创建:

  • 可以声明带泛型的数组引用,但不能直接创建带泛型的数组对象
    泛型会在编译前类型擦除,而数组会在整个编译运行阶段持有其初始类型,这两个起了冲突
        List<String>[] lists = new ArrayList[10];//注意,后面这块儿不要使用泛型

        List<String> strList = new ArrayList<>();
        strList.add("张三");
        strList.add("李四");

        lists[0] = strList;

        System.out.println(Arrays.toString(lists));
  • 可以通过java.lang.reflect.ArraynewInstance(Class<T>,int)创建T[]数组
    public static void main(String[] args) {
        List<String>[] lists = (List<String>[]) Array.newInstance(ArrayList.class,10);
        List<String> list = new ArrayList<>();

        list.add("张三");
        list.add("李四");

        lists[0] = list;

        System.out.println(Arrays.toString(lists));
    }

6. 泛型和反射

反射常用的泛型类:

  • Class
/*
	快捷键
	类名.class.var+回车
*/
Class<Test> testClass = Test.class;
  • Constructor
/*
	快捷键
	testClass.getConstructor().var
*/
Constructor<Test> constructor = testClass.getConstructor();
//利用反射创建对象
Test test = constructor.newInstance();

对比不使用泛型创建对象

        Class testClass1 = Test.class;
        Constructor constructor1 = testClass1.getConstructor();
        //只能创建Object对象,想要获取Test对象,需要强转
        Object o = constructor1.newInstance();