说明:在学习泛型这一知识点中,主要参考自《疯狂Java讲义》第7章P307-P330的泛型内容,因为是跳着阅读,所以前面的一些名词不是特别清楚,这里也做出适当备注,供自己识记与理解。

1.泛型

理解:说到泛型,感觉最初是为了解决Java集合的一个缺点——当我们想要把一个对象放进集合里面的时候,集合就会忘记这个对象的数据类型,再次把它取出来时,它的编译类型就会变成Object类型(运行类型不会变)。记住我们的目标是:在集合里面存储不会被忘记数据类型的各种对象。例子:

 1 package FanXing;
2
3 public class ListErr {
4 public static void main(String[] args) {
5 List strList = new ArrayList();
6 strList.add("泛型主题讨论");
7 strList.add(017);//这里不小心把一个Integer对象放在了集合里面,可能报类型强制转换异常ClassCastException
8 for(int i=0;i<strList.size();i++)
9 {String str = (String)strList.get(i);
10 }
11 }
12 }
编译信息:

Exception in thread "main" java.lang.Error: Unresolved compilation problems:
List cannot be resolved to a type
ArrayList cannot be resolved to a type

at FanXing.ListErr.main(ListErr.java:5)

图中的红色框已经提示我们需要用什么区解决所面临的的问题了。

为了达到我们的目标,我们想到了可以手动实现编译时去检查类型。

例子:(既然会发生异常那我们就在运行前先检查,我们这里先创建一个对象List,让它只保存字符串类型,这样就可以扩展ArrayList类)

package FanXing;

import java.util.ArrayList;
import java.util.List; class StrList{
private List strList = new ArrayList();
public boolean add(String ele)//定义StrList的add方法,只添加字符串
{
return strList.add(ele);
}
public String get (int index)
{
return (String)strList.get(index);
}
public int size()
{
return strList.size();
}
} public class ListErr {
public static void main(String[] args) {
StrList strList = new StrList();
strList.add("泛型主题讨论");
//strList.add(017);如果没有这一句,代码可以成功被编译,否组会报错。
System.out.println(strList);
for(int i=0;i<strList.size();i++)
{String str = strList.get(i);
}
}
}

上面的代码中我们定义的StrList类实现了编译时的异常检查,当编译到strList.add(017);时,程序试图将一个Integer对象加入到StrList集合中,程序在这里会无法编译通过,因为StrList只接受String的对象。

不过,既然只接受String对象的时候可以编译通过,说明这个方法还是有用的,但是,这种方法虽然有效,局限性却非常明显——我们需要去定义大量的List子类。

虽然这样也可以实现我们的目标:在集合里面存储不会被忘记数据类型的各种对象。不过这样非常非常麻烦,这个时候,我们的泛型就被设计出来了,有了它,我们的目标可以轻易实现。

package FanXing;

import java.util.ArrayList;
import java.util.List; public class ListErr {
public static void main(String[] args) {
List<String> strList = new ArrayList<String>();//创建一个List集合,只保留字符串
strList.add("泛型主题讨论");
for(int i = 0;i<strList.size();i++)
{
String str = strList.get(i);
}
}
}

很显然这样代码简化了很多,List<String>说明这是一个String类型的List。

所以这里我们可以归纳出,如果List<>尖括号里面是其他类型的话也是同理,即有了一个JDK1.5以后引入的概念:

Java泛型(generics)【Java的参数化类型】 :是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。

泛型最大的好处是可以提高代码的复用性。以List接口为例,我们可以将String、Integer等类型放入List中,如不用泛型,存放String类型要写一个List接口,存放Integer要写另外一个List接口,泛型可以很好的解决这个问题。
2.深入泛型:

①定义泛型接口、类

public interface List<E>//定义接口,指定形参E,在这个接口里面E可以作为泛型使用
{
void add(E,x);
Iterator<E> iterator();//A
...
}
public interface Iterator<E>//在这个接口里,E可以作为类型使用
{
E next();
boolean hasNext();
...
}
public interface Map<K,V>//K,V可以作为类型使用
{
Set<K> keySet()//B
V put (K key ,V value)
...
}

