技术标签: java 8 lambada表达式
匿名内部类的一个问题是:当一个匿名内部类的实现非常简单,比如说接口只有一个抽象函数,那么匿名内部类的语法有点笨拙且不清晰。我们经常会有传递一个函数作为参数给另一个函数的实际需求,比如当点击一个按钮时,我们需要给按钮对象设置按钮响应函数。lambda表达式就可以把函数当做函数的参数,代码(函数)当做数据(形参),这种特性满足上述需求。当要实现只有一个抽象函数的接口时,使用lambda表达式能够更灵活。
使用Lambda表达式的一个用例
假设你正在创建一个社交网络应用。你现在要开发一个可以让管理员对用户做各种操作的功能,比如搜索、打印、获取邮件等操作。假设社交网络应用的用户都通过Person类表示:
public class Person {
public enum Sex {
MALE, FEMALE
}
private String name;
private LocalDate birthday;
private Sex gender;
private String emailAddress;
public int getAge() {
// ...
}
public void printPerson() {
// ...
}
}
假设社交网络应用的所有用户都保存在一个 List的实例中。
我们先使用一个简单的方法来实现这个用例,再通过使用本地类、匿名内部类实现,最终通过lambda表达式做一个高效且简洁的实现。
方法1:创建一个根据某一特性查询匹配用户的方法
最简单的方式是创建几个函数,每个函数搜索指定的用户特征,比如searchByAge()这种方法,下面的方法打印了年龄大于某特定值的所有用户:
public static void printPersonsOlderThan(List roster, int age) {
for (Person p : roster) {
if (p.getAge() >= age) {
p.printPerson();
}
}
}
这个方法是有潜在的问题的,如果引入一些变动(比如新的数据类型)这个程序会出错。假设更新了应用且变化了Person类,比如使用出生年月代替了年龄;也有可能搜索年龄的算法不同。这样你将不到不再写许多API来适应这些变化。
方法2:创建一个更加通用的搜索方法
这个方法比起printPersonsOlderThan更加通用;它提供了可以打印某个年龄区间的用户:
public static void printPersonsWithinAgeRange(
List roster, int low, int high) {
for (Person p : roster) {
if (low <= p.getAge() && p.getAge() < high) {
p.printPerson();
}
}
}
如果想打印特定的性别或者打印同时满足特定性别和某年龄区间的用户呢?如果要改动Person类,添加其他属性,比如恋爱状态、地理位置呢?尽管这个方法比printPersonsOlderThan方法更加通用,但是每个查询都创建特定的函数都是有可以导致程序不够健壮。你可以使用接口将特定的搜索转交给需要搜索的特定类中(面向接口编程的思想——简单工厂模式)。
方法3:在本地类中设定特定的搜索条件
下面的方法可以打印出符合搜索条件的所有用户信息
public static void printPersons(
List roster, CheckPerson tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
这个方法通过调用tester.test方法检测每个roster列表中的元素是否满足搜索条件。如果tester.test返回true,则打印符合条件的Person实例。
通过实现CheckPerson接口实现搜索。
interface CheckPerson {
boolean test(Person p);
}
下面的类实现了CheckPerson接口的test方法。如果Person的属性是男性并且年龄在18到25岁之间将会返回true
class CheckPersonEligibleForSelectiveService implements CheckPerson {
public boolean test(Person p) {
return p.gender == Person.Sex.MALE &&
p.getAge() >= 18 &&
p.getAge() <= 25;
}
}
当要使用这个类的时候,只需要实例化一个实例,并将实例以参数的形式传递给printPersons方法。
printPersons(roster, new CheckPersonEligibleForSelectiveService());
尽管这个方式不那么脆弱——当Person发生变化时你不需要重新更多方法,但是你仍然需要在添加一些代码:要为每个搜索标准创建一个本地类来实现接口。CheckPersonEligibleForSelectiveService类实现了一个接口,你可以使用一个匿内部类替代本地类,通过声明一个新的内部类来满足不同的搜索。
方法4:在匿名内部类中指定搜索条件
下面的printPersons函数调用的第二个参数是一个匿名内部类,这个匿名内部类过滤满足性别为男性并且年龄在18到25岁之间的用户:
printPersons(
roster,
new CheckPerson() {
public boolean test(Person p) {
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
}
}
);
这个方法减少了很多代码量,因为你不必为每个搜索标准创建一个新类。但是,考虑到CheckPerson接口只有一个函数,匿名内部类的语法有显得有点笨重。在这种情况下,可以考虑使用lambda表达式替换匿名内部类,像下面介绍的这种。
方法5:通过Lambda表达式实搜索接口
CheckPerson接口是一个函数式接口。接口中只有一个抽象方法的接口属于函数式接口(一个函数式接口也可能包换一个活多个默认方法或者静态方法)。由于函数式接口只包含一个抽象方法,你可以在实现该方法的时候省略方法的名字。因此你可以使用lambda表达式取代匿名内部类表达式,像下面这样调用:
printPersons(
roster,
(Person p) -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
lambda表达式的语法后面会做详细介绍。你还可以使用标准的函数式接口取代CheckPerson接口,这样会进一步减少代码量。
方法6:使用标准的函数式接口和Lambda表达式
CheckPerson接口是一个非常简单的接口:
interface CheckPerson {
boolean test(Person p);
}
它只有一个抽象方法,因此它是一个函数式接口。这个函数有个一个参数和一个返回值。它太过简单以至于没有必要在你应用中定义它。因此JDK中定义了一些标准的函数式接口,可以在java.util.function包中找到。比如,你可以使用Predicate取代CheckPerson。这个接口中只包含boolean test(T t)方法。
interface Predicate {
boolean test(T t);
}
Predicate是一个泛型接口,泛型需要在尖括号(<>)指定一个或者多个参数。这个接口中只包换一个参数T。当你声明或者通过一个真实的类型参数实例化泛型后,你将得到一个参数化的类型。比如,参数化后的类型Predicate像下面代码所示:
interface Predicate {
boolean test(Person t);
}
参数化后的的接口包含一个接口,这和 CheckPerson.boolean test(Person p)完全一样。因此,你可以像下面的代码一样使用Predicate 取代CheckPerson:
public static void printPersonsWithPredicate(
List roster, Predicate tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
那么,可以这样调用这个函数:
printPersonsWithPredicate(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
这个不是使用lamdba表达式的唯一的方式。建议使用下面的其他方式使用lambda表达。
方法7:在应用中全都使用Lambda表达式
再来看看方法printPersonsWithPredicate哪里还可以使用lambda表达式:
public static void printPersonsWithPredicate(
List roster, Predicate tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
这个方法检测roster中的每个Person实例是否满足tester的标准。如果Person实例满足tester中设定的标准,那么Person实例的信息将会被打印出来。
你可以指定一个不同的动作来执行打印满足tester中定义的搜索条件的Person实例。你可以指定这个动作是一个lambda表达式。假设你想要一个功能和printPerson一样的lambda表示式(一个参数、返回void),你需要实现一个函数式接口。在这种情况下,你需要一个包含一个只有一个Person类型参数和返回void的函数式接口。Consumer接口包换一个void accept(T t)函数,它符合上述需求。下面的函数使用 Consumer 调用accept()从而取代了p.printPerson()的调用。
public static void processPersons(
List roster,
Predicate tester,
Consumer block) {
for (Person p : roster) {
if (tester.test(p)) {
block.accept(p);
}
}
}
那么可以这样调用processPersons函数:
processPersons(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.printPerson()
);
如果你想对用户的信息进行更多处理而不止打印出来,那该怎么办呢?假设你想验证成员的个人信息或者获取他们的联系人的信息呢?在这种情况下,你需要一个有返回值的抽象函数的函数式接口。Function接口包含了R apply(T t)方法,有一个参数和一个返回值。下面的方法获取参数匹配到的数据,然后根据lambda表达式代码块做相应的处理:
public static void processPersonsWithFunction(
List roster,
Predicate tester,
Function mapper,
Consumer block) {
for (Person p : roster) {
if (tester.test(p)) {
String data = mapper.apply(p);
block.accept(data);
}
}
}
下面的函数从roster中获取符合搜索条件的用户的邮箱地址,并将地址打印出来。
processPersonsWithFunction(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email)
);
方法8:使用泛型使之更加通用
再处理processPersonsWithFunction函数,下面的函数可以接受包含任何数据类型的集合:
public static void processElements(
Iterable source,
Predicate tester,
Function mapper,
Consumer block) {
for (X p : source) {
if (tester.test(p)) {
Y data = mapper.apply(p);
block.accept(data);
}
}
}
可以这样调用上述函数来实现打印符合搜索条件的用户的邮箱:
processElements(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email)
);
该方法的调用只要执行了下面动作:
从集合中获取对象,在这个例子中它是包换Person实例的roster集合。roster是一个List类型,同时也是一个Iterable类型。
过滤符合Predicate数据类型的tester的对象。在这个例子中,Predicate对象是一个指定了符合搜索条件的lambda表达式。
使用Function类型的mapper映射每个符合过滤条件的对象。在这个例子中,Function对象时要给返回用户的邮箱地址。
对每个映射到的对象执行一个在Consumer对象块中定义的的动作。在这个例子中,Consumer对象时一个打印Function对象返回的电子邮箱的lamdba表达式。
你可以通过一个聚合操作取代上述操作。
方法9:使用lambda表达式作为参数的合并操作
下面的例子使用了聚合操作,打印出了符合搜索条件的用户的电子邮箱:
roster
.stream()
.filter(
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25)
.map(p -> p.getEmailAddress())
.forEach(email -> System.out.println(email));
下面的表映射了processElements函数执行操作和与之对应的聚合操作
processElements动作
聚合操作
获取对象源
Stream stream()
过滤符合Predicate对象(lambda表达式)的实例
Stream filter(Predicate super T> predicate)
使用Function对象映射符合过滤标准的对象到一个值
Stream map(Function super T,? extends R> mapper)
执行Consumer对象(lambda表达式)设定的动作
void forEach(Consumer super T> action)
filter,map和forEach是聚合操作。聚合操作是从stream中处理各个元素的,而不是直接从集合中(这就是为什么第一个调用的函数是stream())。steam是对各个元素进行序列化操作。和集合不同,它不是一个储存数据的数据结构。相反地,stream加载了源中的值,比如集合通过pipeline将数据加载到stream中。pipeline是stream的一种序列化操作,这个例子中的就是filter- map-forEach。还有,聚合操作通常可以接收一个lambda表达式作为参数,这样你可自定义需要的动作。
在GUI程序中使用lambda表达式
为了处理一个图形用户界面(GUI)应用中的事件,比如键盘输入事件,鼠标移动事件,滚动事件,你通常是实现一个特定的接口来创建一个事件处理。通常,时间处理接口就是一个函数式接口,它们通常只有一个函数。
之前使用匿名内部类实现的时间相应:
btn.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
可以使用如下代码替代:
btn.setOnAction(
event -> System.out.println("Hello World!")
);
Lambda表达式语法
一个lambda表达式由一下结构组成:
()括起来参数,如果有多个参数就使用逗号分开。CheckPerson.test函数有一个参数p,代表Person的一个实例。
注意: 你可以省略lambda表达式中的参数类型。另外,如果只有一个参数也可以省略括号。比如下面的lambda表达式也是合法的:
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
箭头符号:->
主体:有一个表达式或者一个声明块组成。例子中使用这样的表达式:
p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
如果设定的是一个表达式,java运行时将会计算表达式并最终返回结果。同时,你可以使用一个返回声明:
p -> {
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
}
在lambda表达式中返回的不是一个表达式,那么就必须使用{}将代码块括起来。但是,当返回的是一个void类型时则不需要括号。比如,下面的也是一个合法的lambda表达式:
email -> System.out.println(email)
lambda表达式看起来有点像声明函数,可以把lambda表达式看做是一个匿名函数(没有名称的函数)。
下面是一个有多个形参的lambda表达式的例子:
public class Calculator {
interface IntegerMath {
int operation(int a, int b);
}
public int operateBinary(int a, int b, IntegerMath op) {
return op.operation(a, b);
}
public static void main(String... args) {
Calculator myApp = new Calculator();
IntegerMath addition = (a, b) -> a + b;
IntegerMath subtraction = (a, b) -> a - b;
System.out.println("40 + 2 = " +
myApp.operateBinary(40, 2, addition));
System.out.println("20 - 10 = " +
myApp.operateBinary(20, 10, subtraction));
}
}
方法operateBinary执行两个数的数学操作。操作本身是对IntegerMath类的实例化。实例中通过lambda表达式定义了两种操作,加法和减法。例子输出结果如下:
40 + 2 = 42
20 - 10 = 10
获取闭包中的本地变量
像本地类和匿名类一样,lambda表达式也可以访问本地变量;它们有访问本地变量的权限。lambda表达式也是属于当前作用域的,也就是说它不从父级作用域中继承任何命名名称,或者引入新一级的作用域。lambda表达式的作用域就是声明它所在的作用域。下面的这个例子说明了这一点:
import java.util.function.Consumer;
public class LambdaScopeTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
Consumer myConsumer = (y) ->
{
System.out.println("x = " + x);
System.out.println("y = " + y);
System.out.println("this.x = " + this.x);
System.out.println("LambdaScopeTest.this.x = " +
LambdaScopeTest.this.x);
};
myConsumer.accept(x);
}
}
public static void main(String... args) {
LambdaScopeTest st = new LambdaScopeTest();
LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
将会输出如下信息:
x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0
如果像下面这样在lambda表达式myConsumer中使用x取代参数y,那么编译将会出错。
Consumer myConsumer = (x) -> {
}
编译会出现"variable x is already defined in method methodInFirstLevel(int)",因为lambda表达式不引入新的作用域(lambda表达式所在作用域已经有x被定义了)。因此,可以直接访问lambda表达式所在的闭包的成员变量、函数和闭包中的本地变量。比如,lambda表达式可以直接访问方法methodInFirstLevel的参数x。可以使用this关键字访问类级别的作用域。在这个例子中this.x对成员变量FirstLevel.x的值。
然而,像本地和匿名类一样,lambda表达式值可以访问被修饰成final或者effectively final的本地变量和形参。比如,假设在methodInFirstLevel中添加定义声明如下:
Effectively Final:一个变量或者参数的值在初始化后就不在发生变化,那么这个变量或者参数就是effectively final类型的。
void methodInFirstLevel(int x) {
x = 99;
}
由于x =99的声明使methodInFirstLevel的形参x不再是effectively final类型。结果java编译器就会报类似"local variables referenced from a lambda expression must be final or effectively final"的错误。
目标类型
在运行时java是怎么判断lambda表达式的数据类型的?再看一下那个要选择性别是男性,年龄在18到25岁之间的lambda表达式:
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
这个lambda表达式已参数的形式传递到如下两个函数:
public static void printPersons(List roster, CheckPerson tester)
public void printPersonsWithPredicate(List roster, Predicate tester)
当java运行时调用方法printPersons时,它期望一个CheckPerson类型的数据,因此lambda表达式就是这种类型。当java运行时调用方法printPersonsWithPredicate时,它期望一个Predicate类型的数据,因此lambda表达式就是这样一个类型。这些方法期望的数据类型就叫目标类型。为了确定lambda表达式的类型,java编译器会在lambda表达式的的上下文中判断它的目标类型。只有java编译器可推测出来了目标类型,lambda表达式才可以被执行。
目标类型和函数参数
对于函数参数,java编译器可以确定目标类型通过两种其他语言特性:重载解析和类型参数推断。看下面两个函数式接口( java.lang.Runnable and java.util.concurrent.Callable):
public interface Runnable {
void run();
}
public interface Callable {
V call();
}
方法 Runnable.run 不返回任何值,但是 Callable.call 有返回值。假设你像下面一样重载了方法invoke:
void invoke(Runnable r) {
r.run();
}
T invoke(Callable c) {
return c.call();
}
那么执行下面程序哪个方法将会被调用呢?
String s = invoke(() -> "done");
方法invoke(Callable)会被调用,因为这个方法有返回一个值;方法invoke(Runnable)没有返回值。在这种情况下,lambda表达式() -> "done"的类型是Callable。
ip addr查看本机是否分配IP一、进入网卡配置文件的目录 cd /etc/sysconfig/network-scripts/二、修改网卡文件 vi ifcfg-ens33将ONBOOT=no 改为 ONBOOT=yes 保存退出(点击“esc” 左下角插入insert 状态消失,此时输入冒号:,下方出现冒号,等待输入命令,输入WQ(W:write 写入 Q:quit 退出) 回车,就保存退出了)三、查看命令 cat /etc/sysconfig/network-scripts/
NTML文档结构HTML文档由4个主要标记组成,包括<html>、<head>、<title>、<body>.这4个标记构成了HTML页面最基本的元素。1.<html>标记<html>标记是HTML文件的开头。所有的HTML文件都以标记开头,以标记结束,即HTML页面的所有标记都要放置在与标记中。标记虽然没有实质性的功能,但却是HTML必不可少的部分。2.<head>标记<head>标记是
为什么80%的码农都做不了架构师?>>> ...
ProgressBar(进度条) 1.常用属性讲解与基础实例从官方文档,我们看到了这样一个类关系图:ProgressBar继承与View类,直接子类有AbsSeekBar和ContentLoadingProgressBar, 其中AbsSeekBar的子类有SeekBar和RatingBar,可见这二者也是基于ProgressBar实现的常用属性详解:android:...
文章目录1. 题目2. 思路(1) KMP3. 代码1. 题目2. 思路(1) KMP对字符串求解KMP算法中的next数组,可以得到字符串的最长相等前后缀的长度。若字符串s是由重复的子字符串s’组成,则s=s’s’…s’s’,显然,字符串的长度减去最长相等前后缀的长度即为重复的子字符串s’的长度。因此,若最长相等前后缀的长度不为0,且字符串的长度减去最长相等前后缀的长度能够被字符串的长度整除,则表示字符串是由重复的子字符串组成。3. 代码public class Test {
1、机器人本体标定一般而言,机器人的重复定位精度较高,能达到±0.05 mm 左右。其标定方法一般包括四个步骤:①运动学建模;②数据测量;③参数辨识;④误差补偿。数据测量目前用于机器人连杆姿态测量的方式主要有两种类型,一种是借助外部精密的设备来测量。另一类测量方式是借助机器人本身传感器而不需要外部设备的简单标定方法。三坐标测量仪精度高 只能测量静止状态下的姿态,占用空间大...
小编又回顾了一下野火的高级定时器的视频,然后就想实现一个串口控制实现可调PWM波的实现,整了两天终于搞明白咋回事了,我太菜了,下面不多说直接上实验工程是在野火的源文件的基础上修改的,不要喷昂工程里的User文件包括以下内容:其中的GeneralTim文件夹包含:bsp_GeneralTim.c和bsp_GeneralTim.hUsart文件夹包含:bsp_usart.c和bsp_usart.h好了,直接上代码:main.c// TIM—通用定时器(TIM3)-1路(通道3)可调PWM输出应用
PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。全局安装,简直不能更简单。npm install -g pm2用express应用来举例。一般我们都是通过npm start启动应用,其实就是调用node ./bin/www。那么,换成pm2就是pm2 start ./bin/www --watch...
UC中只有一种怪Bot,这是一种很简单的怪,它不会跑,只会旋转并原地射击,所以也没有用什么行为树之类的,所以这里围绕Bot怪如何发现玩家、攻击玩家、被玩家攻击分析Bot怪1 当游戏开始时可以看到在编辑器时Bot怪是没有带武器的,是在游戏开始是把武器刷出来,放在手上的2. 发现玩家当游戏开始时,Bot怪就启动了一个0.5秒的定时器,执行CheckForPlayer去...
一、基础知识篇:HttpHeader之User-AgentUserAgent中文名为用户代理,是Http协议中的一部分,属于头域的组成部分,UserAgent也简称UA。它是一个特殊字符串头,是一种向访问网站提供你所使用的浏览器类型及版本、操作系统及版本、浏览器内核、等信息的标识。通过这个标识,用户所访问的网站可以显示不同的排版从而为用户提供更好的体验或者进行信息统计;例如用手机访问谷歌和电脑访问...
题目相关题目链接目前还没有官方的题目,本题目来自洛谷,https://www.luogu.com.cn/problem/P7072?contestId=37027。题目描述NOI2130 即将举行。为了增加观赏性,CCF 决定逐一评出每个选手的成绩,并直播即时的获奖分数线。本次竞赛的获奖率为 w%,即当前排名前 w% 的选手的最低成绩就是即时的分数线。更具体地,若当前已评出了 p 个选手的成绩,则当前计划获奖人数为 ,其中 w 是获奖百分比, 表示对 x 向下取整,max(x,y) 表示
本帖最后由 宇智波·佐助 于 2013-2-4 21:16 编辑1. 移动控件 控件.发送信息 (274, 61458, 0)其中参数1的值自61457---61471都可用,结果是一样的,都是移动控件。还有一个大家都熟,就是控件.发送信息 (161, 2, 0)2.调整控件尺寸 控件.发送信息 (274, 61441, 0) (274,61442,0) (274,61443,0) (274,61...