当前位置: 首页 > news >正文

Java SE “JDK1.8新特性”面试清单(含超通俗生活案例与深度理解)

一、接口默认方法

面试问题1:什么是接口默认方法?它和接口里的抽象方法有什么不一样?

核心理解

接口默认方法是用default关键字修饰的接口方法,它自带完整的实现逻辑;而抽象方法没有实现,必须让实现接口的类去重写才能用。核心区别在于“是否强制重写”——默认方法不用重写,实现类能直接调用;抽象方法必须重写,否则实现类会编译报错。

生活例子

比如“健身器材”接口,早期只定义了“启动设备”这个抽象方法(所有健身器材,像跑步机、动感单车,都得自己实现“启动”的逻辑)。后来要给接口加“显示设备电量”的功能:如果加抽象方法,已经在健身房使用的跑步机、动感单车都要改代码补“显示电量”的实现;用默认方法的话,“显示电量”自带“读取电池数据→在屏幕上显示”的逻辑,跑步机可以直接用这个功能,动感单车如果不需要(比如是插电款),也不用改代码,依然能正常“启动”,不用被迫重写。

面试问题2:为什么说接口默认方法能兼容旧代码?如果没有默认方法,给老接口加功能会有什么麻烦?

核心理解

旧接口发布后,可能已经有很多类实现了它。如果给接口加抽象方法,所有实现类都要补写这个方法的实现,否则编译不通过——相当于“牵一发动全身”,可能导致大量旧代码报错。而默认方法自带实现,旧实现类不用改代码,直接能调用新方法,新实现类如果需要定制,再重写即可,完美解决“新增功能不影响旧代码”的问题。

生活例子

比如“手机APP插件”接口,早期只有“打开插件”的抽象方法,很多开发者做了“天气插件”“日历插件”实现了这个接口。后来接口要加“关闭插件时清理缓存”的功能:没有默认方法的话,所有天气插件、日历插件都要改代码,加“清理缓存”的逻辑,否则无法更新到新版本;有默认方法后,“清理缓存”自带“删除临时文件→记录清理日志”的实现,旧插件不用改,直接能用这个功能,新插件如果有特殊缓存逻辑(比如要保留部分数据),再重写这个方法就行,不会影响已有的插件使用。

面试问题3:实现类能重写接口的默认方法吗?如果一个类实现了两个有相同默认方法的接口,该怎么处理?

核心理解

实现类可以重写默认方法,重写时不用加default关键字,直接写方法逻辑即可。如果一个类实现的两个接口有“方法名、参数列表、返回值完全一样”的默认方法,会触发“方法冲突”,编译报错。解决办法有两个:一是实现类自己重写这个方法,定义新的逻辑;二是在重写时通过“接口名.super.方法名()”,指定用其中一个接口的默认实现。

生活例子

比如“家电”接口有默认方法“定时关闭”(实现“倒计时10分钟后关机”),“智能设备”接口也有同样的“定时关闭”默认方法(实现“倒计时5分钟后关机”)。现在做一个“智能电视”类,同时实现这两个接口:如果不处理,编译会报错。这时可以让“智能电视”重写“定时关闭”,自己定义“倒计时15分钟后关机”;也可以在重写时写“return 家电.super.定时关闭();”,直接用“家电”接口的10分钟倒计时逻辑,这样就能解决冲突。

二、Lambda表达式与函数式接口

面试问题1:什么是Lambda表达式?它为什么能让代码更简洁?

核心理解

Lambda表达式本质是“能直接传递的匿名函数”——不用单独定义类或方法,直接把代码逻辑作为参数传给其他方法。它简化代码的关键是“省略模板代码”:比如不用写类名、方法名、返回值类型(编译器能自动推断),只保留核心的参数和逻辑,让代码从“多层嵌套”变成“一行直达”。

生活例子

比如“小区快递代收”:早期要让快递员“代收一份快递”,需要写一个“代收人”类,里面定义“代收快递”方法(包括查收件人信息、登记快递、发短信通知等逻辑)。用Lambda的话,直接把逻辑写成“(快递) -> 查收件人电话 + 登记快递编号 + 发通知短信”,不用建“代收人”类,直接把这个逻辑传给快递员,一步就能完成代收,代码少了很多冗余。