可以发现:在A、B处方法声明返回值类型是Iterrator<E>和Set<K>,说明他们是一种特殊的数据类型,可以认为是Iterrator和Set类型的子类。

例如:使用List类型的时候,为E形参传入String实参,则产生了一个新的类型List<String>,把它想象成E全部被String取代的特殊的List子接口。

public interface ListString extends List
{
void add(String x);
Iterator<String> iterator();
...
}

这样虽然只是设置了一个List<E>接口,实际实验时却是可以产生无数多个List 子接口。

【注意】包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态的生成无数多个逻辑子表,但这种子类在物理上并不存在。也就是说,List<String>不会被替代成ListString,系统并没有进行源代码复制。

②从泛型类派生子类

当定义完泛型接口和泛型父类额时候,我们就可以为接口创建实现类或者从父类派生出子类,不过使用接口和父类的时候,不能再包含类型参数。

//错误演示

public class A extends List<E>{
//A继承List,List不能跟类型形参
} //正确演示1
public class A extends List<String>{
//A继承List,为List的E形参传入String
} //正确演示2
public class A extends List{
//A继承List,也可以不为类型形参传入实际的类型参数,不过可能会出现unchecked警告
}

③并不存在泛型类

前面有提到,可以把List<String>类当成是List的子类,这里可能会给大家带来误解,实际上,系统并没有为List<E>生成新的class文件,而且也不会把它当成新的类来处理。这里给一个验证:

package FanXing;

import java.util.ArrayList;
import java.util.List; public class ListErr {
public static void main(String[] args) { List<String> aaa = new ArrayList<>();
List<Integer> aaa1 = new ArrayList<>();
System.out.println(aaa1.getClass()==aaa1.getClass()); }
}

从输出true可以看出,不管为泛型的类型形参传入哪一种类型实参,对于Java来说,他们依然被当做同一个类来处理,在内存中也只占用一块内存空间。

3.类型通配符

package FanXing;

import java.util.List;

public class test {
public void test1 (List c)
{
for(int i = 0;i<c.size();i++)
{
System.out.println(c.get(i));
}
}
}

这是一个普通的遍历List集合的代码,在编译过程中出现了一个泛型警告,因为在这里使用List接口时没有传入实际的参数类型。

修改后:

package FanXing;

import java.util.List;

public class test {
public void test1 (List<?> c)
{
for(int i = 0;i<c.size();i++)
{
System.out.println(c.get(i));
}
}
}

看到原来的List变成了List<?>,这里就引入了类型通配符的概念。

类型通配符就是一个“?”,它的元素类型可以匹配任何类型。

比如,当使用List<?>时,List就成了任何泛型List的父类,比如List既是List<String>的父类,又是List<Integer>的父类,但是,类型之间没有继承关系,String是Object的子类,List<String>不是List<Object>的子类。

①设置类型通配符的上限

格式:List<? extends XXX>它表示所有XXX泛型List的父类

②设定类型形参的上限

例子:

public class List<T extends Number & java.io.Serializable>
{
...//表明T类型必须是Number类或者其子类,并且必须实现java.io.Serializable接口
}

注意:为类型参数指定多个上限时,所有的接口上限必须位于类上限之后。

4.泛型方法

格式:

修饰符 <T,S> 返回值类型 方法名 (形参列表)
{
//方法体
}

泛型方法和类型通配符的区别:

①大多数时候都可以用泛型方法来替换通配符
②使用通配符情况:用来支持灵活的子类化
③泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。
④形参a的类型或返回值的类型依赖于另一个形参b的类型,则b的类型声明不应该使用通配符,因为使用通配符表示类型b不确定,那么a的类型也不能确定,这时候要考虑使用泛型方法。
⑤类型不被依赖时,使用通配符。

泛型方法与方法重载:

