当前位置: 首页 > news >正文

java 泛型

今天来聊聊java中的泛型,泛型主要对类型进行限定。

下面看一段代码

代码

/**
 * 图形
 */
interface Shape {
    void draw();
}

/**
 * 圆形
 */
class Circle implements Shape {
    @Override
    public void draw() {
    }
}

/**
 * 正方形
 */
class Square implements Shape {
    @Override
    public void draw() {

    }
}

/**
 * 颜色
 */
class Color{}

没有指定泛型,默认list是可以添加任意Object类型。这就存在一定的风险,取数据的时候对类型进行转换需要避免类型不一致的问题。

public class Test {
    public static void main(String[] args) {
        //没有指定泛型
        List list = new ArrayList<>();
        list.add(new Circle());
        list.add(new Square());
        //需要类型强制转换
        Shape shape = (Shape) list.get(0);
    }
}

泛型的用法是添加<>里边对类型进行限定,下边的例子ArrayList后边的<>无需添加类型,因为编译器会做类型推断。

public class Test {
    public static void main(String[] args) {
        //指定泛型
        List<Shape> list = new ArrayList<>();
        list.add(new Circle());
        list.add(new Square());
        //编译失败
        list.add(new Color());
        //无需类型强制转换
        Shape shape =  list.get(0);
    }
}

类型擦除

泛型里边有一个很重要的概念就是类型擦除,泛型的类型信息只存留在编译阶段,运行的时候是没有我们所设定的泛型信息的。

使用 javap -c Test查看编译后的字节码信息

示例1
public class Test {
    public static void main(String[] args) {
        //没有指定泛型
        List list = new ArrayList<>();
        list.add(new Circle());
        list.add(new Square());
        //需要类型强制转换
        Shape shape = (Shape) list.get(0);
    }
}

上面代码编译完成后,查看字节码内容如下:

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: new           #4                  // class Circle
      12: dup
      13: invokespecial #5                  // Method Circle."<init>":()V
      16: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      21: pop
      22: aload_1
      23: new           #7                  // class Square
      26: dup
      27: invokespecial #8                  // Method Square."<init>":()V
      30: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      35: pop
      36: aload_1
      37: iconst_0
      38: invokeinterface #9,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      43: checkcast     #10                 // class Shape
      46: astore_2
      47: return
}
示例2
public class Test {
    public static void main(String[] args) {
        //指定泛型
        List<Shape> list = new ArrayList<>();
        list.add(new Circle());
        list.add(new Square());
        //无需类型强制转换
        Shape shape =  list.get(0);
    }
}

上面代码编译完成后,查看字节码内容如下:

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: new           #4                  // class Circle
      12: dup
      13: invokespecial #5                  // Method Circle."<init>":()V
      16: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      21: pop
      22: aload_1
      23: new           #7                  // class Square
      26: dup
      27: invokespecial #8                  // Method Square."<init>":()V
      30: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      35: pop
      36: aload_1
      37: iconst_0
      38: invokeinterface #9,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      43: checkcast     #10                 // class Shape
      46: astore_2
      47: return
}

上面的两个示例,字节码内容完全一样。特别主要 38行,不管泛型指定什么类型,最终都会按照 Object 类型来处理。

既然运行时,泛型所指定的类型信息已经被擦除,那运行时怎样实例信息呢?

1.可以在构造函数传入Class信息

2.利用反射

//使用spring 提供的现成方法来获取第一个泛型值Class信息
ResolvableType resolvableType = ResolvableType.forClass(this.getClass());
Class<?> aClass =  resolvableType.getSuperType().getGenerics()[0].resolve();

通配符

<?> 表示无界通配符,可以支持所有类型,和原始类型有点相似。既然这样,那和不声明泛型又有什么区别呢?

声明无界通配符,表示接收一个泛型类型。

通配符?后只允许出现一个边界。

通配符只允许出现在引用中(普通变量引用、形参),一般是用作<? extends 具体类型>或者<? super 具体类型>。相对地,比如通配符不允许出现在泛型定义中(泛型类、泛型接口、泛型方法的< >里),class one<? extends Integer> {}这样是不允许的,类定义时继承泛型类时的< >里也不可以出现。在泛型类或泛型方法的{ }里还有泛型方法的形参上,配合占位符,甚至可以使用? extends T或者? super T这种形式来用作引用。

new泛型类的时候也不可以使用通配符,比如new ArrayList<?>()。泛型方法的显式类型说明也不可以使用通配符。

通配符上界

<? extends 类型> 上界通配符声明方式,表示接收的类型只能是指定类型自身及子类。俗称协变,用在读数据场景。

//声明接收类型只能是Shape 及其子类
class Function<T extends Shape> {

}
public class Test {
    public static void main(String[] args) {
        Function function = new Function<Shape>();
        function = new Function<Circle>();
        //编译异常
        function = new Function<Color>();
    }
}

List<?> list = new ArrayList<>();
//编译异常
list.add(1);

List<? extends Number> list2 = new ArrayList<>();
//编译异常
list2.add(1);

上面这种写法编译错误,因为指定了通配符编译器并不知道确切类型是什么,所以传入任何类型都有问题。

通配符下界

<? super 类型> 下界通配符声明方式,表示接收的类型只能是指定类型自身及父类。俗称逆变,用在写数据场景。

class Function {
    public void get(List<? super Circle> list){
        //添加元素类型是自身及其子类
        list.add(new Circle());
    }
}
public class Test {
    public static  void main(String[] args) {
        Function function = new Function();
        function.get(new ArrayList<Shape>());
    }
}

参考:

1.《java编程思想》

2. <https://blog.csdn.net/anlian523/article/details/100865538>

相关文章:

  • 超低延时4K级可定制化专业视觉计算平台
  • 会多门编程语言的你,最推荐哪3-5门语言?
  • 预测足球世界杯比赛
  • C语言刷题(一)
  • 让学前端不再害怕英语单词(三)
  • SpringCloud 组件Gateway服务网关【断言工厂过滤器工厂】
  • 使用synchronized 加锁你加对了么?
  • 【图像处理】基于图像聚类的无监督图像排序问题(Matlab代码实现)
  • 本周总结(11.21-11.27)
  • 【路径规划】(2) A* 算法求解最短路,附python完整代码
  • 单片机和ARM A的区别
  • 《丞相好梦中杀人,我喜梦中听课》(1)密码学入门
  • Kafka系列之:详细介绍部署Kafka Connect分布式集群
  • 兆易创新GD32 (四)FreeRTOS 移植 与 CMSIS OS2
  • 一键编译+执行c语言小Demo
  • 【网络编程】第一章 网络基础(协议+OSI+TCPIP+网络传输的流程+IP地址+MAC地址)
  • 第九章 堆排序与TOPK问题
  • 让学前端不再害怕英语单词(一)
  • CSDN编程竞赛 ——— 第十期
  • ssh外网访问内网服务器