Java:泛型(深入解析,一文读懂)
基本概念和原理
为什么使用泛型:
在没有使用泛型之前,一旦把一个对象“丢进”Java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理。当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换不仅是代码臃肿,而且容易引起ClassCastException异常。
标题的基本概念:
所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参(或叫泛型)将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。
使用泛型的好处:
更好的安全性
通过使用泛型 ,开发环境和编译器能确保不会用错类型,为程序多设置一道安全防护网。更好的可读性
使用泛型,可以省去繁琐的强制类型转换,再加上明确的类型信息,代码可读性也会更好。
泛型的使用:
public class Test<T> {T first;T second;public Test(T first, T second) {this.first = first;this.second = second;}public T getFirst() {return first;}public T getSecond() {return second;}
}
Test是一个泛型类,与普通类的区别:
- 类名后多了一个<T>;
- first和second的类型都是T;
T表示类型参数,泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入。
泛型的原理:
Java有Java编译器和Java虚拟机,编译器将Java源代码转换为.class文件,虚拟机加载并运行.class文件。对于泛型类,Java编译器会将泛型代码转换为普通的非泛型代码,就像上面的普通Test类代码及其使用代码一样,将类型参数T擦除,替换Object,插入必要的强制类型转换。Java虚拟机实际执行的时候,它是不知道泛型这回事的,只知道普通的类及代码。
泛型擦除:
Java泛型是通过擦除实现额,类定义中的类型参数如T会被替换为Object,在程序运行过程中,不知道泛型的实际类型参数,比如Test<Integer>,运行中只知道Test,而不知道Integer。
深入泛型
//定义接口时指定了一个泛型形参,该形参名为E
public interface Test<E> {//在接口方法里,E可作为类型使用//下面方法可以使用E作为类型参数void add(E x);Iterable<E> iterator();//在接口里,E完全可以作为类型使用E next();
}
public interface Map<K, V> {//在接口里K、V完全可以作为类型使用Set<K> keySet();V put(K key, V value);
}
解释:
允许在定义接口、类时声明泛型形参,泛型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通方法类型的地方都可以使用这种泛型形参。
注:
当创建泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。 例如,为Test<T>类定义构造器,其构造器名依然是Test,而不是Test<T>;!调用该构造器时却可以使用Test<T>的形式,当然应该为T形参传入实际的类型参数。Java7提供了“菱形”语法,允许省略<>中的类型实参。
从泛型类派生子类:
方法中的形参代表变量、常量、表达式等数据,本文把它们直接称为形参,或者称为数据形参。定义方式时可以声明数据形参,调用方法(使用方法)时必须为这些数据形参传入实际的数据;于此类似的是,定义类、接口、方法时可以使用声明泛型形参,使用类、接口、方法时应该为泛型形参传入实际的类型。
//定义类A继承Apple类,Apple类不能跟泛型形参
public class A extends Apple<T> {} //错误//使用Apple类时为T形参传入String类型
public class A extends Apple<String> //正确
调用方法时必须为所有的数据形参传入参数值, 与调用方法不同的是,使用类、接口时也可以不为泛型形参传入实际的类型参数,即下面代码也是正确的。
public class A extends Apple //正确
像这种使用Apple类时省略泛型的形式被称为原始类型(raw type)。
如果使用Apple类时没有传入实际的类型(即使用原始类型),Java编译器可能发出警告:使用了未经检查或不安全的操作 - - 就是泛型检查的警告。
并不存在泛型类:
看如下代码:
List<String> list = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
//调用getClass()方法来比较list和list2的类是否相等
System.out.println(list.getClass() == list2.getClass());
运行上面的代码片段,可能有读者认为应该输出false,但实际输出true。因为不管泛型的实际类型参数是什么,它们在运行时总有同样的类(Class)。
不管为泛型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一个类来处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或者静态变量(它们都是类相关的)的声明和初始化中不允许使用泛型形参。
public class Test<T> {//下面代码错误,不能在静态变量声明中使用泛型形参static T info;//正确T age;public void foo(T msg) { }//下面代码错误,不能再静态方法声明中使用使用泛型形参public static void bar(T msg) {}
}
由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。
java.util.Collection<String> cs = new java.util.ArrayList<String>();
//下面代码编译时引起错误:instanceof运算符后不能使用泛型
if(cs instanceof java.util.ArrayList<String>) {}
使用类型通配符:
为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是元素类型未知的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。
看如下代码:
public void test(List<?> c) { }
设定类型通配符的上限(协变):
指定通配符上限的集合,只能从集合中取元素(取出的元素总是上限的类型或其子类),不能向集合中添加元素(因为编译器没法确定集合元素实际是哪种子类型)。
设定类型通配符的下限(逆变):
除可以指定通配符的上限之外,Java也允许指定通配符的下限,通配符的下限用<? super 类型>的方式类指定,通配符下限的作用与通配符上限的作用恰好相反。
Foo是Bar的子类,当程序需要一个A<? super Foo>变量时,程序可以将A<Bar>、A<Object>赋值给A<? super Foo>类型的变量,这种方式称为逆变。
对于逆变的泛型来说,编译器只知道集合元素是下限的父类型,但具体是哪种父类型则不确定。因此,这种逆变的泛型集合能向其中添加元素(因为实际赋值的集合元素总是逆变声明的父类),从集合中取元素时只能被当成Object类型处理(编译器无法确定取出的到底是哪个父类的对象)。
Java:泛型(深入解析,一文读懂)相关推荐
- java 审批流_一文读懂工作流
网上关于工作流引擎有比较多的简介,也有很多工作流的实际应用场景.本文结合笔者多年对工作流的经验来阐述一下对工作流的理解. 一.什么是工作流? 先贴上wiki百科对于工作流的定义 工作流(Workflo ...
- java元编程_一文读懂元编程
元编程(Metaprogramming)是编写.操纵程序的程序,简而言之即为用代码生成代码.元编程是一种编程范式,在传统的编程范式中,程序运行是动态的,但程序本身是静态的.在元编程中,两者都是动态的[ ...
- java arraylist排序_一文读懂Java集合框架
欢迎关注微信公众号:深入浅出Java源码 概念 Java集合框架为程序员提供了预先包装的数据结构和算法来操纵他们.集合框架被设计成要满足以下几个目标. 该框架必须是高性能的.基本集合(动态数组,链表, ...
- java 委派关系_一文读懂java类加载之双亲委派机制
一个编译后的class文件,想要在JVM中运行,就需要先加载到JVM中.java中将类的加载工具抽象为类加载器,而通过加载工具加载类文件的具体方式被称为双亲委派机制. 知识点 类加载器:通过一个类全限 ...
- 解析|一文读懂AGV的关键技术——激光SLAM与视觉SLAM的区别
来源:新机器视觉 移动机器人(AGV)是工业机器人的一种.它由计算机控制,具有移动.自动导航.多传感器控制.网络交互等功能,在实际生产中最主要的用途是搬运,可以说只要有搬运需求的地方,就有移动机器人的 ...
- Java必备基础一——一文读懂的Java基础中的基础
内容虽然很简单,但是由于自己的基础很差,练习又太少,在编程这件事情上总是十分吃力,故记于此,长于心. 写在最后,打个鸡血,共勉:真正能让你走远的,都是自律.积极和勤奋. 程序人生,你可以靠努力去实现的 ...
- 一文读懂HttpServletRequest
点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 精彩内容 java实战练习项目教程 全网最全电子图书分享 你所需要的大数据视频 ...
- JVM(一)一文读懂Java编译全过程
一文读懂Java编译全过程 java代码首先要通过前端编译器编译成.class字节码文件,然后再按一定的规则加载到JVM(java 虚拟机)内运行,有三种运行方式,解释模式(javac).编译模式(C ...
- 一文读懂Java中File类、字节流、字符流、转换流
一文读懂Java中File类.字节流.字符流.转换流 第一章 递归:File类: 1.1:概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. ...
- java中date类型如何赋值_一文读懂java中的Reference和引用类型
简介 java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型.java为引用类型专门定义了一个类叫做Reference.Reference是跟jav ...
最新文章
- Heartbeat双机热备配置
- MySQL必知必会读书笔记
- 用Uber的方式管理机器学习模型
- Redis3.0.5配置文件详解
- arm ida 伪代码 安卓 符号表_IDA调试界面介绍及快捷键
- python快速入门答案-Python 开发 14 天快速入门
- mysql数据清洗_mysql数据库如何实现亿级数据快速清理
- 详解Node.js包的工程目录与NPM包管理器的使用_node.js
- C++学习笔记:(四)运算符重载 类型转换
- 晋升,结婚,出书,买房,这就是我的2019年!
- 分享12306抢票心得-最终篇
- Windows下的修改Tomcat的可用内存
- Python 完整学习路线,非常赞!
- 高等数学-用等价无穷小求极限题集
- Acer 4750 安装黑苹果_黑苹果 MacOS 10.15 Catalina安装教程
- csdn博客改用户名,取名字慎重
- 服务器流量异常的原因分析和解决办法
- 如何将CSDN博客下载为PDF文件
- 网景创始人:软件应用无所不在 正吞噬整个世界
- 读书笔记002:《伤寒论》-十二经络