前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 泛型(上)

Java 泛型(上)

作者头像
帅飞
发布2019-01-28 16:11:37
4430
发布2019-01-28 16:11:37
举报
文章被收录于专栏:Java知其所以然

为什么需要

场景

开发一个能够存储各种类型对象(比如:String 对象、Integer 对象等)的容器(容器就是存储数据的,可以是对象,可以是数组等等)。

解决方案

在 JDK 1.5 之前

在 JDK 1.5 之前是没有泛型的,最好的办法是开发一个能够存储和检索 Object 类型本身的容器,然后再将该对象用于各种类型时进行类型转换。

代码语言:javascript
复制
  public class ObjectContainer {
      public Object obj;
  }

虽然这个容器会达到预期效果,但就我们的目的而言,它并不是最合适的解决方案。它不是类型安全的(Java 的编译器对于类型转换的错误是检测不到的,在运行时执行到 checkcast这个字节码指令时,如果类型转换错误才会抛出 ClassCastException ),并且要求在检索封装对象时使用显式类型转换(向下转型),因此有可能引发运行时异常。

测试方法

代码语言:javascript
复制
  public static void main(String[] args) {
      ObjectContainer myObj = new ObjectContainer();
      // 这里发生向上转型
      myObj.obj = "Test";       
      // 检索封装对象,发生向下转型
      String myStr = (String) myObj.obj; 
      System.out.println("myStr: " + myStr);
  }

注意:一个面试点,发生向下转型的必要条件是先发生向上转型

从 JDK 1.5 开始

从 JDK 1.5 开始出现了泛型,使用泛型可以很好的解决我们的场景需求。在实例化时为所使用的容器分配一个类型,也称泛型类型,这样就可以创建一个对象来存储所分配类型的对象。泛型类型可以看成一种弱类型(类似于 js 中的 var,定义的时候你可以随便定义,使用的时候就需要给出具体类型),这意味着可以通过执行泛型类型调用分配一个类型,将用分配的具体类型替换泛型类型。然后,所分配的类型将用于限制容器内使用的值,这样就无需进行类型转换,还可以在编译时提供更强的类型检查

代码语言:javascript
复制
  // 根据这个代码来看,泛型类型就是 T。泛型类型也可以称为泛型形参。
  public class ObjectContainer<T>{
      public Object obj;
  }

测试方法

代码里的注释很重要!很重要!很重要!建议多看几遍。

代码语言:javascript
复制
  public static void main(String[] args) {
      // 执行泛型类型调用分配 String 这个具体类型,String 也可以称为泛型实参。
      ObjectContainer<String> myObj = new ObjectContainer<String>();
      myObj.obj = "ypf";
      // 这里不需要类型转型,因为通过执行泛型类型调用分配了 String 这个类型,编译器会帮我们去生成一个 checkcast 字节码指令来做类型转换。也就是说我们以前需要手动去做的事(类型转型),现在编译器帮我们做了。其实泛型也可以看成是 Java 的一种语法糖。
      String myStr = myObj.obj;
      System.out.println("myStr: " + myStr);
  }

从上面的类中我们已经知道了泛型类型就是 T。在这个测试方法中执行泛型类型调用对应的代码就是第 2 行,可以看到我们泛型类型 T 在执行时分配了 String 这个类型。其实执行泛型类型调用就是在写类的时候把 ClassName<T> 写成 ClassName<具体的类型>,也就是说把泛型类型替换成具体的类型。

优点

  • 更强的类型检查,避开运行时可能引发的 ClassCastException ,可以节省时间。
  • 消除了类型转换。
  • 开发泛型算法。(可以多去看看 Java 集合中是怎么利用泛型的)

怎么用

泛型类

代码语言:javascript
复制
  public class GenericClass<T>{ 
      // key 这个成员变量的类型为 T,T 的类型由外部使用时指定。  
      private T key;
  
      public Generic(T key) { 
          this.key = key;
      }
  
      public T getKey(){ 
          return key;
      }
  }

泛型接口

定义一个泛型接口

代码语言:javascript
复制
  public interface Generator<T> {
      public T next();
  }

定义实现泛型接口的类

代码语言:javascript
复制
  // 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中。
  public class FruitGenerator<T> implements Generator<T>{
      // 使用泛型类型 T。
      @Override
      public T next() {
          return null;
      }
  }
代码语言:javascript
复制
  // 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型。
  public class FruitGenerator implements Generator<String> {
  
      private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
  
      // 泛型类型 T 被替换成分配的类型 String
      @Override
      public String next() {
          Random rand = new Random();
          return fruits[rand.nextInt(3)];
      }
  }

