对于范型的使用或者说印象只有集合,其他地方即使使用过也不知道,反正就是只停留在List<E> Map<K, V>,最近刚好闲来无事,就找找资料学习一下;下列为个人学习总结,欢迎学习交流;

1. 什么是java泛型

范型:参数化类型,所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法;

List<Integer> list = new ArrayList<>();

上述代码申明了一个集合,操作的数据类型被指定为Integer(此处Integer为类型参数);

2. 为什么需要泛型

引入泛型的好处是可以将运行时错误提前到编译时错误

List list = new ArrayList();
list.add(100);
list.add("zhangsan"); for(int i = 0; i< list.size();i++){
int num = (int)list.get(i);
}

上面的代码在编译时没有任何问题,但是在运行时会报错:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

如果没有引入范型,集合内的操作数据类型可以是任何类型,如果在操作数据时进行类型判断然后在强转也是没有问题的,但是很明显不切合实际,所以如果引入范型对操作数据类型做一定的约束的话,将会对后续的操作提供太多的方便也能减少错误的出现;

List<Integer> list = new arrayList<>();
//list.add("zhangsan");//在编译期就会报错

3. 范型的使用

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

3.1. 范型类

泛型类中的类型参数几乎可以用于任何可以使用接口名、类名的地方

/**
* 范型标识可以是任何标识符号,如常见的E, T, K, V ...
*/
class 类名<范型标识>{
} class Stu<T>{
} class People<E>{
}

注意:

  • 泛型的类型参数只能是类类型,不能是基础类型;
List<int> list;//基础类型不能当作类型参数
  • 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。
if(item instanceof List<Integer>) // Illegal generic type of instanceof

3.2. 范型接口

泛型接口与泛型类的定义及使用基本相同

public interface Iterable<T> {
Iterator<T> iterator();
}
  1. 实现范型接口,未明确范型时:实现类后的范型标识不能省略
class Iter<T> implements Iterable<T> {
@Override
public Iterator<T> iterator() {
return null;
}
}
  1. 实现范型接口,明确范型时:实现类后的范型标识省略
class Iter1 implements Iterable<String> {
@Override
public Iterator<String> iterator() {
return null;
}
}

3.3. 范型方法

public 与 返回值 中间非常重要,声明此方法为泛型方法;

/**
* 泛型方法的基本介绍
* @param tClass 传入的泛型实参
* @return T 返回值为T类型
* 说明:
* 1)public 与 返回值 中间<T>非常重要,声明此方法为泛型方法;
* 2)只有声明了<T>的方法才是泛型方法,若没有<T>泛型类中即使使用了泛型的成员方法也不是泛型方法;
* 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T;
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型;
*/
public <T> T genInstance(Class<T> tClass) throws IllegalAccessException, InstantiationException {
T instance = tClass.newInstance();
return instance;
}

光看上面的例子可能依然会非常迷糊,我们再通过一个例子

public class ArrayList<E> implements List<E> {

    /**
* 虽然在方法中使用了泛型,但是这并不是一个泛型方法。
* 这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
* 所以在这个方法中才可以继续使用 T 这个泛型。
*/
public E get(int index) {
//...
return elementData(index);
} /**
* 将E改为T后,方法报错,"cannot reslove symbol T"
* 因为在类的声明中并未声明泛型T,所以在使用E做形参和返回值类型时,编译器会无法识别
*/
public T set(int index, T element) {
//...
return oldValue;
} /**
* 这才是一个真正的泛型方法。
* 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
* 这个T可以出现在这个泛型方法的任意位置.
*/
public <T> T[] toArray(T[] a) {
//...
return a;
}
}

4. 范型通配符

使用?代替范型标识,标识操作数据类型可以为任何数据类型

java中我们都知道父类可以出现的地方,子类都是可以出现的,但是:

public static void method(List<Number> list){
} public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
method(list); // 此处报错:List<java.lang.Number> cannot be applied to List<java.lang.Integer>
}

通过提示信息我们可以看到List<Integer>不能被看作为List<Number>的子类。

由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

我们可以将上面的方法改一下:

public static void method(List<?> list){
}

类型通配符一般是使用?代替具体的类型实参,此处的和Number、String、Integer一样都是一种实际的类型,可以把看成所有类型的父类,是一种真实的类型;

可以解决当具体类型不确定的时候,这个通配符就是?;当不需要使用类型的具体功能只使用Object类中的功能,那么可以用 ? 通配符来表未知类型。

5. 泛型上下边界

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

public static void method(List<? extends Number> list){
} public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
method(intList); // Integer 是Number的子类 List<Double> doubleList = new ArrayList<>();
method(doubleList); // Double是Number的子类 List<String> strList = new ArrayList<>();
method(strList); // 此处报错,String不是Number的子类
}

6. 范型擦除

Java在编译期间,所有的泛型信息都会被擦掉;

如在代码中定义List<Object>List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法预知在运行时刻出现的类型转换异常的情况;

擦除范型后只保留原始类型

原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