面试问题2:函数式接口的定义是什么?Lambda表达式为什么必须依赖函数式接口?

核心理解

函数式接口是“只有一个抽象方法的接口”——不管接口里有多少默认方法、静态方法,只要抽象方法数量是1,就是函数式接口。Lambda表达式必须依赖它,是因为Lambda本身没有“类型”,它的类型由函数式接口决定;Lambda的代码逻辑,本质就是在实现函数式接口里的那个抽象方法,相当于“用简洁的方式写抽象方法的实现”。

生活例子

比如“餐厅点餐”:函数式接口就是“点餐行为”,里面只有“执行点餐”这一个抽象方法(比如选餐品、选规格、算价格)。Lambda表达式“(餐品) -> 选大份 + 加辣 + 算总价”,就是在实现“点餐行为”里的“执行点餐”方法——如果没有“点餐行为”这个接口,Lambda不知道要实现什么逻辑,也没法传递给服务员,所以必须依赖函数式接口。

面试问题3:JDK1.8里常见的内置函数式接口有哪些?分别用来做什么?举一个例子说明用法。

核心理解

常见的内置函数式接口有4种:①Consumer<T>(消费型):接收一个参数,不返回值,主要用来“处理参数”(比如打印、存储);②Supplier<T>(供给型):不接收参数,返回一个值,主要用来“生成数据”(比如获取随机数、读配置);③Predicate<T>(判断型):接收一个参数,返回布尔值,主要用来“判断条件”(比如筛选、校验);④Function<T,R>(函数型):接收T类型参数,返回R类型值,主要用来“转换数据”(比如类型转换、格式处理)。

生活例子

以Predicate<T>(判断型)为例,比如“超市水果筛选”:要挑“重量≥500克的苹果”,Predicate就是判断逻辑——接收“苹果”参数,返回“是否≥500克”的布尔值。具体用的时候,Predicate<Apple> isHeavy = (苹果) -> 苹果.getWeight() >= 500;,然后把这个判断逻辑传给筛选员,筛选员拿着这个逻辑,就能从一堆苹果里挑出符合条件的,不用再手动写判断代码,非常方便。

三、Stream API

面试问题1:什么是Stream API?它和普通的for循环遍历集合有什么区别?

核心理解

Stream API是处理集合数据的“流水线工具”——它不存储数据,而是对集合里的数据做筛选、排序、转换等操作,支持链式调用和并行处理。和for循环的区别:①for循环是“外部迭代”,要手动控制索引、循环次数(比如for(int i=0;i<list.size();i++));Stream是“内部迭代”,JVM自动优化遍历方式,不用写循环模板;②for循环只能串行处理,Stream能并行处理(多线程同时处理数据);③Stream支持链式调用(比如stream.filter().map().forEach()),代码更简洁,for循环要嵌套多个循环才能实现类似逻辑。

生活例子

比如“班级50个学生的成绩统计”:要找“数学≥90分且语文≥80分的学生”,用for循环需要:①定义一个空列表存结果;②循环50个学生;③每个学生判断“数学≥90且语文≥80”;④符合条件的加进列表。用Stream的话,直接students.stream().filter(s -> s.getMath()>=90 && s.getChinese()>=80).collect(Collectors.toList()),一步链式调用完成,不用手动控制循环,代码更短,还能改成并行流(parallelStream()),让多线程同时判断,速度更快。

面试问题2:Stream的“中间操作”和“终端操作”有什么不一样?各举两个例子说明。

核心理解

中间操作和终端操作的核心区别是“是否触发执行”:①中间操作(比如filter、map):返回的还是Stream,不会立即处理数据,只是“记录”要做的操作(比如“先筛选、再转换”);②终端操作(比如forEach、count):返回非Stream类型(比如void、long),会触发所有中间操作的执行(一次性把记录的操作都做完)。简单说,中间操作是“搭流水线”,终端操作是“开闸放水”——没开闸,流水线只是摆设;开闸后,数据才会顺着流水线处理。

生活例子

