摘要

本文根据《Java核心技术 卷一》一书的第八章总结而成,部分文章摘抄书内,作为个人笔记。

文章不会过于深入,望读者参考便好。

为什么要使用泛型程序设计

泛型程序设计(Generic programming) 意味着编写的代码可以被很多不同类型的对象所重用。

类型参数的好处

在没有泛型类之前,ArrayList类只维护一个Object引用的数组:

public class ArrayList {
private Object[] elementData; // 用于存放数据
public Object get(int i) { . . . }
public void add(Object o) { . . . }
...
}

问题

1.获取一个值时必须进行强制类型转换

2.这里没有错误检査。可以向数组列表中添加任何类的对象,如果数组的类型不一致,将 get 的结果进行强制强制类型,就会错误。

泛型提供了一个更好的解决方案: 类型参数:

ArrayList<String> array = new ArrayList<String>():

利用类型参数的信息,我们就可以在添加数据的时候保持类型统一,调用get方法时候也不需要进行强制类型转换,因为我们在初始化的时候就定义了类型,编译器识别返回值的类型就会帮我们转换该类型。

定义一个简单泛型类

public class Pair<T> {
private T num; public Pair(T num) { this.num = num; }
public T getNum() { return num; }
public void setNum(T num) { this.num = num; }
}

我们可以看到,在Pair类名后面添加了一个,这个是泛型类的类型变量,而且还可以有多个类型变量,如

public class Pair<T,U> {
...
}

如果我们实例化Pair类,例如:

new Pair<String>;

那么我们就可以把上述的Pair类想象成如下:

public class Pair<String> {
private String num; public Pair(String num) { this.num = num; }
public String getNum() { return num; }
public void setNum(String num) { this.num = num; }
}

是不是很简单呢?在Java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型,T、U、S表示任意类型。

泛型方法

定义一个带有类型参数的方法

    public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}

可以看到类型变量(< T >)放在修饰符( public static )的后面,返回类型(T)的前面。泛型方法可以定义在普通类或泛型类中。

类型变量的限定

如果我们需要对类型变量加以约束,例如:传入的变量必须实现Comparable接口,因为需要该变量调用compareTo的方法。这样我们就可以使用extends关键字对变量进行限定。

    public <T extends Comparable> T max(T a) {
a.compareTo(...);
...
}

无论变量需要限定为继承某个类或者实现某个接口,都是使用extends关键字进行限定。

泛型代码和虚拟机

类型擦除

无论我们在代码中怎么定义一个泛型类、泛型方法,都提供了一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类姓名。如 <T> 的原始类型为 Object,<T extends MyClass>的原始类型为MyClass。

代码就像下面这样:

public class Pair<T> {
private T property; public Pair(T property) {
this.property = property;
}
}

类型擦除后:

public class Pair<Object> {
private Object property; public Pair(Object property) {
this.property = property;
}
}

翻译泛型表达式

如果擦除返回类型,编译器会插入强制类型转换,就像下面这样:

Pair<Employee> buddies = . .
Employee buddy = buddies.getFirst();

擦除getFirst的返回类型后将返回Object类型,但是编译器将自动帮我们强制类型转换为Employee。

所以:编译器把这个方法执行操作分为两条指令:

对原始方法Pair.getFirst的调用

将返回的Object类型强制转换为Employee类型

小节总结:

虚拟机中没有泛型,只有普通的类和方法

所有的类型参数都用他们的限定类型替换

为保持类型安全性,必要时插入强制类型转换

桥方法被合成来保持多态(本文没有讲到,不过桥方法可以忽略,Java编写不规范才会有桥方法生成)

约束与局限性

不能用基本类型实例化类型参数

不可以用八大基本数据类型去实例化类型参数,你也没见过ArrayList<int>这样的代码吧。只有ArrayList<Integer>。原因是因为基本数据类型是不属于Object的。所以只能用他们的包装类型替换。

运行时类型查询只适用于原始类型

所有的类型查询只产生原始类型,因为在虚拟机没有所谓的泛型类型。

例如:

	Pair<String> pair = new Pair<String>("johnson");
if (pair instanceof Pair<String>) {} // Error
if (pair instanceof Pair<T>) {} // Error
if (pair instanceof Pair) {} // Pass

不能创建参数化类型的数组

    Pair<String>[] pairs = new Pair<String>[10]; //error

为什么不能这样定义呢?因pairs的类型是Pair[],可以转换为Object[],如果试图存储其他类型的元素,就会抛出ArrayStoreException异常,

	pairs[0] = 10L; //抛异常

总之一句话,不严谨。所以不能创建参数化类型的数组。

不能实例化类型变量

不能使用 new T(...)、new T[...] 或 T.class。因为类型擦除后,T将变成Object,而且我们肯定不是希望实例化Object。

