Java中泛型的使用

泛型是 JDK1.5 中引入的一个新特性,其本质是参数化类型,也就是说在泛型的使用过程中可以将操作的类型指定为一个参数,这种参数化行为可以发生在类、接口和方法中,分别称为泛型类泛型接口泛型方法。

为什么需要有泛型

拿 List 接口来说,在创建对应的 List 时可以指定对应的创建的集合类型,比如创建 Long 类型 List<Long> list = new ArrayList<>();,或者 String 类型 List<String> list = new ArrayList<>();但 在没有泛型之前,需要为不同的类型编写不同的 List 实现,所以泛型的出现减少了重复编码,使得编程变得更加的灵活。

泛型的使用方式

泛型的使用氛围三种,分别是泛型类,泛型接口和泛型方法

泛型类

泛型类的声明和非泛型类的声明不同之处在于,泛型类的声明需要在类型后添加类型声明部分,类型参数声明部门可以是一个或者多个类型参数,多个类型参数之前用逗号分隔,如 <T><T,E>;使用一个或多个类型作为其成员变量、方法参数或者返回值

package com.geekyous.demo.generics;

public class GenericsDemo<T> {

    /**
     * 声明参数类型变量
     */
    private T t;

    public GenericsDemo(T t) {
        this.t = t;
    }

    /**
     * 方法参数为参数类型的方法
     *
     * @param content 输出内容
     */
    public void print(T content) {
        System.out.println(content);
    }

    /**
     * 返回值为参数类型的方法
     *
     * @return T
     */
    public T getValue() {
        return this.t;
    }

    public static void main(String[] args) {
        GenericsDemo<String> stringGenerics = new GenericsDemo<>("this is string");
        System.out.println(stringGenerics.getValue());
        stringGenerics.print("hello word");

        GenericsDemo<Integer> integerGenerics = new GenericsDemo<>(123);
        System.out.println(integerGenerics.getValue());
        integerGenerics.print(456);

    }
}

泛型接口

泛型接口的使用和泛型类的使用相似,在泛型接口的声明中在接口名称后使用 <T>添加类型参数,然后在接口方法中使用该类型参数

package com.geekyous.demo.generics;
/**
 * 泛型接口
 *
 * @param <T>
 */
public interface IGenerics<T> {
    void print(T content);

    T get();
}

package com.geekyous.demo.generics;

/**
 * 泛型接口实现
 *
 * @param <T>
 */
public class IGenericsImpl<T> implements IGenerics<T> {

    private T t;

    public IGenericsImpl() {
        this.t = t;
    }

    @Override
    public void print(T content) {
        System.out.println(content);
    }

    @Override
    public T get() {
        return this.t;
    }
}

泛型方法

泛型方法的使用是在方法的返回值之前添加类型参数声明

    /**
     * 定义一个泛型方法
     *
     * @param t   形参
     * @param <T> 类型参数
     */
    public <T> void print(T t) {
        // do somethig
    }

类型参数的命名

类型参数的命名可以为满足 java 命名规范的字符,但通常使用大写字母表示,常见的命名有:

  • T :type 的首字母,一般表示非容器中的元素的类型参数
  • E:Element 的首字母,一般表示容器中元素的类型参数
  • K V:分别是 Key 和 value 的首字母,一般用于表示键值对中键和值的类型参数
  • N :Number 的首字母,一般表示数字类型参数

泛型通配符

泛型通配符是泛型中的一种特殊语法,它用‘?’表示, 泛型允许创建类型参数,是的类、接口和方法可以处理相同逻辑的业务,而不需要编写重复的代码,简化了代码;而泛型通配符用在某些场景下不关心具体的泛型类型,只关心传入的参数和返回的参数范围,通配符是类型参数更加灵活使用的一种体现。

通配符使用

通配符通常用于方法参数中,当方法的入参中有泛型类或者泛型接口时,当我们无法确定具体的泛类参数类型时,可以使用通配符‘?’表示接收任意参数;通配符的使用方法有三种:

  1. 无界通配符

void add(GenericsDemo<?> e){},比如 add 方法作用添加一个 GenericsDemo 实例,此时无论是GenericsDemo 还是 GenericsDemo的实力都能正常添加。

  1. 上界限定通配符

上界限定通配符用 extends 关键字来限制传入的类型参数必须是相关类型的子类或者其本身,或者限定类型参数是某个接口的实现,例如void add(GenericsDemo<? extends Number> e){},限定传入的类型参数必须是 Number 的子类或者 Number 本身,此时无论是GenericsDemo 还是 GenericsDemo的实力都能正常添加,但无法添加GenericsDemo;注意这里 extends 既表示类的继承也表示接口的实现。如果有多重限定,可以使用‘&’,如 T 既继承 Number 又要实现 Runnable 接口,可以这么表示<T extends Runnable & Number>,但是不能写成 <T extends Number & Runnable>,类是要在接口之前的。

  1. 下届限定通配符

上界限定通配符用 super 关键字来限制传入的类型参数必须是相关类型的父类或者其本身,例如void add(GenericsDemo<? super Number> e){},限定传入的类型参数必须是 Number 的父类或者 Number 本身,此时无论是GenericsDemo 还是 GenericsDemo的实力都能无法添加,但GenericsDemo可以添加。

类型擦除

实际上泛型本质上是一种语法糖,在编译时编译器会进行泛型做类型检查,在编译成字节码后,泛型会被替换成相应的上界限定类型,如会替换成 Object,会替换成 Number,这种独特的机制称为类型擦除。如下编译后的 Anami.class 字节码文件中,成员变量、方法参数和返回值都是 Object 类型的了。

package com.geekyous.demo.generics;

public class Animal<T> {

    private T t;

    void print(T t) {
        System.out.println(t);
    }

    T get() {
        return t;
    }

}

public class com.geekyous.demo.generics.Animal<T> {
  public com.geekyous.demo.generics.Animal();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void print(T);
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_1
       4: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
       7: return

  T get();
    Code:
       0: aload_0
       1: getfield      #19                 // Field t:Ljava/lang/Object;
       4: areturn
}

类型擦除的时期

综上,类型擦除的时期为编译期

类型擦除的意义

类型擦除的意义是为了向后兼容,也就是兼容不支持泛型的 JDK 版本。

类型擦除带来的限制

  1. 泛型只能是引用类型

正是由于有类型擦除的特性存在,所以 java 中的泛型只能支持引用类型,因为基本类型并不继承自 Object 类,所以无法进行类型擦除

  1. 不能使用 new T()

因为编译成字节码后,类型已经被擦除,因此在运行时 JVM 并不知道真正的 T 代表的是什么类型,也不能确定 T 中是否存在无参的构造方法,所以也就无法用 new T()来创建 T 对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值