public class MyUtils {
// (1)
public static <T> void copy(Collection<T> dest, Collection<? extends T> src) {...}
// (2)
public static <T> T copy(Collection<? super T> dest, Collection<T> src) {...}
public static void main(String[] args) {
List<Number> ln = new ArrayList<>();
List<Integer> li = new ArrayList<>();
copy(ln, li); // 这里会编译报错
}
}

允许根据方法参数泛型不同进行方法重载,但是调用时,如果编译器分不清该调用哪个方法则编译报错,上面代码中有两个copy方法,调用的时候,编译器既可以调用第一个copy ,也可以调用第二个copy,这样它就无法确定调用哪个,就会引起编译报错。

5.擦除和转换

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。

参考链接:Java泛型使用详解

【备注1】:Java的编译类型和运行类型的理解。

Java的编译时类型是由声明变量时使用的类型决定,运行时类型是由实际赋值的对象所决定。参考链接:Java的编译类型和运行类型

【备注2】:Java泛型中K T V E ? 分别代表的含义:

E – Element (在集合中使用,因为集合中存放的是元素)

T – Type(Java 类)

K – Key(键)

V – Value(值)

N – Number(数值类型)

? – 表示不确定的java类型(无限制通配符类型)参考链接:泛型相关

【备注3】:为什么说在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型参数。

初步理解:

因为泛型是要在对象创建的时候才知道是什么类型的,而对象创建的代码执行先后顺序是static的部分,然后才是构造函数等等。所以在对象初始化之前static的部分已经执行了,如果你在静态部分引用的泛型,那么毫无疑问虚拟机根本不知道是什么东西,因为这个时候类还没有初始化。因此在静态方法、数据域或初始化语句中,为了类而引用泛型类型参数是非法的。

实际原因:

静态变量是被泛型类所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。假设允许类型参数作为静态变量的类型。那么考虑下面一种情况:

MyClass<String> class1 = new MyClass<String>();

MyClass<Integer> class2 = new MyClass<Integer>();

class1.myStaticVar = "hello";

class2.myStaticVar = 5;

由于泛型系统的类型擦除(type erasure)。myStaticVar被还原成Object类型,然后当调用class1.myStaticVar= "hello"; 编译器进行强制类型转换,即myStaticVar = (String)"hello";接着调用class2.myStaticVar语句时,编译器继续进行强制类型转换,myStaticVar = (Integer)Integer.valueOf(5); 此时myStaticVar是String类型的,当然该语句会在运行时抛出ClassCastException异常,这样一来存在类型安全问题。因此泛型系统不允许类的静态变量用类型参数作为变量类型。

当然,静态泛型方法也不允许。
参考链接:

有关静态不允许使用类型参数的讨论

为什么类型参数不能作为静态变量的类型

【备注4】:Java泛型中上下界限定符extends 和 super的理解:

<? extends T>表示类型的上界,表示参数化类型可能是T或者T的子类;

<? super T>表示类型的下界,表示参数化类型是此类型的超类型(父类型),直至Object。

PECS原则:

如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)

如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)

如果既要存又要取,那么就不要使用任何通配符。

【备注5】:异常类

异常类一般分为两种:异常(Exception)和错误(Error)

Exception就用try&catch&finally来处理,先在try中运行代码,catch处理可能出现异常,finally是一定会执行到里面的代码。
常见的异常有:
NumberFormatException(数字格式异常)
IndexOutOfBoundsException(数组越界异常)
ArithmeticException(除零异常)
RuntimeException(运行时异常)
异常常用的方法:
getMessage():返回异常的详细描述字符串。
printStackTrace():跟踪栈详细输出到标准错误输出
printStackTrace(PrintStream s):跟踪栈详细输出到标准错误输出到指定的输出流
getStackTrace():返回异常的跟踪栈信息。

Error一般是由虚拟机造成的系统崩溃的。

下一步学习拓展及计划:

1.总结讨论并做成思维导图

2.理解学习中一直提到的异常这一章的内容

3.擦除的实例(自己尝试用一个例子实践)