不过在Java8之后,我们可以使用Supplier<T>接口实现,这是一个函数式接口,表示一个无参数而且有返回类型为T的函数:

public class Pair<T> {
private T first;
private T second; private Pair(T first, T second) {
this.first = first;
this.second = second;
} public static <T> Pair<T> makePair(Supplier<T> constr) {
return new Pair<>(constr.get(), constr.get());
} public static void main(String[] args) {
Pair<String> pair = Pair.makePair(String::new);
}
}

泛型类中的静态上下文中类型变量无效

不能在静态域或方法中引用类型变量。例如:

public class Pair<T> {
private static T instance; //Error public static T getInstance() { //Error
if (instance == null)
instance = new Pair<T>();
return instance;
}
}

因为类中的类型变量(<T>)是在对象中的作用域有效,而不是在类中的作用域有效。如果要使用泛型方法,可以参照文章上面的泛型方法哦~

不能抛出或捕获泛型类的实例

即不能抛出也不能捕获泛型类的对象,甚至扩展Throwable都是不合法的:

public class Pair<String> extend Exceotion {} //Error
public static <T extends Throwable> void doWork(Class<T> t) {
try {
...
} catch (T e) { //Error
...
}
}

但是在抛出异常后对异常使用类型变量是允许的(个人感觉没见过这样的代码)。

public static <T extends Throwable> void doWork(Class<T> t) throw T { //Pass
try {
...
} catch (Exception e) {
throw e;
}
}

泛型类型的继承规则

如Manager类继承Employee类。但是Pair<Employee>和Pair<Manager>是没有关联的。就像下面的代码,就会提示报错,传递失败:

        Pair<Manager> managerPair = new Pair<Manager>();
Pair<Employee> employeePair = managerPair; //Error

通配符类型

通配符概念

通配符类型中,允许类型参数变化,使用 ? 标识通配符类型:

Pair<? extends Employee>

若Pair类如下

public class Pair<T> {
private T object; public void setObject(T object) {
this.object = object;
}
}

那么使用通配符可以解决泛型类型的继承规则问题,如:

        Pair<Manager> managerPair = new Pair<Manager>();
Pair<? extends Employee> employeePair = managerPair; //Pass
Manager manager = new Manager();
employeePair.setObject(manager); //Error

使用<? extends Employee>,编译器只知道employeePair 是Employee的子类,但是不清楚具体是什么子类,所以最后一步employeePair.setObject(manager)不能执行。

通配符的超类型限定

通配符还有一个附加的能力,就是可以指定一个超类型限定,如:

public class Pair<T> [
...
public static void salary(Pair<? super Manager> result) {
//...
}
}

<? super Manager>这个通配符为Manager的所有超类型(包含Manger),例如:

        Pair<Manager> managerPair = new Pair<Manager>();
Pair<Employee> employeePair = new Pair<Employee>(); Pair.salary(managerPair); //Pass
Pair.salary(employeePair); //Pass // 假如 ManagerChild为Manager子类
Pair<ManagerChild> managerChildPair = new Pair<ManagerChild>();
Pair.salary(managerChildPair); //Error

无限定通配符

无限定通配符,如:Pair<?>,当我们不需要理会他的实际类型时候,就可以使用无限定通配符,上代码:

    public static boolean hasNull(Pair<?> pair) {
return pair.getObject() == null;
}

说实话,通配符搞得我头昏脑胀的,反复不断地看文章,才开始慢慢看懂,我太难了。。。



文章到这里就结束啦,不知道各位小伙伴看懂了没,没看懂的话可能是我的功底和文章写作能力还有待提高,小伙伴们也可以去看一下《Java核心技术 卷一》这本书呢,感觉还是挺不错的。最近把这本书捡起来看也是发现基础是非常重要的,先把基础沉淀好了,再学习其他的技术点也会更容易入手,也会知其然知其所然。最近非常喜欢的一句话,送给大家:“万丈高楼平地起,勿在浮沙筑高台”。

个人博客网址: https://colablog.cn/

如果我的文章帮助到您,可以关注我的微信公众号,第一时间分享文章给您

