我是 javapub,一名 Markdown
程序员从??,八股文种子选手。
面试官:小伙子,说实话,泛型这个机制一开始我也是一头雾水,搞不太明白它到底要解决什么问题。你能不能不那么书呆子,给我普普通通地讲一讲泛型?
候选人: 好嘞,我们来聊聊泛型。首先,泛型要解决的最主要的问题就是类型不安全。比如说,你有一个箱子,可以装任何东西:
public class Box { private Object obj; public void set(Object obj) { this.obj = obj; } public Object get() { return obj; }}
然后你用它装了一个苹果:
Box b = new Box();b.set(new Apple());
但是当你取出来的时候,是一个水果啊,你不知道是苹果还是香蕉,需要强转类型:
Apple a = (Apple) b.get(); // 强转,可能出现ClassCastException
这就是类型不安全,一旦强转错了类型,程序就GG了。
泛型来了之后,情况就不一样了。我们可以这样定义箱子:
public class Box { // 就是类型参数 private T obj; public void set(T obj) { this.obj = obj; } public T get() { return obj; }}
然后在用的时候,指定T的实际类型,比如:
Box b = new Box(); b.set(new Apple());Apple a = b.get(); // 不需要强转,类型安全!
所以泛型最大的好处就是让代码类型安全,不再需要强制类型转换,避免ClassCastException异常,让代码更健壮。它把类型检查的工作从运行时提前到了编译时。
面试官:哇,原来如此!讲解的真的很通俗易懂,我都明白了!那泛型中最容易搞混的两个概念是什么?
面试官:最容易搞混的两个概念,应该是类型参数和实际类型参数吧?
候选人: 对的,这两个概念容易混淆。我们再举个例子:
public class Box { // 就是类型参数 private T obj;}Box b = new Box(); // Apple就是实际类型参数
类型参数T是在定义泛型类Box时使用的,代表一个未知的类型。我们不知道使用者会替换成什么类型,所以用T表示。
而实际类型参数Apple是在实例化Box时实际替换类型参数T的类型。它给T一个明确的类型,用于这次实例化。
所以类型参数是个未知的类型占位符,实际类型参数是替换类型参数的具体类型。理解了这两个概念的区别,泛型的很多地方就不会再混淆了。
面试官:说的太好了,我都不好意思问你其他的了!那最后两点疑问,1)为啥泛型类不能有静态方法?2)类型擦除是干嘛的?
候选人: 好的,两个很好的疑问:
1)泛型类不能有静态方法的原因是因为静态方法在类加载的时候就被创建,而泛型类在实例化的时候才能确定类型参数的实际类型。这时候静态方法已经创建完了,无法使用这个实际类型,所以编译器不允许这么做。
2)类型擦除就是编译器删除所有与类型参数相关的信息,并替换为上限(通常是Object类型)的过程。因为Java在1.5之前并没有泛型的概念,所以编译器会把所有的泛型类型全部擦除掉,在运行时期间不会存在任何泛型类型的参数信息。这也是为什么泛型类不能有基本类型的参数的原因。
类型擦除有利有弊,好处是可以在1.5之前的VM上运行泛型代码,坏处是导致些许运行期间的效率损失,因为擦除后所有的类型参数都被替换为Object类型。不过这点性能损失在大部分情况下可以忽略。
面试官:太棒了,你的解释简直让人眼前一亮!真的学到很多,谢谢你的精彩讲解!
候选人: 谢谢面试官的夸奖,我也在这个过程中对泛型有了更深的理解,非常高兴能与你进行这次交流与探讨。
面试官:在聊了泛型这么多后,还有些细节想问一下:
1. 泛型中和分别代表什么含义?
候选人: 代表一个未知类型的通配符,可以用在类型参数的位置,表示接受任何类型。比如:
public void print(Box box) { ...}
这个方法可以传递任何类型的Box进来,因为可以匹配任何类型。
而表示从T类型到其子类型之间的某种类型,它代表的上界类型可能是T,也可能是T的子类型。比如:
public void print(Box box) { ...}
这个方法可以传递Box或者Box进来,因为Apple和Orange都是Fruit的子类。但不能传Box
2. 泛型方法和泛型类有什么区别?
泛型方法是在普通类中定义带类型参数的方法,而泛型类是在定义类本身时指定类型参数。比如:
泛型方法:
public class Box { public void print(T t) { ... }}
泛型类:
public class Box { private T t; ... }
主要区别在于泛型类的类型参数可以用在整个类的方法和属性上,而泛型方法的类型参数只在这个方法内有效。泛型方法更灵活,可以在非泛型类上使用。
除此之外,泛型方法可以有static修饰符,可以在静态方法内使用类型参数。而泛型类不能有静态方法和静态属性,原因和前面说的类型擦除有关。
3. 泛型的上下限是什么?使用场景又是什么?
泛型的上限是,表示从T到子类型的范围;下限是,表示从T到父类型的范围。
上限的使用场景是当需要获取T的子类型对象时,比如从集合中取出元素。下限的使用场景是当需要添加T的父类型对象时,比如往集合中添加元素。
Box box1; // 放入Apple、Orange等box1.add(new Apple()); // 只能添加Fruit的子类型 Box box2; // 放入Fruit、Food等 box2.add(new Food()); // 只能添加Fruit的父类型
所以上下限主要是为了在广泛限制类型的同时,也允许满足某些使用场景的需要,使得泛型更加灵活实用。
面试官:泛型真的有些复杂,但你解释的很通俗易懂,我都差不多明白了。最后两个小问题:
1. 泛型中的边界是干嘛的?
边界是对类型参数指定的约束,目的是限制类型参数能被替换的实际类型。比如,我们可以这样定义一个泛型方法:
public void print(T t) { System.out.println(t.intValue());}
这里我们指定T必须是Number或其子类型,如果调用时用String类型替换T,则会编译错误,因为String不符合约束。
边界有两种形式:
- 类名或者接口名,例如T extends Number,表示T必须是Number类型或其子类
- 另一个类型参数,例如,表示S必须是T或其子类型
所以边界的作用就是限制类型参数可以替换的实际类型,确保在方法中可以正常使用某些操作,避免因为替换错误类型导致的运行错误。
2. 泛型中通配符和无边界的有什么区别?
无边界的表示任何类型,它没有任何限制,可以理解为,T可以替换为任何类型。而通配符有些微的区别,它表示“未知类型”,也没有具体的类型边界,但它只能在“读”的场景使用,不能在“写”的场景使用。因为编译器无法确定它到底是哪种类型。
举个例子:
public void print(Box box) { // 读操作,ok ...}public void add(Box box, Object o) { // 写操作,编译错误 box.set(o); }
所以无边界的可以出现在读和写的操作中,而通配符只能在读操作场景使用,这是两者的主要区别。通常在像泛型方法的定义中,使用无边界的会更灵活,而在一些读操作的泛型方法中,使用通配符可以更广泛的匹配不同的Box类型。
面试官:真是一个很细致的区分,我以前也常常搞不清这两者的差别,你的解释让我受益匪浅!谢谢你将这些泛型的概念讲解的如此清晰和深入,我对泛型也有了更全面的认识。真是一个非常愉快的交流过程!
候选人: 非常高兴能帮到您!我自己在准备和回答的过程中,也对泛型有了更深刻的理解,这种问答的形式确实是学习的好方法。谢谢面试官的精彩问题,让这个过程变得非常有价值。我也非常欣赏这次交流,收获颇丰,祝面试官有一个美好的一天!
最近我在更新《面试1v1》系列文章,主要以场景化的方式,讲解我们在面试中遇到的问题,致力于让每一位工程师拿到自己心仪的offer,感兴趣可以关注公众号JavaPub追更!
?目录合集:
Gitee:https://gitee.com/rodert/JavaPub
GitHub:https://github.com/Rodert/JavaPub
http://javapub.net.cn
文章列表?最少必要面试题
- Java基础
- Java并发入门
- Java容器
- JavaWeb
- JVM
- MySQL
- MyBatis
- Spring
- SpringBoot
- Redis
- ElasticSearch
- Kafka
- Zookeeper
- Docker
- 缓存
?知识点总结
下面是原创PDF干货版,持续更新中。
51页的MyBatis
14页的zookeeper总结
…
☕️Java基础锁
- volatile关键字的作用
jdk8
- 原来ThreadLocal的Lambda构造方式这么简单
?数据结构与算法
- 冒泡排序就是这么容易
- 选择排序就是这么容易
- 插入排序就是这么容易
- 希尔排序就是这么容易
- 归并排序就是这么容易
- 快速排序就是这么容易
- 堆排序就是这么容易
- 计数排序就是这么容易
- 桶排序就是这么容易
- 基数排序就是这么容易
- rodert熬夜写了一份BloomFilter总结
- 哈希算法篇 – 布隆过滤器
- B树和B+树的区别
?Mybatis
- rodert熬夜写了一份Mybatis总结
- MyBatis SQL 批量更新(代码+案例)
?搜索Lucene
- Lucene就是这么容易
Elasticsearch
- Springboot2.x整合ElasticSearch7.x实战目录
- Springboot2.x整合ElasticSearch7.x实战(一)
- Springboot2.x整合ElasticSearch7.x实战(二)
- Springboot2.x整合ElasticSearch7.x实战(三)
?Spring
Spring 学习路线图:
https://img-blog.csdnimg.cn/20201230220120483.png
一篇告诉你什么是Spring
第一个Spring程序(代码篇)
手把手整合SSM框架-附源码
公司这套架构统一处理 try…catch 这么香,求求你不要再满屏写了,再发现扣绩效!(全局异常处理)
CTO 说了,如果发现谁用 kill -9 关闭程序就开除
spring的controller是单例还是多例?怎么保证并发的安全
真的!@Autowired和@Resource注解别再用错了!
Spring Boot
SpringBoot最新版常用案例整合,持续更新中 https://github.com/Rodert/SpringBoot-javapub
- SpringBoot快速入门-附源码
- Springboot项目的接口防刷
- SpringBoot 中的线程池,你真的会用么
- docker 打包 springboot 项目快速入门
- 自定义注解+AOP切面日志+源码
- SpringBoot2.x整合Prometheus+Grafana【附源码+视频】
?中间件zookeeper
- rodert熬夜写了一份zookeeper总结
RocketMQ
- RocketMq 快速入门教程
Prometheus
- SpringBoot2.x整合Prometheus+Grafana【附源码+视频】
流程引擎
- 老板要我开发一个简单的工作流引擎
- 手把手实现springboot整合flowable、附源码-视频教程
?Redis
- rodert单排学习redis入门【黑铁】
- rodert 单排学习 redis 进阶【青铜】
- rodert单排学习redis进阶【白银一】
- rodert熬夜写了一份BloomFilter总结
- 了解Redis过期策略及实现原理
- 缓存:热点key重建优化
- 记一次redis线上问题
- 了解Redis过期策略及实现原理
?Docker
- docker 打包 springboot 项目快速入门
?sql
- 求求你不要再用offset和limit了
- 慢查询优化方案-SQL篇【JavaPub版】
- 分表分库解决思路
- 如果mysql磁盘满了,会发生什么?还真被我遇到了!
?设计模式
- 优雅的替换if-else语句
- 单例模式 — 生产环境怎么用
?分布式
- 分布式唯一ID解决方案-雪花算法
?shell
- jar包shell启动脚本
⚡️常用工具Git
- Git【入门】这一篇就够了
- 国内加速访问Github的办法,超级简单
- 企业级git组合命令
- 基于 Gitee 搭建个人网站-入门教程
shell
- 代替xshell的国产免费工具
linux
- 史上最全win10下Linux子系统的安装及优化方案
ffmpeg
- rodert教你学FFmpeg实战这一篇就够了
实用工具
- 压箱底的10款在线工具平台
- 离线IP地址定位库
?加密
- FPE格式保留加密
?GoLang
- Java急速转职GoLang工程师资料-入门篇
?前端
- 网站都变灰色了,1分钟教你实现
?区块链
- 这破玩意就是区块链?
?web实战
下载地址: github | gitee
- SSM项目合集(公众号领取)
- 基于SSM图书馆管理系统
- 私活利器 时薪翻一番,推荐几个SpringBoot项目,建议你改改
- 16K点赞 基于Spring + Vue的前后端分离管理系统ELAdmin,真香
- Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台项目
- 一款基于 Spring Boot 的现代化社区(论坛/问答/社交网络/博客)
- 决定做一个开源项目
?实战面试
- Java 同学注意!这不是演习
20212021 Java面试题系列教程
- Java基础–2021面试题系列教程–大白话解读–JavaPUb版本
- Java容器–2021面试题系列教程(附答案解析)–大白话解读–JavaPub版本
- Java反射–2021面试题系列教程–大白话解读–JavaPub版本
《面试1v1》Java面试八股文
《面试1v1》是我在面试中总结和推理出来的,准备在跳槽时温习回顾使用。
它采用对话的方式、口语化描述技术点,这里没有花费长篇大论的描述 API 怎么用,主要涉及到的都是高频面试题、及工作中如何使用,我还穿插了部分源码解析,因为现在面试中八股文必不可少,让文章由浅入深的更好理解。模拟了在真实面试场景中,候选人该如何回答。
迫不及待要看 面试1v1 全集怎么办? 目前在持续更新中,我一般会先更新到公众号,提催更
什么是《面试1v1》?
《面试1v1》是一个以对话形式讲解知识点的文章合集,是由 JavaPub 编写的真人1对1面试对话教程,通过真实案例编写,生动、有趣、干货满满。
为什么要写《面试1v1》这个专题?
我在后台收到很多读者的描述,说自己在面试准备过程中感觉抓不住重点,总是复习的没考、考的没复习。面试过后导致自己自信心受挫,不知道?♀️该看点什么来。
这里主要以我的经验给大家一个参照,我们如何在面试中自然的对答,不会因为紧张的忘记。如果用自己的话描述技术难题,避免背课文式的对话。
《面试1v1》有什么用?
文中大多是以实际面试中遇到的情况编写,几乎是大白话式的对话。涉及到的源码我也在对话中做了标注,方便我们查阅遗忘的知识点。
最终的目标是帮助大家更好的掌控面试,拿到心仪offer。
《面试1v1》收费吗,在哪里可以看到全集?
由 JavaPub 完全免费提供,并且持续更新中,在 wx 搜索 JavaPub 就可以直接查看全系列文章。
面试1v1 之后会出第二季吗?
会的,第二季会从大白话源码的角度出发,八股文的朋友不要错过。
【面试1v1】hashmap
【面试1v1】java注解
【面试1v1】java泛型
【面试1v1】java多线程
【面试1v1】CAS
【面试1v1】java反射
【面试1v1】动态代理
【面试1v1】javaNIO
【面试1v1】synchronized
【面试1v1】volatile
【面试1v1】线程池
【面试1v1】ThreadLocal
【面试1v1】JVM内存模型
【面试1v1】CountDownLatch-CyclicBarrier
原创电子书
链接:https://pan.baidu.com/s/1BUjGUevP00GqRw2b0HgBBA?pwd=6e67
提取码:6e67
看到这里了,点个关注呗!双击即可点赞!关注 @JavaPub