比如“咖啡店做咖啡”:中间操作就像“磨咖啡豆”“加牛奶”——这两步只是准备操作,没把咖啡递给顾客,不算完成;终端操作就是“递给顾客”——这一步会触发前面的“磨豆”“加牛奶”,最终完成咖啡制作。具体到Stream:①filter(筛选)是中间操作:比如“筛选出拿铁咖啡订单”,只是记录“要挑拿铁”,没处理订单;②map(转换)是中间操作:比如“把订单转换成制作步骤”,还是记录操作;③forEach(遍历)是终端操作:比如“按制作步骤做咖啡”,会触发前面的“筛选拿铁”“转换步骤”,真正开始做咖啡;④count(计数)是终端操作:比如“算拿铁订单总数”,会触发筛选,最后返回数量。

面试问题3:filter、map、reduce这三个Stream操作分别是做什么的?各举一个生活例子。

核心理解

①filter:按条件“筛选”元素,保留符合条件的,过滤掉不符合的,元素数量可能减少;②map:把元素“转换”成另一种形式(比如把数字转成字符串、把对象转成属性值),元素数量不变,类型可能变;③reduce:把多个元素“合并”成一个值(比如求和、拼接字符串),最终只返回一个结果。

生活例子

以“水果店处理水果订单”为例:①filter:比如“筛选出订单里的苹果”——订单里有苹果、香蕉、橙子,filter按“水果类型是苹果”的条件,把苹果挑出来,香蕉和橙子被过滤掉,最后只剩苹果订单;②map:比如“把苹果订单转换成重量”——每个苹果订单里有“品种、重量、价格”,map把每个订单转成“重量”(比如1kg、2kg),订单数量不变,只是从“订单对象”变成“重量数字”;③reduce:比如“算所有苹果订单的总重量”——把map得到的1kg、2kg、0.5kg合并,加起来得到3.5kg,最终只返回一个总重量,完成“多元素转单值”的操作。

面试问题4:Stream的并行流和串行流有什么区别?什么场景适合用并行流?

核心理解

串行流(stream())是单线程处理数据,所有操作在一个线程里依次执行;并行流(parallelStream())是多线程处理,把数据分成多个片段,每个线程处理一个片段,最后合并结果。适合用并行流的场景:数据量极大(比如10万条以上)、操作耗时(比如复杂计算、读取文件)——这时多线程能减少处理时间;不适合的场景:数据量小(比如100条以内)、操作简单(比如打印)——多线程的线程切换成本可能比处理时间还高,反而变慢。

生活例子

比如“学校统计全校学生的捐款总额”:①如果全校只有50个学生(数据量小),用串行流(一个老师算)就行,不用找多个老师;②如果全校有10万个学生(数据量大),用并行流(找10个老师,每个老师算1万个学生的捐款,最后把10个老师的结果加起来),比一个老师算快很多。但如果只是“统计50个学生里捐款≥10元的人数”(操作简单、数据量小),用并行流反而麻烦——找多个老师分工,还得等所有人算完,不如一个老师直接数快。

四、日期时间API(LocalDate/LocalTime等)

面试问题1:JDK1.8之前的日期类(比如Date、Calendar)有什么缺点?举个例子说明。

核心理解

旧日期类的缺点主要有3个:①线程不安全:Date和Calendar是可变的,多线程同时修改会出错(比如两个线程改同一个时间,结果可能混乱);②API设计乱:Calendar的月份从0开始(1月是0,12月是11),年份从1900开始(new Date(2024,5,1)实际是3924年),容易写错题;③功能少:没有直接算“两个日期间隔”“日期加减”的方法,要手动写逻辑,麻烦。

生活例子

比如“公司算员工入职3周年的日期”:用Calendar的话,步骤是:①创建Calendar对象,设置入职时间(比如2021年5月10日);②因为月份从0开始,所以要写calendar.set(2021,4,10)(4代表5月);③加3年:calendar.add(Calendar.YEAR,3);④获取结果。如果不小心把月份写成5,就会变成2021年6月10日,算错入职周年;而且如果多线程同时算多个员工的入职时间,Calendar对象可能被改乱,导致有的员工周年日期错成其他时间。

面试问题2:新的日期时间API(比如LocalDate、LocalTime)有什么改进?怎么解决旧类的问题?