泛型方法

当我们只想在某个方法中使用泛型而并不需要给整个类加个泛型时,可以使用泛型方法来指定一个泛型类型。泛型方法的泛型类型完全独立于类,也就是说可以与泛型类中声明的 T 不是同一种类型。通过下面的代码来验证这个结论。

代码语言:javascript
复制
  public class GenericTest<T> {
      public T t;
  
      public static <T> T genericMethod(Class<T> clazz)throws InstantiationException ,IllegalAccessException{
          T instance = clazz.newInstance();
          return instance;
      }
  
      public static void main(String[] args) throws IllegalAccessException, InstantiationException {
          GenericTest g = new GenericTest();
          g.t = "666";
          System.out.println(g.t);
          GenericTest g2 = genericMethod(GenericTest.class);
          System.out.println(g2);
      }
  }

输出结果

从输出结果可以看出 GenericTest 类的泛型类型 T 为 String,genericMethod 方法的泛型类型 T 为 GenericTest 。

泛型方法和可变参数灵活使用

通过泛型方法和可变参数,我们可以 new 出任何类型的数组。这样我就很方便创建一个数组,其实在底层实现上是编译器帮我们去 new 数组这个操作了。

代码语言:javascript
复制
  public class GenericTest<T> {
      // 巧妙利用语言的特性。多去了解一下语言的特性,利用起来。
      public static <T> T[] of(T... array){
          return array;
      }
  
      public static void main(String[] args)  {
          Integer[] numArr = of(1,2,3,4);
          String[] strArr = of("y", "p", "f");
          System.out.println(Arrays.toString(numArr));
          System.out.println(Arrays.toString(strArr));
      }
  }

泛型通配符

泛型类型是没有继承关系的。

代码语言:javascript
复制
  public class GenericTest<T> {
      public T t;
  
      public GenericTest(T t) {
          this.t = t;
      }
  
      public static void showKeyValue(GenericTest<Number> obj){
          System.out.println(obj.toString());
      }
  
      public static void main(String[] args)  {
          GenericTest<Integer> gInteger = new GenericTest<Integer>(123);
          GenericTest<Number> gNumber = new GenericTest<Number>(456);
          showKeyValue(gNumber);
          // 下面这个会报错,编译不通过。因为泛型没有继承这个说法。
          // GenericTest<Integer> 和 GenericTest<Number> 经过泛型擦除后都变为 GenericTest。
  //      showKeyValue(gInteger);
      }
  }
解决方案

当操作类型时,不需要使用类型的具体功能时,只使用 Object 类中的功能。那么可以用 ? 通配符来表未知类型。?和 Number、String、Integer 一样都是一种被分配的具体类型,可以把?看成所有类型的父类来理解(也可以把这个看成 Java 语言的一种规范)。

代码语言:javascript
复制
  public class GenericTest<T> {
      public T t;
  
      public GenericTest(T t) {
          this.t = t;
      }
  
      // 使用泛型通配符,可以接收任意类型的泛型类。
      public static void showKeyValue(GenericTest<?> obj){
          System.out.println(obj.toString());
      }
  
      public static void main(String[] args)  {
          GenericTest<Integer> gInteger = new GenericTest<Integer>(123);
          GenericTest<Number> gNumber = new GenericTest<Number>(456);
          showKeyValue(gNumber);
          showKeyValue(gInteger);
      }
  }

泛型上下边界

在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。

  • 为泛型类型添加上边界,即传入的类型实参必须是指定类型的子类型。
  • 为泛型类型添加下边界,即传入的类型实参必须是指定类型的父类型。

泛型上下边界这块具体怎么使用在下次分析时介绍。

注意事项

泛型类型不可以是基本类型,只能是类。

泛型类型没有继承关系。

不能对确切的泛型类型使用 instanceof 操作。

不可以创建一个确切的泛型类型的数组,但是可以声明泛型数组。

底层实现

下次分析时从字节码角度来讲解。

参考链接:https://blog.csdn.net/s10461/article/details/53941091

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java知其所以然 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么需要
    • 场景
      • 解决方案
        • 在 JDK 1.5 之前
        • 从 JDK 1.5 开始
    • 优点
    • 怎么用
      • 泛型类
        • 泛型接口
          • 泛型方法
            • 泛型方法和可变参数灵活使用
              • 泛型通配符
                • 解决方案
              • 泛型上下边界
              • 注意事项
              • 底层实现
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档