全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

Java 泛型总结(二):泛型与数组

简介

上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系。数组相比于Java 类库中的容器类是比较特殊的,主要体现在三个方面:

  • 数组创建后大小便固定,但效率更高
  • 数组能追踪它内部保存的元素的具体类型,插入的元素类型会在编译期得到检查
  • 数组可以持有原始类型 ( int,float等 ),不过有了自动装箱,容器类看上去也能持有原始类型了

那么当数组遇到泛型会怎样? 能否创建泛型数组呢?这是这篇文章的主要内容。

这个系列的另外两篇文章:

  • Java 泛型总结(一):基本用法与类型擦除
  • Java 泛型总结(三):通配符的使用

泛型数组

如何创建泛型数组

如果有一个类如下:

 class Generic<T> {
 
}

如果要创建一个泛型数组,应该是这样: Generic<Integer> ga = new Generic<Integer>[]  不过行代码会报错,也就是说不能直接创建泛型数组。

那么如果要使用泛型数组怎么办?一种方案是使用 ArrayList,比如下面的例子:

public class ListOfGenerics<T> {
 private List<T> array = new ArrayList<T>();
 public void add(T item) { array.add(item); }
 public T get(int index) { return array.get(index); }
}

如何创建真正的泛型数组呢?我们不能直接创建,但可以定义泛型数组的引用。比如:

public class ArrayOfGenericReference {
 static Generic<Integer>[] gia;
}

gia 是一个指向泛型数组的引用,这段代码可以通过编译。但是,我们并不能创建这个确切类型的数组,也就是不能使用 new Generic<Integer>[]  具体参见下面的例子:

public class ArrayOfGeneric {
 static final int SIZE = 100;
 static Generic<Integer>[] gia;
 @SuppressWarnings("unchecked")
 public static void main(String[] args) {
 // Compiles; produces ClassCastException:
 //! gia = (Generic<Integer>[])new Object[SIZE];
 // Runtime type is the raw (erased) type:
 gia = (Generic<Integer>[])new Generic[SIZE];
 System.out.println(gia.getClass().getSimpleName());
 gia[0] = new Generic<Integer>();
 //! gia[1] = new Object(); // Compile-time error
 // Discovers type mismatch at compile time:
 //! gia[2] = new Generic<Double>();
 Generic<Integer> g = gia[0];
 }
} /*输出:
Generic[]
*///:~

数组能追踪元素的实际类型,这个类型是在数组创建的时候建立的。上面被注释掉的一行代码: gia = (Generic<Integer>[])new Object[SIZE],数组在创建的时候是一个 Object 数组,如果转型便会报错。成功创建泛型数组的唯一方式是创建一个类型擦除的数组,然后转型,如代码: gia = (Generic<Integer>[])new Generic[SIZE],gia 的 Class 对象输出的名字是 Generic[]。

我个人的理解是:由于类型擦除,所以 Generic<Integer> 相当于初始类型 Generic,那么 gia = (Generic<Integer>[])new Generic[SIZE] 中的转型其实还是转型为 Generic[],看上去像没转,但是多了编译器对参数的检查和自动转型,向数组插入 new Object()new Generic<Double>()均会报错,而 gia[0] 取出给 Generic<Integer> 也不需要我们手动转型。

使用 T[] array

上面的例子中,元素的类型是泛型类。下面看一个元素本身类型是泛型参数的例子:

public class GenericArray<T> {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArray(int sz) {
 array = (T[])new Object[sz]; // 创建泛型数组
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 public T get(int index) { return array[index]; }
 // Method that exposes the underlying representation:
 public T[] rep() { return array; } //返回数组 会报错
 public static void main(String[] args) {
 GenericArray<Integer> gai =
 new GenericArray<Integer>(10);
 // This causes a ClassCastException:
 //! Integer[] ia = gai.rep();
 // This is OK:
 Object[] oa = gai.rep();
 }
}

在上面的代码中,泛型数组的创建是创建一个 Object 数组,然后转型为 T[]。但数组实际的类型还是 Object[]。在调用 rep()方法的时候,就报 ClassCastException 异常了,因为 Object[] 无法转型为 Integer[]。

那创建泛型数组的代码 array = (T[])new Object[sz] 为什么不会报错呢?我的理解和前面介绍的类似,由于类型擦除,相当于转型为 Object[],看上去就是没转,但是多了编译器的参数检查和自动转型。而如果把泛型参数改成 <T extends Integer> ,那么因为类型是擦除到第一个边界,所以 array = (T[])new Object[sz] 中相当于转型为 Integer[],这应该会报错。下面是实验的代码:

public class GenericArray<T extends Integer> {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArray(int sz) {
 array = (T[])new Object[sz]; // 创建泛型数组
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 public T get(int index) { return array[index]; }
 // Method that exposes the underlying representation:
 public T[] rep() { return array; } //返回数组 会报错
 public static void main(String[] args) {
 GenericArray<Integer> gai =
 new GenericArray<Integer>(10);
 // This causes a ClassCastException:
 //! Integer[] ia = gai.rep();
 // This is OK:
 Object[] oa = gai.rep();
 }
}

相比于原始的版本,上面的代码只修改了第一行,把  <T> 改成了 <T extends Integer>   那么不用调用 rep(),在创建泛型数组的时候就会报错。下面是运行结果:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at GenericArray.<init>(GenericArray.java:15)

使用 Object[] array

由于擦除,运行期的数组类型只能是 Object[],如果我们立即把它转型为 T[],那么在编译期就失去了数组的实际类型,编译器也许无法发现潜在的错误。因此,更好的办法是在内部最好使用 Object[] 数组,在取出元素的时候再转型。看下面的例子:

public class GenericArray2<T> {
 private Object[] array;
 public GenericArray2(int sz) {
 array = new Object[sz];
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 @SuppressWarnings("unchecked")
 public T get(int index) { return (T)array[index]; }
 @SuppressWarnings("unchecked")
 public T[] rep() {
 return (T[])array; // Warning: unchecked cast
 }
 public static void main(String[] args) {
 GenericArray2<Integer> gai =
 new GenericArray2<Integer>(10);
 for(int i = 0; i < 10; i ++)
 gai.put(i, i);
 for(int i = 0; i < 10; i ++)
 System.out.print(gai.get(i) + " ");
 System.out.println();
 try {
 Integer[] ia = gai.rep();
 } catch(Exception e) { System.out.println(e); }
 }
} /* Output: (Sample)
0 1 2 3 4 5 6 7 8 9
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
*///:~

现在内部数组的呈现不是 T[] 而是 Object[],当 get() 被调用的时候数组的元素被转型为 T,这正是元素的实际类型。不过调用 rep() 还是会报错, 因为数组的实际类型依然是Object[],终究不能转换为其它类型。使用 Object[] 代替 T[] 的好处是让我们不会忘记数组运行期的实际类型,以至于不小心引入错误。

使用类型标识

其实使用 Class 对象作为类型标识是更好的设计:

public class GenericArrayWithTypeToken<T> {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArrayWithTypeToken(Class<T> type, int sz) {
 array = (T[])Array.newInstance(type, sz);
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 public T get(int index) { return array[index]; }
 // Expose the underlying representation:
 public T[] rep() { return array; }
 public static void main(String[] args) {
 GenericArrayWithTypeToken<Integer> gai =
 new GenericArrayWithTypeToken<Integer>(
 Integer.class, 10);
 // This now works:
 Integer[] ia = gai.rep();
 }
}

在构造器中传入了 Class<T> 对象,通过 Array.newInstance(type, sz) 创建一个数组,这个方法会用参数中的 Class 对象作为数组元素的组件类型。这样创建出的数组的元素类型便不再是 Object,而是 T。这个方法返回 Object 对象,需要把它转型为数组。不过其他操作都不需要转型了,包括 rep() 方法,因为数组的实际类型与 T[] 是一致的。这是比较推荐的创建泛型数组的方法。

总结

数组与泛型的关系还是有点复杂的,Java 中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解,如果错误希望大家指正。下一篇会总结通配符的使用。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!


# java  # 泛型数组  # 创建泛型数组  # 泛型与数组  # Java封装数组实现包含、搜索和删除元素操作详解  # Java封装数组实现在数组中查询元素和修改元素操作示例  # Java封装数组之添加元素操作实例分析  # 使用java数组 封装自己的数组操作示例  # java数组、泛型、集合在多态中的使用及对比  # java 用泛型参数类型构造数组详解及实例  # JAVA得到数组中最大值和最小值的简单实例  # JavaScrip数组删除特定元素的几种方法总结  # Java中高效的判断数组中某个元素是否存在详解  # java中数组的定义及使用方法(推荐)  # Java封装数组之改进为泛型数组操作详解  # 报错  # 擦除  # 创建一个  # 是一个  # 这是  # 是在  # 把它  # 就会  # 也不  # 都不  # 让我们  # 是这样  # 也能  # 会在  # 这段  # 上一  # 可以通过  # 更高  # 来看看  # 希望大家 


相关文章: ,巨量百应是干嘛的?  网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?  如何安全更换建站之星模板并保留数据?  网站制作服务平台,有什么网站可以发布本地服务信息?  深入理解Android中的xmlns:tools属性  如何用wdcp快速搭建高效网站?  香港服务器部署网站为何提示未备案?  如何确认建站备案号应放置的具体位置?  Thinkphp 中 distinct 的用法解析  如何正确下载安装西数主机建站助手?  如何基于云服务器快速搭建网站及云盘系统?  如何快速查询域名建站关键信息?  公司网站的制作公司,企业网站制作基本流程有哪些?  车管所网站制作流程,交警当场开简易程序处罚决定书,在交警网站查询不到怎么办?  东莞市网站制作公司有哪些,东莞找工作用什么网站好?  如何用已有域名快速搭建网站?  如何选择域名并搭建高效网站?  建站168自助建站系统:快速模板定制与SEO优化指南  如何选择适配移动端的WAP自助建站平台?  宝华建站服务条款解析:五站合一功能与SEO优化设置指南  制作网站的过程怎么写,用凡科建站如何制作自己的网站?  单页制作网站有哪些,朋友给我发了一个单页网站,我应该怎么修改才能把他变成自己的呢,请求高手指点迷津?  如何在Ubuntu系统下快速搭建WordPress个人网站?  宝塔建站助手安装配置与建站模板使用全流程解析  哪家制作企业网站好,开办像阿里巴巴那样的网络公司和网站要怎么做?  西安专业网站制作公司有哪些,陕西省建行官方网站?  佛山企业网站制作公司有哪些,沟通100网上服务官网?  焦点电影公司作品,电影焦点结局是什么?  番禺网站制作公司哪家值得合作,番禺图书馆新馆开放了吗?  建站之星好吗?新手能否轻松上手建站?  详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)  网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?  建站主机类型有哪些?如何正确选型  如何注册花生壳免费域名并搭建个人网站?  如何在腾讯云免费申请建站?  如何在阿里云通过域名搭建网站?  如何在香港免费服务器上快速搭建网站?  网站制作公司,橙子建站是合法的吗?  如何通过商城免费建站系统源码自定义网站主题?  大连 网站制作,大连天途有线官网?  建站之星后台管理如何实现高效配置?  官网自助建站系统:SEO优化+多语言支持,快速搭建专业网站  如何通过西部数码建站助手快速创建专业网站?  网站建设制作需要多少钱费用,自己做一个网站要多少钱,模板一般多少钱?  头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?  北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?  七夕网站制作视频,七夕大促活动怎么报名?  合肥制作网站的公司有哪些,合肥聚美网络科技有限公司介绍?  建站之星会员如何解锁更多建站功能?  如何通过主机屋免费建站教程十分钟搭建网站? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。