核心理解

新API的改进主要有4个:①不可变:LocalDate、LocalTime都是不可变的,修改操作(比如plusYears())会返回新对象,不会改原对象,解决线程安全问题;②API直观:月份从1开始(5代表5月),年份按实际值(2024就是2024年),不用记偏移量;③功能拆分:LocalDate只处理日期(年/月/日),LocalTime只处理时间(时/分/秒),不用混在一起;④功能全:自带日期加减、间隔计算、格式化方法,不用手动写工具类。

生活例子

还是“算员工入职3周年日期”:用LocalDate的话,直接写LocalDate.of(2021,5,10).plusYears(3),就能得到2024年5月10日——月份直接写5,不用减1,不会算错;而且如果多线程同时算,每个线程调用plusYears(3)都会得到新对象,不会影响其他线程的结果,解决了线程安全问题。再比如“算两个日期的间隔”:入职日期2021-05-10,现在2024-05-10,用Period.between(入职日期, 现在日期),直接得到“3年0月0日”,不用手动算年份差,方便很多。

面试问题3:LocalDate、LocalTime、Duration分别用来做什么?各举一个生活例子。

核心理解

①LocalDate:专门处理“没有时间的日期”,比如生日、节假日,只包含年/月/日;②LocalTime:专门处理“没有日期的时间”,比如闹钟时间、上班打卡点,只包含时/分/秒;③Duration:计算“两个时间(或日期时间)的间隔”,精确到秒/纳秒,比如会议时长、电影时长。

生活例子

①LocalDate:比如“记录朋友的生日”——生日只需要2000年10月1日,不用关心具体几点几分,用LocalDate.of(2000,10,1)就能准确表示,没有多余的时间信息;②LocalTime:比如“设置早上闹钟”——闹钟只要7点30分响,不管是周一还是周日,用LocalTime.of(7,30),不用带日期,设置一次就能每天用;③Duration:比如“算一场电影的时长”——电影14:10开始,16:45结束,用Duration.between(LocalTime.of(14,10), LocalTime.of(16,45)),直接得到“2小时35分钟”,不用手动算“16-14=2小时,45-10=35分钟”,避免算错。

五、Optional类

面试问题1:Optional类的作用是什么?它和直接用null表示“没有值”有什么不一样?

核心理解

Optional类的作用是“优雅处理null,避免空指针异常”——它是一个“包装容器”,里面可以装“有值”(非null对象)或“无值”(空容器,不是null)。和直接用null的区别:①语义明确:方法返回Optional,就告诉调用者“这个值可能没有”,强制你处理“无值”的情况;返回null,你可能没注意,直接用就会抛异常;②避免嵌套判断:比如user.getAddress().getCity(),用null要写if(user!=null && user.getAddress()!=null),用Optional可以链式调用,代码更简洁;③容器安全:Optional本身不会是null,调用它的方法不会抛空指针,只有强行用get()且无值时才会报错。

生活例子

比如“外卖平台查用户的默认收货地址”:如果用null,方法返回“地址对象”或null——你可能没判断null,直接调用address.getCity(),如果用户没设置地址,就会抛空指针。用Optional的话,方法返回Optional<Address>,你看到Optional就知道“地址可能没有”,必须处理无值的情况(比如返回“请先设置收货地址”),不会忘记判断;而且就算用户没设置地址,返回的是空容器,不是null,调用optionalAddress.map(Address::getCity)也不会报错,更安全。

面试问题2:Optional的of()、ofNullable()、empty()这三个方法有什么区别?分别在什么场景用?

核心理解

①of(T value):传入的value必须非null,否则直接抛异常——适合“明确知道值一定不为null”的场景(比如从数据库查一个必存在的配置);②ofNullable(T value):传入的value可以是null,也可以是非null——如果是null,返回空容器;非null,返回装值的容器,适合“值可能为null”的场景(最常用,比如查用户、查订单);③empty():返回空容器,适合“明确知道没有值”的场景(比如查一个不存在的用户,返回空容器)。

生活例子

