JavaSE基础知识(二十一)--Java集合(容器)之泛型概述

本文介绍了Java集合容器中的泛型概念,强调了泛型的作用在于限定容器只能存储特定类型的对象,防止编译期和运行期错误。通过示例展示了如何声明和使用泛型ArrayList,以及泛型的类型安全性和向上转型特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java SE 是什么,包括哪些内容(二十)?

本文内容参考自Java8标准
再次感谢Java编程思想对本文的启发!
在Java SE5之前,使用容器的一个主要的问题是:编译器允许你向容器中插入不同的类型。例如,一个Apple对象的容器,我们使用最基本可靠的容器类ArrayList来说明,实际上,你可以把ArrayList当作"可以自动扩充自身尺寸的数组"来看待。
ArrayList的使用相当简单:创建一个对象实例,用方法add()插入对象,用方法get()访问对象(此时需要索引,就像数组一样,但是不需要方括号)。ArrayList还有一个size()方法,你可以通过它了解目前已经有多少个对象添加了进来,从而不会不小心因索引越界而引发错误。
在下面的代码示例中,你将会看到,我们创建了一个ArrayList用来装Apple对象,但是,同时我们将Apple对象和Orange对象都放置了进去,然后试图将它们取出,在正常情况下,Java编译器会报告警告信息,因为这个示例没有使用泛型。

// ArrayList
   //类Apple
   class Apple{
      //static long类型的类变量counter
      private static long counter;
      //final long类型的类变量id,值为表达式counter++的值
      private final long id = counter++;
      //方法id()
      public long id(){
         //返回类变量id的值
         return id;
      }
   }
   //类Orange
   class Orange{}
   //类ApplesAndOrangeWithOutGenerics
   public class ApplesAndOrangeWithOutGenerics{
      //程序执行入口main()方法
      public static void main(String[] args){
         //创建ArrayList类的对象实例。
         ArrayList apples = new ArrayList();
         //for循环
         for(int i = 0;i<3;i++){
            //往创建的ArrayList容器中装入Apple对象实例
            //这里你能看到,创建的每个Apple对象都没有命名的引用
            //仅仅只有new表达式(new Apple())
            apples.add(new Apple());
         }
         //在装入Apple对象以后,继续装入Orange对象
         //容器并没有及时阻止这种操作
         apples.add(new Orange());
         //for循环,访问容器中的每一个对象
         for(int i = 0;i<apples.size();i++){
            //输出每个对象的id
            System.out.println(((Apple)apples.get(i)).id());
            //当输出Apple对象的时候是没问题的,当遇到Orange对象的时候
            //程序就会报错。
         }
      }
   }

结果示例:
结果报错,提示类Orange不能强制转换成类Apple
前三个对象都是Apple类型(因为ArrayList是有序的容器类,什么顺序放入的,就按照什么顺序保存,所以访问的顺序和放入的顺序是完全一致的),当访问第四个对象的时候(第四个对象是Orange类型)结果报错,提示类Orange不能强制转换成类Apple。
类Apple和类Orange是有区别的,它们除了都是继承自类Object之外,没有其它任何的共性(如果一个类没有使用关键字extends,那么它自动继承自类Object),上面实例代码的ArrayList对象默认是保存Object的,因此你不仅可以通过它的add()方法将Apple对象放进这个容器,还能将Orange对象也放进这个容器,而且无论在编译期还是运行期都不会有问题。
当你在使用ArrayList对象的get()方法来取出你认为是Apple类型的对象时,实际上你得到的只是Object类型的引用,你还必须将其转型为Apple类型,因此,需要将整个表达式括起来,在调用类Apple的id()方法之前,强制执行转型,否则,就会得到语法错误。
从上面的结果示例中可以看到,当你试图将ArrayList中的第四个元素(Orange对象)转型为Apple是,程序报告了一个异常错误。

泛型的作用就是严格规定一个容器对象所能存储的类型,未涉及的类型对象则放不进去。

目前在这里还未涉及到,慢慢后面你会了解到,使用Java泛型来创建类会非常复杂。但是,应用预定义的泛型通常很简单。
例如,要想定义一个专门用来保存Apple类型对象的ArrayList,可以在代码中这么声明:

ArrayList< Apple>

通过一个尖括号来声明这个容器能存储的类型
而不仅仅是ArrayList。其中尖括号括起来的是类型参数(可以有多个),它指定了这个容器对象能保存的类型。
通过使用泛型,就可以在编译期防止将错误类型的对象放置到容器中。
下面来看个代码示例

// 使用泛型容器
   //类AppleAndOrangeWithGenerics
   public class ApplesAndOrangeWithGenerics{
      //程序执行入口main()方法
      public static void main(String[] args){
         //创建ArrayList类的对象实例,同时使用泛型
         //规定这个容器只能存储Apple类型
         ArrayList<Apple> apples = new ArrayList<Apple>();
         //for循环
         for(int i = 0;i<3;i++){
            //往创建的ArrayList容器中装入Apple对象实例
            //这里你能看到,创建的每个Apple对象都没有命名的引用
            //仅仅只有new表达式(new Apple())
            apples.add(new Apple());
         }
         //因为这个ArrayList使用了泛型,所以下面这句代码添加
         //Orange对象会报错,无法添加
         //apples.add(new Orange());
         //for循环,访问容器中的每一个对象
         for(int i = 0;i<apples.size();i++){
            //输出每个对象的id,因为使用了泛型,这里不再需要强制转型了
            //泛型能保证取出来的都是Apple对象
            System.out.println((apples.get(i)).id());
         }
         //还可以利用foreach语法,因为全都是Apple类型
         for(Apple c:apples){
            System.out.println(c.id());
         }
      }
   }

结果示例:
结果示例
使用了泛型后,不能存储Orange类型对象了:
使用了泛型后,不能存储Orange类型对象了
现在,你会发现,编译期能阻止你将Orange对象放置到ArrayList< Apple>中,因此它变成了一个编译期错误,而不是运行期错误。
你应该还注意到了,在将对象从ArrayList< Apple>中取出的时候,类型转换也不再是必须的了,因为有了泛型,容器知道自己保存的是什么类型,因此,在调用get()方法的时候会自动替你执行转型。
这样,通过使用泛型,你不仅知道编译器将会检查你放置到容器中的对象类型,同时,在使用容器中的对象时,可以使用更加清晰的语法(因为知道了确切的类型,就知道可以调用什么方法)。
上面这个代码示例还表明,如果不需要使用每个元素的索引,可以直接使用foreach语法来选择ArrayList< Apple>中的每个元素。
当你指定了某个类型作为泛型的参数时,并不仅限于只能将该确切类型的对象放置到容器中,向上转型也可以像作用于其它类型一样作用于泛型。
代码示例:

// 泛型中的向上转型
   //类GrannySmith继承类Apple
   class GrannySmith extends Apple{}
   //类Gala继承类Apple
   class Gala extends Apple{}
   //类Fuji继承类Apple
   class Fuji extends Apple{}
   //类Braeburn继承类Apple
   class Braeburn extends Apple{}
   //类GenericsAndUpcasting
   public class GenericsAndUpcasting{
      //程序执行入口main()方法
      public static void main(String[] args){
         //创建ArrayList对象,使用泛型<Apple>
         ArrayList<Apple> apples = new ArrayList<Apple>();
         //将类Apple的子类GrannySmith对象放入容器中
         apples.add(new GrannySmith());
         //将类Apple的子类Gala对象放入容器中
         apples.add(new Gala());
         //将类Apple的子类Fuji对象放入容器中
         apples.add(new Fuji());
         //将类Apple的子类Braeburn对象放入容器中
         apples.add(new Braeburn());
         //通过foreach语法来循环容器中的对象。
         for(Apple c:apples){
            //打印对象(实际上调用的是对象的toString()方法)
            System.out.println(c);
         }
      }
   }

结果示例:
结果示例
从结果示例中可以看出,你可以将类Apple的子类添加到被指定保存Apple对象的容器中。
PS:时间有限,有关Java SE的内容会持续更新!今天就先写这么多,如果有疑问或者有兴趣,可以加QQ:2649160693,并注明优快云,我会就博文中有疑义的问题做出解答。同时希望博文中不正确的地方各位加以指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值