Java泛型主题讨论的更多相关文章

  1. Java泛型总结

    1. 什么是泛型?泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的 ...

  2. java泛型的讲解

    java泛型 什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指 ...

  3. Java基础学习总结(83)——Java泛型总结

    1. 什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型 ...

  4. Java深度历险(五)——Java泛型

      作者 成富 发布于 2011年3月3日 | 注意:QCon全球软件开发大会(北京)2016年4月21-23日,了解更多详情!17 讨论 分享到:微博微信FacebookTwitter有道云笔记邮件 ...

  5. Java泛型-内部原理: 类型擦除以及类型擦除带来的问题

    一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...

  6. java泛型 8 泛型的内部原理:类型擦除以及类型擦除带来的问题

    参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...

  7. Java 泛型 协变性、逆变性

    Java 泛型 协变性.逆变性 @author ixenos 摘要:协变性.协变通配符.协变数组.协变返回值 协变性.逆变性和无关性 在面向对象的计算机程序语言中,经常涉及到类型之间的转换,例如从具体 ...

  8. 令人费解的java泛型

         对于我们java中的泛型,可能很多人知道怎么使用并且使用的还不错,但是我认为想要恰到好处的使用泛型,还是需要深入的了解一下它的各种概念和内部原理.本文将尽可能的囊括java泛型中的重要的概念 ...

  9. 解析令人费解的java泛型

    对于我们java中的泛型,可能很多人知道怎么使用并且使用的还不错,但是我认为想要恰到好处的使用泛型,还是需要深入的了解一下它的各种概念和内部原理.本文将尽可能的囊括java泛型中的重要的概念.主要内容 ...

随机推荐

  1. sqlserver和oracle修改表结构

    sqlserver和oracle修改表结构常用SQL Server:1.增加列  ALTER TABLE users ADD address varchar(30);2.删除列  ALTER TABL ...

  2. Python导入模块的几种方法

    Python 模块 Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句. 模块让你能够有逻辑地组织你的 Python 代 ...

  3. matlab中reshape 重构数组

    来源:https://ww2.mathworks.cn/help/matlab/ref/reshape.html?searchHighlight=reshape&s_tid=doc_srcht ...

  4. 【小白学PyTorch】21 Keras的API详解(上)卷积、激活、初始化、正则

    [新闻]:机器学习炼丹术的粉丝的人工智能交流群已经建立,目前有目标检测.医学图像.时间序列等多个目标为技术学习的分群和水群唠嗑答疑解惑的总群,欢迎大家加炼丹兄为好友,加入炼丹协会.微信:cyx6450 ...

  5. springCloud项目搭建

    新建父maven项目 groupId:pers.xzp.springCloudartifactId:springCloud 父项目中仅仅需要一个pom文件,用于管理模块的依赖统一.继承等 编辑pom文 ...

  6. devops工具链概述

    1. devops工具链概述  1)devops工具篇 2) 持续集成 3) 持续交付 4) 持续部署 2. devops工具链概述

  7. JavaScript常用对象介绍

    目录 对象(object) 对象的创建方式 点语法 括号表示法 内置对象 Array 数组创建方式 检测数组 转换方法 分割字符串 栈方法 队列方法 重排序方法 操作方法 位置方法 迭代方法 Stri ...

  8. 多测师讲解selenium_运行报告相出错归纳_高级讲师肖sir

    <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'> EETraceback (most recent c ...

  9. Oracle函数总结

    <Trunc()> 描       述(实际应用):截取小数或者日期整数 简      介:https://baike.baidu.com/item/trunc/9657216?fr=al ...

  10. 【源码项目+解析】C语言/C++开发,打造一个小项目扫雷小游戏!

    一直说写个几百行的小项目,于是我写了一个控制台的扫雷,没有想到精简完了代码才200行左右,不过考虑到这是我精简过后的,浓缩才是精华嘛,我就发出来大家一起学习啦,看到程序跑起来能玩,感觉还是蛮有成就感的 ...