比如“电商平台查订单”:①如果用户刚下单,订单号肯定存在(比如从支付成功的回调里拿订单号),用Optional.of(订单)——因为订单一定在,不用怕抛异常;②如果用户输入一个订单号查订单,订单可能存在也可能不存在(比如输入错了),用Optional.ofNullable(订单Dao.findByNo(订单号))——不管订单在不在,都不会报错;③如果用户查的订单号根本不存在(比如数据库里没这个记录),返回Optional.empty()——明确告诉调用者“没有这个订单”,不用返回null。

面试问题3:为什么不推荐用Optional的get()方法?安全获取Optional里的值有哪些方法?

核心理解

不推荐get()是因为:如果Optional是“无值”的,get()会直接抛NoSuchElementException,和直接用null没区别,没发挥Optional的作用。安全获取值的方法有4种:①orElse(T other):无值时返回默认值(比如optionalAddress.orElse(默认地址));②orElseGet(Supplier<? extends T> supplier):无值时调用Supplier生成默认值(比如optionalAddress.orElseGet(() -> 生成临时地址()));③ifPresent(Consumer<? super T> consumer):有值时执行逻辑,无值时不做任何事(比如optionalAddress.ifPresent(addr -> 打印地址));④orElseThrow(Supplier<? extends X> exceptionSupplier):无值时抛自定义异常(比如optionalAddress.orElseThrow(() -> new 地址不存在异常()))。

生活例子

比如“查用户的默认收货地址,要获取城市”:①如果用get(),optionalAddress.get().getCity()——如果用户没设置地址,直接抛异常;②用orElse:optionalAddress.orElse(新 Address("未知城市")).getCity()——没地址时返回“未知城市”,不会报错;③用ifPresent:optionalAddress.ifPresent(addr -> System.out.println("用户城市:" + addr.getCity()))——有地址就打印,没地址就不打印,不会有异常;④用orElseThrow:optionalAddress.orElseThrow(() -> new RuntimeException("请先设置收货地址")).getCity()——没地址时抛自定义异常,提醒用户设置,比get()的默认异常更清晰。

 

http://www.dtcms.com/a/426909.html

相关文章:

  • 站台建筑资阳网站推广
  • 【论文阅读 | ECCV 2024 | DAMSDet:具有竞争性查询选择与自适应特征融合的动态自适应多光谱检测变换器】
  • 企业网站 三网系统好玩有趣的网站
  • 小程序的页面宽度 设置多少合适??
  • 基于libwebsockets与cJson的ASR Server实时语音识别实现指南
  • golang 写路由的时候要注意
  • EXCEL哪个版本开始支持VSTO-office插件?
  • 盲盒抽卡机小程序的技术挑战与解决方案
  • 全网网站建设推广国外设计网站都有哪些
  • 零基础学AI大模型之LangChain聊天模型多案例实战
  • GPU 网络基础,Part 2(MoE 训练中的网络挑战;什么是前、后端网络;什么是东西向、南北向流量)
  • 【菜狗学聚类】序列嵌入表示、UMAP降维——20250930
  • 网站外链建设的八大基本准则东大桥做网站的公司
  • MySQL进阶知识点(八)---- SQL优化
  • 【C++STL :vector类 (二) 】攻克 C++ Vector 的迭代器失效陷阱:从源码层面详解原理与解决方案
  • C++ string类常用操作
  • 修改网站模板详解如何开网站需要多少钱
  • 浅谈WebSocket
  • 做网站背景wordpress登录样式
  • 自动化通信谜团:耐达讯自动化Modbus RTU如何变身 Profibus连接触摸屏
  • 调节阀控制的“语言障碍“:耐达讯自动化一招破解,让Modbus RTU变身Profibus!
  • LE AUDIO之助听器Hearing Access Profile
  • 提升学习自主性:听写自动化对儿童习惯养成的技术支持
  • MySql的存储过程以及JDBC实战
  • 中国电建地产北京山谷:以“三好”战略绘就文旅康养与乡村振兴融合新图景
  • 中国网站建设中心做旅游网站公司
  • 【PyCharm】远程本地的WSL2如何配置copilot的Tab不冲突
  • 49.多路转接epoll
  • flash网站源码下载北京网站建设案例
  • 景德镇做网站代理游戏平台赚钱吗