Java泛型的那些事
1.泛型概述
1.1.为什么使用泛型
没有泛型,在编写代码时只能使用具体类型或Object类型,无法做到使用者想要使用什么类型就是类型。比如:创建一个方法,形参需要指定需要使用的数据类型,在创建方法之初就已经决定了该方法可以处理的数据类型,这大大限制了编程的灵活性。正因如此,才出现了在使用时才决定具体类型是什么的泛型编程。
1.2.泛型是什么
泛:广泛、普遍,非具体的东西,泛型就是定义之初用符号表示不具体的类型,在使用的时候才动态地指定具体的类型。更应该明白这种泛型编程设计思想,使用泛型带来的好处是代码更加简洁、更加灵活、使程序更加健壮(编译期没警告,运行期不会出现类强转异常--ClassCastException)。
2.泛型接口、类、方法
泛型允许在定义接口、类、方法时使用,将在声明变量、创建对象、调用方法时动态地指定。
2.1.泛型接口
定义泛型接口:比如集合中的List接口
// 定义接口时指定泛型:E,E类型在接口中就可以作为类型使用
public interface List<E> extends Collection<E>{
……
boolean add(E e);
Iterator<E> iterator();
……
}
// 定义接口时指定泛型:K 和 V,K和V类型在接口中就可以作为类型使用
public interface Map<K,V>{
……
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
……
}
使用泛型接口:List接口的泛型类型E,在使用时指定为具体类型String
public static void main(String[] args) {
List<String> list = new ArrayList<>();// 指定泛型类型E=String
list.add("我只认识字符串");//boolean add(E e); 等价于boolean add(String e);
Iterator<String> iterator = list.iterator();//Iterator<E> iterator();
while (iterator.hasNext()){
String next = iterator.next();//不需要强转为String
System.out.println(next);
}
}
关于泛型接口Map<K,V> 集合怎么用,就自行编写感受下。
2.2.泛型类
普通泛型类
定义泛型类
public class DemoFx<D> {
private D dd;
public D getDd(){
return this.dd;
}
public void setDd(D dd){
this.dd = dd;
}
}
使用泛型类
public static void main(String[] args) {
DemoFx<String> stringDemoFx = new DemoFx<>();
stringDemoFx.setDd("我是字符串类型");
System.out.println(stringDemoFx.getDd());
}
泛型类的继承与实现
定义泛型类:以ArrayList 类为例,继承泛型抽象类和实现泛型接口:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
……
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
……
}
使用泛型类
public static void main(String[] args) {
List<String> list = new ArrayList<String>();// 指定泛型类型E=String
list.add("我只认识字符串");
String s = list.get(0);// 返回值为String
}
2.3.泛型方法
定义泛型方法:还是ArrayList案例
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
……
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of as runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
……
}
使用泛型方法:public <T> T[] toArray(T[] a)
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("s1");
list.add("s2");
list.add("sn");
// public <T> T[] toArray(T[] a)
String[] strings = list.toArray(new String[list.size()]);
System.out.println(Arrays.asList(strings));
}
3.类型通配符
3.1.使用类型通配符
通配符表示符号是问号<?>,它是未知类型,可以匹配任何类型,也称为无界通配符。
对比”通配符“和”泛型“创建的方法
// 通配符定义
public void foreach(List<?> list){
for (int i =0 ;i<list.size();i++) {
Object o = list.get(i);
System.out.println(o.toString());
}
}
// 泛型定义
public <T> void foreach2(List<T> list){
for(T t : list){
System.out.println(t.toString());
}
}
// 使用
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("s1");
list.add("s2");
list.add("sn");
Demo demo = new Demo();
demo.foreach(list); // 通配符
demo.foreach2(list); // 泛型
}
通配符和泛型都可以实现相同的效果,并且泛型方法还可以使用本身定义的泛型类型,而通配符的”?“不可以当作数据类型来使用,所以通配符方法案例中只能用Object来接收list的元素,这也是通配符的缺点:无法确定未知类型是什么类型。
所以通配符的出现到底有什么用呢?
通配符为泛型的一种特例,无需定义既可在形参中使用的未知类型。
泛型和通配符的区别
Java编译器把泛型【T】推断成T类型,在代码块中允许出现 T类型变量;而把通配符【?】推断成未知类型,不存在 ?类型变量; Class<T>需要依赖于T,需要在方法声明时指定<T>,而Class<?>则不需要;
这样可能更好理解泛型和通配符:泛型 强调的是类型,通配符 强调的是符号。
Class<?> 表示任意类型,但又不等同于Class<Object>,前者在类型不匹配的情况下只能够插入null,但是后者可以插入Object或任何Object对象的子类。
例如:不能往List<?> list里添加任意类型的对象,除了null
3.2.类型上限
通配符上限:<? extends Demo> ,通配符【?】的上限是Demo类型,既是<? extends Demo> 的范围是Demo或其子类类型。
泛型上限:<T extends Demo> ,和通配符理解一样。类型上限如图
案例:
创建三个类DemoFather、Demo、DemoChildren,关系如上图
public class DemoTest {
public static void main(String[] args) {
List<DemoChildren> demoChildrens = new ArrayList<>();
demoChildrens.add(new DemoChildren());
demoChildrens.add(new DemoChildren());
DemoTest test = new DemoTest();
test.testDemo(demoChildrens); // 通配符
test.testDemo2(demoChildrens);// 泛型
}
// 通配符上限:控制list 集合允许的类型范围为Demo或其子类
public void testDemo(List<? extends Demo> list){
// 若无上限,这里只能用Object类型代替Demo类型
for (Demo demo : list){
System.out.println(demo.toString());
}
}
// 泛型上限:控制list 集合允许的类型范围为Demo或其子类
public <T extends Demo> void testDemo2(List<T> list){
for (T t : list){
System.out.println(t.toString());
}
// or
for(Demo demo:list){
System.out.println(demo.toString());
}
}
}
泛型的上限是在定义时确定上限<T extends Demo>;通配符直接在形参上确定上限<? extends Demo>。其实都很好理解,类型上限就是在一般写法的基础上加入范围“上限”,既是 extends xxx。
源码的一些例子
// 接口泛型上限
public interface ObservableArray<T extends ObservableArray<T>> extends Observable {……}
// 抽象类泛型上限
public abstract class ArrayListenerHelper<T extends ObservableArray<T>> extends ExpressionHelperBase {……}
public abstract class CellBehaviorBase<T extends Cell> extends BehaviorBase<T> {……}
// 方法泛型上限
public static <T extends Number> ReadOnlyLongProperty readOnlyLongProperty(final ReadOnlyProperty<T> property) {……}
// 通配符上限
void putAll(Map<? extends K, ? extends V> m);
3.3.类型下限
通配符下限:<? super Demo> ,通配符【?】的下限是Demo类型,既是<? super Demo> 的范围是Demo的父类类型。
泛型下限:无。主要是因为类型下限会令人困惑并且不是特别有用。为什么类型参数没有下限的一些解释:http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ107
public static void main(String[] args) {
Demo demo = new Demo();
List<Demo> demos = new ArrayList<>();
demos.add(demo);
DemoTest test = new DemoTest();
test.testSuper(demos);
DemoFather demoFather = new DemoFather();
List<DemoFather> demoFathers = new ArrayList<>();
demoFathers.add(demoFather);
DemoTest test2 = new DemoTest();
test2.testSuper(demoFathers);
}
public void testSuper(List<? super Demo> list){
// 虽然有下限,但无法直接使用Demo类型接收参数
for (Object obj : list){
System.out.println(obj.toString());
}
}
虽然有下限,但无法直接使用Demo类型接收参数。这就像“向上转型”和“向下转型”,向上转型是自动的,向下转型需要强转;类型上限可以使用最大类型(父类)接收比它小的类型(子类),类型下限不可以使用最小类型(子类)接受可能比它大的类型(父类)。
源码的一些例子
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
……
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
}
public class Arrays {
public static <T> void sort(T[] a, int fromIndex, int toIndex,
Comparator<? super T> c) {
if (c == null) {
sort(a, fromIndex, toIndex);
} else {
rangeCheck(a.length, fromIndex, toIndex);
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, fromIndex, toIndex, c);
else
TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
}
}
}
关于泛型字母E、K、V、T是什么?
E表示Element, K表示Key, V表示Value, T表示Type, N表示Number, ? 表示 未知类型
除了【?】,其他泛型符号你写成字符串都可以,但要注意可读性,一般都是使用单个大写字母表示,不然代码反而不简洁、不易阅读。
4.泛型实现原理--类型擦除
把一个具有泛型信息的对象 赋给 另一个没有泛型信息的变量引用,类型信息都将被擦除。
案例一:不指定泛型上限,类型擦除后为Object
public static void main(String[] args) {
// 定义集合泛型E = String
List<String> stringArrayList = new ArrayList<>();
stringArrayList.add("test1");
// 获取到的类型为:String
String s = stringArrayList.get(0);
// 把带有泛型信息的stringArrayList 对象赋给不确定泛型的List
List listObject = stringArrayList;
// listObject 只知道get的类型为Object,而不是String
Object obj = listObject.get(0);
}
案例二:指定泛型上限,类型擦除后为上限的类型
public class DemoFather {}
public class Demo extends DemoFather{}
public class DemoChildren<T extends DemoFather> {
private T t;
public T getT(){
return this.t;
}
public void setT(T t){
this.t= t;
}
}
// 测试public class DemoTest {
public static void main(String[] args) {
//class DemoChildren<T extends DemoFather>,指定泛型T=Demo类型
DemoChildren<Demo> demoChildren = new DemoChildren<Demo>();
// 拿到的方法类型确实是T=Demo类型
Demo demo = demoChildren.getT();
// 把带有泛型信息的 demoChildren 对象赋给不确定泛型的demoChildren2
DemoChildren demoChildren2 =demoChildren;
// 再来获取方法的类型时,变为了上限的DemoFather类型
DemoFather demoFather = demoChildren2.getT();
}
}
结论:
“
指定泛型上限时,类型擦除后为上限的类型;反之是Object类型,因为Java中所有类都默认继承了Object类。
”
所以案例二的泛型类在编译阶段是长这样的
public class DemoChildren {
private DemoFather t;
public DemoFather getT(){
return this.t;
}
public void setT(DemoFather t){
this.t= t;
}
}
// 原来的泛型类,对比一下
public class DemoChildren<T extends DemoFather> {
private T t;
public T getT(){
return this.t;
}
public void setT(T t){
this.t= t;
}
}
Java泛型的那些事的更多相关文章
- Java泛型总结
1. 什么是泛型?泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的 ...
- java泛型的讲解
java泛型 什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指 ...
- Java泛型:类型擦除
类型擦除 代码片段一 Class c1 = new ArrayList<Integer>().getClass(); Class c2 = new ArrayList<String& ...
- java 泛型 窜讲
一.为什么使用泛型 复用性:泛型的本质就是参数化类型,因而使用编写的泛型代码可以被许多不同类型的对象所复用. 安全性:在对类型Object引用的参数操作时,往往需要进行显式的强制类 ...
- Java泛型知识点全方位总结
前言 我一直认为泛型是编程语言设计中一个非常基本和重要的概念.Java中的泛型是什么?他们为什么在那里?他们是如何发展的?在学习基础知识时,对仿制药的透彻理解是非常重要的.因此,我阅读了<Jav ...
- Net is as typeof 运行运算符详解 net 自定义泛型那点事
Net is as typeof 运行运算符详解 概述 在了解运行运算符的前提我们需要了解什么是RTTI ,在任何一门面向对象的语言中,都有RTTI这个概念(即 运行时). RTTI(Run-Ti ...
- Java基础学习总结(83)——Java泛型总结
1. 什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型 ...
- Java泛型解析(01):认识泛型
Java泛型解析(01):认识泛型 What Java从1.0版本号到如今的8.中间Java5中发生了一个非常重要的变化,那就是泛型机制的引入.Java5引入了泛型,主要还是为了满足在199 ...
- JAVA泛型知识--> <? extends T>和<? super T>
<? extends T> 和 <? super T> 是Java泛型中的“通配符(Wildcards)” 和 “边界(Bounds)”的概念 <? extends T& ...
随机推荐
- 【Android】安卓中的存储
[Android]安卓中的存储 1.存储在App内部 最简单的一种.在尝试过程中发现,手机中很多文件夹都没有权限读写.我们可以将我们需要写的文件存放到App中的files文件夹中,当然我们有权限在整个 ...
- 系统信号SIGHUP、SIGQUIT、SIGTERM、SIGINT的场景
SIGHUP:hong up 挂断.本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联.登录Linux时 ...
- 集合框架-Vector集合
1 package cn.itcast.p1.vector.demo; 2 3 import java.util.Enumeration; 4 import java.util.Iterator; 5 ...
- Android开发----使用 Room 将数据保存到本地数据库
Room介绍以及不使用SQLite的原因 Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库. 处理大量结构化数据的应用可极大地受益于 ...
- Kubernetes的Pod进阶(十一)
一.Lifecycle 官网:https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/ 通过前面的分享,关于pod是什么相信看 ...
- 深入学习python内存管理
深入Python的内存管理 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 语言的内存管理是语言设计的一个重要方面.它是决定语 ...
- oracle中的常用函数、字符串函数、数值类型函数、日期函数,聚合函数。
一.字符串的常用函数. --一.oracle 字符串常用函数 --1. concat 连接字符串的函数,只能连接[两个]字符串. 字符写在括号中,并用逗号隔开! --2."||"符 ...
- JVM 问题分析思路
1. 前言 工作中有可能遇到 java.lang.OutOfMemoryError: Java heap space 内存溢出异常, 本文提供一些内存溢出的分析及解决问题的思路. 常见异常如下: 20 ...
- Uwl.Admin.Core开源框架(二) 使用QuartzNet
Uwl.Admin.Core中使用QuartzNet定时任务模块: 本文负责讲解RabbitMQ的使用 Uwl.Admin.Core使用的技术有: *.Async和Await 异步编程 *.Repos ...
- PHP扩展开发编译环境的搭建
PHP允许使用扩展的方式,直接使用C语言开发PHP的功能,由于编译的问题,很多底层和框架的基础工作如果用PHP必然会带来额外的开销,降低系统的吞吐量,必然yaf就是这样的思路,把框架封装在扩展里,让系 ...