Java核心技术第八章-泛型的更多相关文章

  1. Java核心技术第八章——泛型程序设计(1)

    1.泛型程序设计 泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用.例如:不希望为了聚集String和Integer对象分别设计不同的类.(个人觉得此处说的聚集译为:创建一个对象,属性可以为 ...

  2. Java核心技术点之泛型

    1. Why ——引入泛型机制的原因 假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过了一阵,我们想要实现一个大小 ...

  3. Java核心技术点之集合框架

    1. 概述     Java集合框架由Java类库的一系列接口.抽象类以及具体实现类组成.我们这里所说的集合就是把一组对象组织到一起,然后再根据不同的需求操纵这些数据.集合类型就是容纳这些对象的一个容 ...

  4. 读《java核心技术卷一》有感

    过去一个多月了吧.才囫囵吞枣地把这书过了一遍.话说这书也够长的,一共706页.我从来不是个喜欢记录的人,一直以来看什么书都是看完了就扔一边去,可能有时候有那么一点想记录下来的冲动,但算算时间太紧,很多 ...

  5. java核心技术卷一

    java核心技术卷一 java基础类型 整型 数据类型 字节数 取值范围 int 4 +_2^4*8-1 short 2 +_2^2*8-1 long 8 +_2^8*8-1 byte 1 -128- ...

  6. 对《Java核心技术卷一》读者的一些建议

    <Java核心技术卷一>是唯一可以和<Java编程思想>媲美的一本 Java 入门书.单从技术的角度来看,前者更好一些.但上升到思想层面嘛,自然后者更好,两者的偏重点不同. 思 ...

  7. 小白学Java:老师!泛型我懂了!

    目录 小白学Java:老师!泛型我懂了! 泛型概述 定义泛型 泛型类的定义 泛型方法的定义 类型变量的限定 原生类型与向后兼容 通配泛型 非受限通配 受限通配 下限通配 泛型的擦除和限制 类型擦除 类 ...

  8. 《Java核心技术卷I》观赏指南

    Tomxin7 如果你有想看书的计划,但是还在纠结哪些书值得看,可以简单看看"观赏指南"系列,本文会简单列出书中内容,给还没有买书的朋友提供一个参考. 前言 秋招过去很久了,虽然在 ...

  9. 【阅读笔记】Java核心技术卷一 #0

    这是一篇备忘性质的读书笔记,仅记录个人觉得有用的知识点 本文作为一个目录索引,部分章节跳过 吐槽:此书中文翻译有不少地方不太通顺,这种情况我要把英文版对应的部分也读一遍才能明白(说实话,英文里的从句表 ...

随机推荐

  1. vue运行报错error:Cannot assign to read only property 'exports' of object '#<Object>'

    用weex做项目的时候,npm start 之后一直报错error:Cannot assign to read only property 'exports' of object '#<Obje ...

  2. 基于verdaccio的npm私有仓库搭建

    详见个人博客:https://shengchangwei.github.io/verdaccio/ 一.使用npm安装 npm install --global verdaccio 二.cmd 启动 ...

  3. [考试反思]1105csp-s模拟测试102: 贪婪

    还是有点蠢... 多测没清空T3挂40...(只得了人口普查分20) 多测题要把样例复制粘两遍自测一下防止未清空出锅. 然而不算分... 其实到现在了算不算也不重要了吧... 而且其实T3只考虑最长路 ...

  4. [考试反思]0820NOIP模拟测试27:幻影

    注:某让我把“傻孩子”三个字全部删掉了语法不通之处自行脑补(这句本身就语法不通) skyhAK 我和以及milk_feng220 还真的没有考虑过如果我考前3的话这个颜色该怎么表示(自从不粘排行榜以来 ...

  5. [考试反思]0814NOIP模拟测试21

    前两名是外校的240.220.kx和skyh拿到了190的[暴力打满]的好成绩. 我第5是170分,然而160分就是第19了. 在前一晚上刚刚爆炸完毕后,心态格外平稳. 想想前一天晚上的挣扎: 啊啊啊 ...

  6. NOIP模拟 35

    int乘爆见祖宗 难得地T3暴力打满 T1T2思路也都正确 然而T2没看出一个int乘爆直接滚粗.. 这谁抗的住啊... 跟天皇说我差点就rank1了 换来的只有一张奸笑的大脸和一堆垃圾的嘲讽 真巨啊 ...

  7. NOIP模拟 14

    垃圾成绩,一点都不稳定. 如果把数组开小的分得到的话..总分还挺不错.. 那又能怪谁,都快NOIP了还犯这种傻逼错误 nc哥是要阿卡的节奏..真是太强了 某kyh也不知道偷了谁的rp,分高的一批 wd ...

  8. MongoDB 谨防索引seek的效率问题

    目录 背景 初步分析 索引seeks的原因 优化思路 小结 声明:本文同步发表于 MongoDB 中文社区,传送门: http://www.mongoing.com/archives/27310 背景 ...

  9. 2019.NET Conf China(中国.NET开发者峰会)活动全纪录:.NET技术之崛起,已势不可挡

    一.微软走向开放与.NET Core的诞生 当今时代,气象更新,技术飞速发展. 当今时代,开发者大概是最优秀的群体.每一位开发者,无不奋勇向前,努力追寻时代的步伐,以大无畏的精神迎接挑战,紧跟大时代成 ...

  10. PCA降维的原理、方法、以及python实现。

    PCA(主成分分析法) 1. PCA(最大化方差定义或者最小化投影误差定义)是一种无监督算法,也就是我们不需要标签也能对数据做降维,这就使得其应用范围更加广泛了.那么PCA的核心思想是什么呢? 例如D ...