一个小栗子聊聊JAVA泛型基础

背景

周五本该是愉快的,可是今天花了一个早上查问题,为什么要花一个早上?我把原因总结为两点:

  • 日志信息严重丢失,茫茫代码毫无头绪。
  • 对泛型的认识不够,导致代码出现了BUG。

第一个原因可以通过以后编码谨慎的打日志来解决,我们今天主要来一起回顾下JAVA泛型基础。

一个小栗子

先看下面一个例子,test1实例化一个List容器的时候没有指定泛型参数,那么我们可以往这个容器里面放入任何类型的对象,这样是不是很爽?但是当我们从容器中取出容器中的对象的时候我们必须小心翼翼,因为容器中的对象具有运行时的类型信息,这意味着你不能够将一个带有运行时类型信息的对象赋值给另一个类型,否则ClassCastException

@Test
public void test1() throws Exception {
    List list = new ArrayList();
    list.add("float.lu");
    list.add(1);

    String name = (String) list.get(0);
    int num = (Integer) list.get(1);
    System.out.println(String.format("name[%s], num[%s]", name, num));
}

上面的代码没问题,可以很好地编译和运行通过,问题是我必须要事先很清楚地知道容器中的索引为0的对象是什么类型,索引为1的对象是什么类型,很显然,这在实际应用中是不切实际的,也是一种很不靠谱的做法,那么这个问题如何解决呢?泛型。

引入泛型

为了解决这个问题,我们引入泛型,下面代码可以看出与上面不同的是我们在实例化容器的时候加了<String>这个东西,这个东西的学名叫做泛型参数,就像普通方法带有参数一样,interface List<E>中的E为形式参数、而String为实参。

@Test
public void test2() throws Exception {
    List<String> list = new ArrayList<String>();
    list.add("a");
    list.add(1)//1
}

引入泛型后,我们规定这个容器中只能存放类型为字符串类型的对象,好的,编译器可以识别泛型并帮我们检查编译错误,上面的代码中1处会出现编译错误。注意:泛型信息仅仅存在于编译期间,编译器可以通过泛型信息来对代码是否存在违规行为(编译错误)来进行检查,当编译器将代码编译为字节码之后,泛型信息将不复存在,然而对象的运行时信息仍然是有的,这就解释了为什么会出现ClassCastException。

别高兴太早

有了泛型我们可以让代码安全地通过编译,并且我们认为他是安全的了,嘿嘿,是否就真的安全了呢?是否就能和ClassCastException说拜拜了呢?答案是:NO。看看下面这段代码:

@Test
public void test3() throws Exception {
    List<String> list = new ArrayList<String>();
    list.add("a");
    list.add("b");

    List _list = list;
    List<Integer> integerList = _list;
    for (Integer item : integerList) {
        System.out.println(String.format("item[%s]", item));
    }
}

上面这段代码编译没有问题,我们没有直接将泛型参数为String的容器赋值给泛型参数为Integer的容器,而是花了点点小心思,我们现将list赋值给_list,_list生命为可以存储任何类型,也就相当于无特定类型,而后我们又把_list赋值给integerList容器,integerList容器被声明为只能存储类型为Integer的对象。悲催的是这段代码在运行的时候报了ClassCastException,很明显,我们知道在迭代integerList容器中的对象的时候,这些对象是有运行时类型信息的,当带有String类型信息的对象赋值给Integer的时候显然就报错了。这一切看起来似乎没问题,符合逻辑,但是有一个问题我们还没有问:为什么会没有编译错误?

泛型术语

在学习数学的时候我们往往会对一个证明题进行论证,而论证之前我们手上往往会有一些不需要证明的已知定理,下面这些“定理”将被用来直接回答上一节中遗留的问题。

  • List<E>被称作泛型类型。
  • List<E>中的E被称为类型变量或类型参数。
  • List<String>被称为参数化类型。
  • List<String>中的String被称为实际类型参数。
  • List<E>中的<>年typeof。
  • List被称为原始类型。
  • 参数化类型可以引用一个原始类型对象,编译报告警告。
  • 原始类型可以引用一个参数化类型对象,编译报告警告。

由上可知,List<Integer> integerList = _list;可以通过编译。

看清本质

经过上面的一些小波折,我们了解一些关于泛型的本质:泛型是给javac编译器使用的,javac是JAVA的编译器,而泛型可以让代码在编译期间确定类型安全,比如我们告诉编译器某个容器只能存储某种类型的对象,那么编译器会为我们好好地检查,确保类型安全,但是安全是相对的,只要我们逃过编译器,我们就有一百种方法让代码ClassCastException(比如反射)。同时编译之后参数化类型在运行时没有任何泛型信息,也就是为什么List.class和List<String>.class是同一个东西。除了参数化类型之外,容器中的对象在运行的时候是有类型信息的,也就是为什么会ClassCastExcetion。关于泛型还有很多内容,这里不做多讲,文中有误也欢迎留言讨论。