List<String> list1 = new ArrayList<>();
list1.add("abc"); List<Integer> list2 = new ArrayList<>();
list2.add(123); System.out.println(list1.getClass() == list2.getClass()); // true
System.out.println(list1.getClass()); // class java.util.ArrayList

说明泛型类型String和Integer都被擦除掉了,只剩下原始类型List;

ArrayList<Integer> list = new ArrayList<Integer>();

list.add(1);  //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
list.add("asd"); // 此处报错 list.getClass().getMethod("add", Object.class).invoke(list, "asd"); // 通过反射获取实例后可以添加成功 for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。

参考资料:

https://www.cnblogs.com/wuqinglong/p/9456193.html

https://blog.csdn.net/caihuangshi/article/details/51278793

https://www.cnblogs.com/iyangyuan/archive/2013/04/09/3011274.html

https://blog.csdn.net/caihuangshi/article/details/51278793

Java范型学习笔记的更多相关文章

  1. java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

  2. java之jvm学习笔记十三(jvm基本结构)

    java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成 ...

  3. Java:NIO 学习笔记-2

    Java:NIO 学习笔记-2 上一篇 NIO 学习笔记-1 看了 尚硅谷 的相应教程,此处又对比看了 黑马程序员 的课程 JAVA通信架构I/O模式,做了相应的笔记 前言 在 Java 的软件设计开 ...

  4. Java:NIO 学习笔记-1

    Java:NIO 学习笔记-1 说明:本笔记是根据bilibili上 尚硅谷 的课程 NIO视频 而做的笔记 主要内容 Java NIO 简介 Java NIO 与 IO 的主要区别 缓冲区(Buff ...

  5. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  6. Java范型随笔

    最近在帝都好无聊啊, 排遣寂寞就只有让自己不要停下来,不断的思考了 QWQ; 最近做ndk, java有点忘了,突然看到了一些java范型方面的问题, 踌躇了一会, 想着想着,决定还是写个随笔记录下来 ...

  7. Java多线程技术学习笔记(二)

    目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和w ...

  8. Java安全防御学习笔记V1.0

    Java安全防御学习笔记V1.0http://www.docin.com/p-766808938.html

  9. java之jvm学习笔记三(Class文件检验器)

    java之jvm学习笔记三(Class文件检验器) 前面的学习我们知道了class文件被类装载器所装载,但是在装载class文件之前或之后,class文件实际上还需要被校验,这就是今天的学习主题,cl ...

随机推荐

  1. 浏览器从输入url 到页面展示完成响应过程

    用户从输入 url 到浏览器响应,呈现给用户的具体过程 1.用户在输入栏输入地址 (1) 如果有 beforeunload 事件会先执行判断继续还是跳出操作 (2) 浏览器进程识别是 地址还是关键字检 ...

  2. Redis-缓存穿透、缓存雪崩、缓存击穿、缓存一致性、并发竞争

    缓存流程 在讲这五个问题之前,首先我们回顾下正常的缓存的使用流程 程序在处理请求时,会先从缓存中进行查询,如果缓存中没有对应的key,则会从数据库中查询,如果查询到结果,并将查询结果添加到缓存中去,反 ...

  3. H5录音音频可视化-实时波形频谱绘制、频率直方图

    这段时间给GitHub Recorder开源库添加了两个新的音频可视化功能,比以前单一的动态波形显示丰富了好多(下图后两行是不是比第一行看起来丰满些):趁热打铁写了一个音频可视化相关扩展测试代码,下面 ...

  4. 关于SDWebImage的一点小坑

        做项目遇到一个问题,是用sd加载图片,明明本地有图片,使用sd的内部方法也可以拿到那些个图片,但是就是加载缓慢,如果网络还行,网络加载图片都比加载本地图片快.而使用[[SDImageCache ...

  5. 发布到远程存储库时遇到错误: Git failed with a fatal error.

    正在推送 master发布到远程存储库时遇到错误: Git failed with a fatal error.Authentication failed for 'http://1212121xxx ...

  6. Iaas/paas/saas 三种模式分别都是做什么?

    任何一个在互联网上提供其服务的公司都可以叫做云计算公司.其实云计算分几层的,分别是Infrastructure(基础设施)-as-a- Service,Platform(平台)-as-a-Servic ...

  7. Asp.Net Core Identity 隐私数据保护

    前言 Asp.Net Core Identity 是 Asp.Net Core 的重要组成部分,他为 Asp.Net Core 甚至其他 .Net Core 应用程序提供了一个简单易用且易于扩展的基础 ...

  8. Nginx作为web静态资源服务器——跨域访问

    跨站访问 ​ 为什么浏览器禁止跨域访问 ​ Nginx跨站访问 Syntax:add_header name value [always]; Default:—— Context:http,serve ...

  9. numpy 索引和切片

    一.取行 1.单行 数组[index, :] # 取第index+1行 例子 import numpy as np arr1 = np.arange(0, 24).reshape(4, 6) # 取第 ...

  10. 8.for循环及练习

    For循环:   虽然所有循环结构都可以用 while 或者 do...while 表示,但Java提供了另一种语句— —for循环,使一些循环结构变的更加简单. for 循环语句是支持迭代的一种通用 ...