Java-8新特性:学习如何使用Lambda表达式(二)(1),java高级面试问题大全及答案大全

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

class Speaker {
public void speak() {
System.out.println(“Hello, world!”);
}
}
class ConcurrentSpeaker extends Speaker {
public void speak() {
Thread t = new Thread(super::speak);
t.start();
}
}

当线程启动时,run调用方法并super::speak执行,调用speak其基类的方法。请注意,在内部类中,您可以this将封闭类的引用,捕获为EnclosingClass.this::methodEnclosingClass.super::method

构造函数参考

构造函数引用与方法引用相同,只是方法名称为new。例如,Button::new是类的构造函数引用Button。将调用哪个构造函数取决于上下文。想象一下,您想要将字符串列表转换为按钮数组。在这种情况下,您应该在每个字符串上调用构造函数。它可能是这样的:

List strs = …;
Stream stream = strs.stream().map(Button::new);
List buttons = stream.collect(Collectors.toList());

有关stream的更多信息,您可以查看文档。在这种情况下,最重要的是该方法为每个列表元素调用构造函数。有多个构造函数,但编译器选择带有参数的构造函数,因为从上下文中可以明显看出应该调用带有字符串的构造函数。map``collect``map``Button(String)``Button``String

还可以为数组类型创建构造函数引用。例如,int数组的构造函数引用是int[]::new。它需要一个参数:数组的长度。它等同于lambda表达式x -> new int[x]

数组的构造函数引用对于超越Java的限制很有用。创建泛型类型的数组是不可能的T。表达式new T[n]不正确,因为它将替换为new Object[n]。对于图书馆作者来说这是一个问题。想象一下,我们想拥有一系列按钮。有一种方法toArray在类Stream返回的数组Object

Object[] buttons = stream.toArray();

但那不是你想要的。用户不想要Objects,只有按钮。该库使用构造函数引用解决了这个问题。你应该传递Button[]::new给方法toArray

Button[] buttons = stream.toArray(Button[]::new);

toArray方法调用此构造函数以获取所需类型的数组,然后在填充后返回此数组。

可变范围

通常,您希望从lambda表达式中的封闭范围访问变量。考虑以下代码:

public static void repeatText(String text, int count) {
Runnable r = () -> {
for (int i = 0; i < count; i++) {
System.out.println(text);
Thread.yield();
}
};
new Thread®.start();
}

看下面这个调用:

repeatText(“Hi!”, 2000); // Prints Hi 2000 times in a separate thread

注意变量counttext没有在lambda表达式中定义; 这些是封闭方法的参数。

如果仔细观察这段代码,你会发现幕后有某种魔力。该repeatText方法可以在lambda表达式的代码运行之前返回,而那时参数counttext变量将消失,但它们仍然可用于lambda。秘密是什么?

要了解发生了什么,我们需要提高对lambda表达式的理解。lambda表达式由三个部分组成:

  1. 代码块
  2. 参数
  3. 自由变量不是参数,也没有在lambda中定义

在我们的案例中有两个自由变量,textcount。表示lambda的数据结构必须存储它们的值,“嗨!” 他们说这些值是由lambda表达式捕获的。(如何完成取决于实现。例如,实现可以使用一个方法将lambda表达式转换为对象,并将自由变量的值复制到对象的实例变量中。)

有一个特殊的术语“封闭”; 它是一个代码块和自由变量的值。Lambda用一种方便的语法表示Java中的闭包。顺便说一句,内部类总是封闭的。

因此,lambda表达式可以捕获封闭范围中变量的值,只是有一些限制。你无法更改这些捕获变量的值。以下代码不正确:

public static void repeatText(String text, int count) {
Runnable r = () -> {
while (count > 0) {
count–; // Error: Can’t modify captured variable
System.out.println(text);
Thread.yield();
}
};
new Thread®.start();
}

这种限制是合理的,因为lambda表达式中的变量变量不是线程安全的。想象一下,我们有一系列并发任务,每个任务都更新一个共享计数器。