时间: 2024-12-08 21:32:08

一个小栗子聊聊JAVA泛型基础的相关文章

2.java泛型基础

Java泛型(generics)是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter).声明的类型参数在使用时用具体的类型来替换.泛型最主要的应用是在JDK 5中的新集合类框架中.对于泛型概念的引入,开发社区的观点是褒贬不一.从好的方面来说,泛型的引入可以解决之前的集合类框架在使用过程中通常会出现的运行时刻类型错误,因为编译器可以在编译时刻就发现很多明显的错误.而从不好的地方来说,为了保证与旧有版本的兼容性,Java泛型的实现上存在着一些不够优雅的地

Java:泛型基础

泛型 引入泛型 传统编写的限制: 在Java中一般的类和方法,只能使用具体的类型,要么是基本数据类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制就会束缚很多! 解决这种限制的三种方法: 1.多态:将方法的参数类型设为基类,那么该方法就可以接收从这个基类导出的任何类作为参数. class Primary{} //定义基类 class Test() { public void f(Primary p) {...} } 2.方法的参数使用接口:任何实现了该接口的类都可以满足该

汇道科技:一个小技巧让JAVA程序员顺利拿到理想Offer

有朋友跟我抱怨,明明平常对知识掌握的很精炼了,一到面试就会紧张,发挥失常,错失工作机会,这种情况就像"考前综合症",平常对知识点都掌握的很好,一到大场合就怯场,其实我们无论是在考试还是面试,都有很多技巧的.今天汇道科技小编分享一些面试技巧给各位程序员,让大家在面试时能更加得心应手,更顺利的拿到理想的offer. Java程序员面试的有哪些小技巧 01 笔试 笔试这个环节是很容易,一般由5至10个选择题+2至5个论述题+1至2个编程题组成. 我们保持平静心态--浏览所有题目--开答.答题

201671010127 2016—2017-2 通过一个小程序对Java的再认识。

学习了将近四周的Java语言,对于Java语言,我也有了更进一步的理解,出于对Java语言的喜爱,我总是喜欢没事的时候,自己敲一些很简单的代码,一边学习Java语言,一边对比C语言,往往可以帮助我们更好的学习Java言,由于我们先学习了C语言,每次写Java程序时,我们总会习惯性的把C语言的语法带到Java语言里,但由于Java和C语言的语法又有一定的差别,所以会导致我们写的程序无法通过编译.因此我们通过比较性的学习,更加能使我们很好地了解Java语言,下面就我遇到的问题和大家分享一下. 我分别

一个小讨论 关于java 数据遍历时size不能修改

有一个这样的模型 伪代码: Arrays array= [a,5,c,1,e,f] 里面有几个对象,假如要把里面的数字删除,如何编码 我开始的想法是这样的 for (Item item:array) { if (item 是数字) { array.remove(item); } } 问题来了,这样写了之后直接Exception了?为毛! 原因也很简单,先挖坑 让自己多想想

每天学习一个小功能:java文件上传

====(1.)第一种.利用普通缓冲流进行文件上传 ① 前端 注意: 1.指定表单类型为文件上传表单 :enctype="multipart/form-data"  2.提交方式必须为:post3.表单中,存在文件域 的表单元素 <form name="frm_test" action="${pageContext.request.contextPath }/shangchuan" method="post" encty

每天学习一个小功能:java文件下载

思路: 1.获取文件上传到upload文件夹下的文件名 2.将文件名处理成上传时的文件名并封装成集合给前端展示 3.前端根据提交的文件名再后台查找upload文件夹下查找并下载 代码: /**     * 1.获取文件上传到upload文件夹下的文件名 2.将文件名处理成上传时的文件名并封装成集合给前端展示     * @param request     * @param model     * @return     */        @RequestMapping("xiazai&quo

[python学习] 介绍python的property,以及为什么要用setter,一个小栗子

python中的property是比较好用的. 先来一段代码 #-*- coding:utf-8 -*- class C(object): status_dict = { 1: 'accept', 2: 'reject' } def __init__(self): self._x = 1 @property def status(self): return self.status_dict[self._x] @status.setter def status(self, val): if val

利用哨兵简化编程的一个小栗子

// 在数组 a 中,查找 key,返回 key 所在的位置 // 其中,n 表示数组 a 的长度 // 我举 2 个例子,你可以拿例子走一下代码 // a = {4, 2, 3, 5, 9, 6} n=6 key = 7 // a = {4, 2, 3, 5, 9, 6} n=6 key = 6 int find(char* a, int n, char key) { if(a == null || n <= 0) { return -1; } // 这里因为要将 a[n-1] 的值替换成 k