int matchCount = 0;
for (Path p : files)
new Thread(() -> { if (p has some property) matchCount++; }).start();
// Illegal to change matchCount

如果这段代码是正确的,那将是非常非常糟糕的。increment运算符++不是原子的,如果多个线程同时执行此代码,则无法控制结果。

内部类也可以从外部类中捕获值。在Java 8之前,内部类只能访问final局部变量。此规则已扩展为与lambda表达式匹配。现在,内部类可以处理其值不会发生变化的任何变量(实际上是final变量)。

不要指望编译器捕获所有并发访问错误。您应该知道,此修改规则仅适用于局部变量。如果我们使用外部类的实例或静态变量,则不会出现错误,结果是未定义的。

您也可以修改共享对象,即使它不健全。例如,

List matchedObjs = new ArrayList<>();
for (Path p : files)
new Thread(() -> { if (p has some property) matchedObjs.add§; }).start();
// Legal to change matchedObjs, but unsafe

如果你仔细观察,你会发现变量matchedObjs实际上是final的。(有效的final变量是一个在初始化后再无变化的变量。)在此代码中,matchedObjs始终引用同一个对象。但是,该变量matchedObjs已被修改,并且它不是线程安全的。在多线程环境中运行此代码的结果是不可预测的。

这种多线程任务有安全的机制。例如,您可以使用线程安全计数器和集合,流来收集值。

对于内部类,有一种解决方法允许lambda表达式更新封闭的本地范围中的计数器。想象一下,你使用长度为1的数组,如下所示:

int[] counts = new int[1];
button.setOnAction(event -> counts[0]++);

很明显,这段代码不是线程安全的。对于按钮回调,这没关系,但一般来说,在使用这个技巧之前你应该三思而后行。

lambda的主体与嵌套块具有相同的范围。名称冲突和阴影的规则是相同的。您不能在lambda中声明与封闭范围中的变量同名的参数或局部变量。

Path first = Paths.get(“/usr/local”);
Comparator comp =
(first, second) -> Integer.compare(first.length(), second.length());
// Error: Variable first already defined

在方法中不能有两个具有相同名称的局部变量。因此,您也不能在lambda表达式中引入这些变量。同样的规则适用于lambda。在thislambda中使用关键字时,请参考this创建lambda的方法。我们考虑一下这段代码

public class Application() {
public void doSomething() {
Runnable r = () -> { …; System.out.println(this.toString()); … };

}
}

在此示例中<code>this.toString()调用对象的toString方法Application,而不是Runnable实例。this在lambda表达式中使用没有什么特别之处。lambda表达式的范围嵌套在doSomething方法中,并且在该方法中的任何位置都具有相同的含义。

默认方法

最后,让我们谈谈与lambdas没有直接关系的新功能,它也非常有趣 - 默认方法。

在许多编程语言中,函数表达式与其集合库集成在一起。这通常会导致代码比使用循环的等效代码更简单,更容易理解。我们考虑一下代码:

for (int i = 0; i < strList.size(); i++)
System.out.println(strList.get(i));

我们可以改进这段代码。该库可以提供一种方便的方法forEach,将函数应用于每个元素。所以你可以简化代码

strList.forEach(System.out::println);

如果库是从头开始设计的,那么一切都还可以,但是如果它是很久以前在Java中创建的呢?如果Collection接口添加了一个新方法,例如forEach,那么定义自己实现此接口的类的每个应用程序都将无法工作,直到它实现该方法。它在Java中并不好。

Java中的这个问题通过允许具有特定实现的接口方法(命名为默认方法)来解决。您可以将此类方法安全地添加到现有接口。现在我们将更密切地研究默认方法。在Java 8 中,使用您将在下面看到的技巧将该forEach方法添加到Iterable基本接口Collection中。

考虑这个界面:

最后

面试前一定少不了刷题,为了方便大家复习,我分享一波个人整理的面试大全宝典

  • Java核心知识整理

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

Java核心知识

  • Spring全家桶(实战系列)

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

  • 其他电子书资料

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

Step3:刷题

既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

以下是我私藏的面试题库:

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-aqQO4TiU-1713540058421)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值