1、java基础之Stream java8提供了Stream,通过StreamAPI可以简化一些代码的表达方式,但是要学习Stream,必须先来学习Lambda表达式。首先我们要学习的就是Lamdba表达式基础。 1.1、Lamdba表达式的表示方式 在java中我们通常都是定义单行变量,就好比如下的操作
[Java]
纯文本查看
复制代码
1
2
3
|
nt n =
10
;
String str =
"hello"
;
boolean
b =
true
;
|
而Lamdba是函数变量,什么是函数变量呢,就等于我们用一个名称来表示一个函数,如下所示
[Java]
纯文本查看
复制代码
1
2
3
|
elloFunction =
public
void
sayHello() {
System.out.println(
"hello world!"
);
}
|
对于上面一个函数变量而言,public是访问控制修饰符,在对于函数的变量而言,没有意义,可以省略,之后就成如下的样子
[Java]
纯文本查看
复制代码
1
2
3
|
helloFunction =
void
sayHello(){
System.out.println(
"hello world!"
);
}
|
对于函数变量而言,我们关心的是函数这个变量的结果,函数的名称就没有任何意义,void表示没有返回值,也没有意义,将这两个删除之后
[Java]
纯文本查看
复制代码
1
2
3
|
elloFunction = () {
System.out.println(
"hello world!"
);
}
|
如果这个函数中只有一行代码,我们可以将花括号省略,使用->来替换前花括号,最后就变成了如下的一个样子
[Java]
纯文本查看
复制代码
1
|
helloFunction = ()‐>System.out.println(
"hello world!"
);
|
已上就是一个Lamdba表达式的写法,通过这个例子各位应该清楚什么是Lamdba了吧,它其实就是一个函数级别的变量,当然这个代码是无法被编译。在继续讲解之前,我们来看一下其他几个函数的转换问题
[Java]
纯文本查看
复制代码
1
2
3
|
blic
int
add(
int
a,
int
b) {
return
a+b;
}
|
对于以上函数,首先定义一个变量来存储这个函数,然后去掉public和add名称,最后就是返回值的处理,只要直接声明函数的结果是a+b即可
[Java]
纯文本查看
复制代码
1
|
addFunction = (
int
a,
int
b)‐>a+b;
|
1.2、通过java实现Lamdba 下面我们会通过一个例子来实现一个Lamdba表达式,首先看helloFunction,对于helloFunction这个变量而言如果需要符合语法规则,第一是需要一个变量类型,在java8中通过接口来定义这个函数类型,只要创建一个接口,接口中有一个和这个函数的参数和返回值一样的抽象函数即可。
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
|
interface
HelloFunction {
/**
* helloFunction中的函数没有返回值和没有参数,此时只要HelloFunction接口中
* 有一个不带参数和没有返回值的函数即可
*/
public
void
foo();
}
|
此时的helloFunction就可以使用这个接口作为变量类型
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
|
blic
class
HelloLambda {
public
static
void
main(String[] args) {
HelloFunction helloFunction = () ‐> System.out.println(
"hello world!"
);
helloFunction.foo();
}
}
|
此时如果修改HelloFunction中的函数的参数类型和返回值都会报错,而且HelloFunction中也不能添加其他的方法,如果添加其他方法都会报错,所以函数接口中只能有一个方法。Java提供了一个Annotation来声明这种函数接口@FunctionalInterface接下来看看add方法的操作,原理也一样,定义接口,接口中有个方法有两个int的参数和int的返回值。
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
|
ctionalInterface
interface
AddFunction {
public
int
add(
int
a,
int
b);
}
public
static
void
main(String[] args) {
AddFunction addFunction = (a,b) ‐> a+b;
System.out.println(addFunction.add(
12
,
22
));
}
|
需要注意的是由于在定义函数变量时,可以通过函数变量的类型就可以知道里面的函数参数类型,所以在Lamdba表达式中也不用声明函数类型。
1.3、Lamdba表达式和匿名内部类 Lamdba表达式的这种方式非常类似Java的匿名内部类
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
|
匿名内部类,整个操作和Lamdba非常类似
AddFunction af =
new
AddFunction() {
public
int
add(
int
a,
int
b) {
return
a+b;
};
};
System.out.println(af.add(
12
,
22
));
|
两者的操作非常类似,当然到后面我们会发现他们之间的区别,目前我们所看到的主要区别有这样三点:1、匿名内部类的接口可以有多个方法,但是Lamdba的函数接口只能有一个方法。2、匿名内部类的实现方式比较麻烦一些。3、对于Lamdba表达式而言,并不一定是固定类型的,只要有另外一个接口满足这个函数的基本要求,就可以用来设置Lamdba表达式,这是Lamdba表达式的优点,利用这个优点可以快速的开发多线程的程序。举个例子,java的Runnable接口中只有一个run方法,这个接口和方法完全满足HelloFunction的需求,我们就可以按照下面的方式来定义这种写法。
[Java]
纯文本查看
复制代码
1
|
Runnable rfunction = () ‐> System.out.println(
"hello world!"
);
|
此时的cfunction其实就是一个实现了Runnable的函数对象,同样可以作为Thread的参数传进去,就可以实现多线程的开发。 Lamdba作为函数的参数值 Lamdba表达式还可以作为函数的参数值来使用,看如下一个例子
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
public
class
TypeLamdba {
public
static
void
main(String[] args) {
printStrLen((s)‐>s.length(),
"this is a Lamdba!"
);
}
/**
* 该方法的参数使用了函数接口作为第一个参数
*/
private
static
void
printStrLen(StrLengthFunction sl,String value) {
System.out.println(sl.length(value));
}
@FunctionalInterface
interface
StrLengthFunction {
public
int
length(String str);
}
}
|
通过上面一个例子,我们获取了字符串的长度,并且通过Lamdba表达式将其打印出来,这里大家看起来似乎比较的麻烦,但是这个实例提供一种操作。下面我们将看一个基于Lamdba处理列表的实例。 1.4、Lamdba表达式的具体实例 首先创建一个实体类Student
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public
class
Student {
private
String name;
private
String no;
private
int
age;
public
Student() {}
public
Student(String name, String no,
int
age) {
super
();
this
.name = name;
this
.no = no;
this
.age = age;
}
/.省略getter和setter./
}
|
接着创建一个列表
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
|
public
class
TestStudent {
public
static
void
main(String[] args) {
List<Student> stuList = Arrays.asList(
new
Student(
"张三"
,
"001"
,
19
),
new
Student(
"李四"
,
"005"
,
22
),
new
Student(
"王五"
,
"010"
,
14
),
new
Student(
"赵六"
,
"004"
,
18
),
new
Student(
"何琦"
,
"006"
,
12
)
);
}
}
|
我们先按照传统的方式来完成如下三个操作,根据用户no排序,列表所有的用户信息,显示年龄大于15岁的所有学生。 这三个操作如果不使用Lamdba表达式非常简单,比较大小只要通过Collections.compare()方法即可完成,第二个参数是一个Comparator的接口,我们通过内部类来实现,下面是三个函数的代码
[AppleScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/
/
根据学号排序
public static void sortByNo
(
List
<
Student
>
list
)
{
Collections.sort
(
list
,
new
Comparator
<
Student
>
(
)
{
public int compare
(
Student o
1
,
Student o
2
)
{
return
o
1.
getNo
(
)
.compareTo
(
o
2.
getNo
(
)
)
;
}
;
}
)
;
}
/
/
列表所有学生信息
public static void listAll
(
List
<
Student
>
stuList
)
{
for
(
Student stu
:
stuList
)
{
System.out.println
(
stu
)
;
}
}
/
/
过滤小于
15
岁的学生
public static void filterAge
(
List
<
Student
>
list
)
{
for
(
Student stu
:
list
)
{
if
(
stu.getAge
(
)
>
15
)
{
System.out.println
(
stu
)
;
}
}
}
|
已上三个代码都是以硬编码的方式实现,这种所带来的问题是不灵活,特别是filterAge,在此处其实除了过滤年龄,我们还可能会有其他的条件过滤需求,所以可以通过传入一个条件接口来实现,看看下面改造后的代码
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
|
blic
static
void
filterCondition(List<Student> stuList,Condition<Student> c) {
for
(Student stu:stuList) {
if
(c.test(stu)) {
System.out.println(stu);
}
}
}
interface
Condition<T> {
public
boolean
test(T t);
}
|
增加了一个Condition的接口,来传入条件,当满足条件之后就执行打印,这个条件可以通过匿名内部类的方式来实现
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
|
lterCondition(stuList,
new
Condition<Student>() {
@Override
public
boolean
test(Student t) {
if
(t.getAge()>
15
)
return
true
;
return
false
;
}
});
|
已上实现了这个操作,这种方式看起来复杂,但是它提供了一个条件检测,只要用不同的方式Condition接口,就可以根据条件来处理不同的信息,这和java中File类的FileFilter非常类似。
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public
class
TestNewStudentByJdk7 {
public
static
void
main(String[] args) {
List<Student> stuList = Arrays.asList(
new
Student(
"张三"
,
"001"
,
19
),
new
Student(
"李四"
,
"005"
,
22
),
new
Student(
"王五"
,
"010"
,
14
),
new
Student(
"赵六"
,
"004"
,
18
),
new
Student(
"何琦"
,
"006"
,
12
)
);
sortByNo(stuList);
listAll(stuList);
System.out.println(
"‐‐‐‐‐‐‐‐‐‐‐‐‐"
);
filterCondition(stuList,
new
Condition<Student>() {
@Override
public
boolean
test(Student t) {
if
(t.getAge()>
15
)
return
true
;
return
false
;
}
});
}
public
static
void
sortByNo(List<Student> list) {
Collections.sort(list,
new
Comparator<Student>() {
public
int
compare(Student o1, Student o2) {
return
o1.getNo().compareTo(o2.getNo());
};
});
}
public
static
void
listAll(List<Student> stuList) {
for
(Student stu:stuList) {
System.out.println(stu);
}
}
public
static
void
filterCondition(List<Student> stuList,Condition<Student> c) {
for
(Student stu:stuList) {
if
(c.test(stu)) {
System.out.println(stu);
}
}
}
interface
Condition<T> {
public
boolean
test(T t);
}
}
|
下面我们将看一下Lamdba的实现方式,首先看sortByName方法,Comparator接口中只有一个compare的函数,所以我们可以使用Lamdba来简化这一部分的操作。
[Java]
纯文本查看
复制代码
1
2
3
4
|
public
static
void
sortByNo(List<Student> list) {
Collections.sort(list,(s1,s2)‐>s1.getNo().compareTo(s2.getNo()));
}
|
接下来通过Lamdba的方式来操作filter方法,这个使用了Lamdba之后会简化非常多的操作,看如下三个例子
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
|
filterCondition(stuList,(t)‐>t.getAge()>
15
);
filterCondition(stuList,(t)‐>t.getName().startsWith(
"张"
));
filterCondition(stuList, (t)‐>
true
);
|
可见使用了Lamdba之后整个代码得到了极大的精简,看一下完整的代码
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public
class
TestStudentByLamdba {
public
static
void
main(String[] args) {
List<Student> stuList = Arrays.asList(
new
Student(
"张三"
,
"001"
,
19
),
new
Student(
"李四"
,
"005"
,
22
),
new
Student(
"王五"
,
"010"
,
14
),
new
Student(
"赵六"
,
"004"
,
18
),
new
Student(
"何琦"
,
"006"
,
12
)
);
sortByNo(stuList);
System.out.println(
"age > 15"
);
filterCondition(stuList,(t)‐>t.getAge()>
15
);
System.out.println(
"name like 张"
);
filterCondition(stuList,(t)‐>t.getName().startsWith(
"张"
));
System.out.println(
"all student"
);
filterCondition(stuList, (t)‐>
true
);
}
public
static
void
sortByNo(List<Student> list) {
Collections.sort(list,(s1,s2)‐>s1.getNo().compareTo(s2.getNo()));
}
public
static
void
filterCondition(List<Student> stuList,Condition<Student> c) {
for
(Student stu:stuList) {
if
(c.test(stu)) {
System.out.println(stu);
}
}
}
interface
Condition<T> {
public
boolean
test(T t);
}
}
|
关于Lamdba的讲解第一部分就到这里,相信通过这些实例,会让大家对Lamdba有一个基本的认识,下一部分将会讲解一些相对深入的知识。 1.5、Java基础之Lamdba表达式 深入表达式 上一讲给大家介绍了Lamdba表达式入门,让我们先回顾上一讲的实例代码
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
class
TestStudentByLamdba {
public
static
void
main(String[] args) {
List<Student> stuList = Arrays.asList(
new
Student(
"张三"
,
"001"
,
19
),
new
Student(
"李四"
,
"005"
,
22
),
new
Student(
"王五"
,
"010"
,
14
),
new
Student(
"赵六"
,
"004"
,
18
),
new
Student(
"何琦"
,
"006"
,
12
)
);
sortByNo(stuList);
System.out.println(
"age > 15"
);
filterCondition(stuList,(t)‐>t.getAge()>
15
);
System.out.println(
"name like 张"
);
filterCondition(stuList,(t)‐>t.getName().startsWith(
"张"
));
System.out.println(
"all student"
);
filterCondition(stuList, (t)‐>
true
);
}
public
static
void
sortByNo(List<Student> list) {
Collections.sort(list,(s1,s2)‐>s1.getNo().compareTo(s2.getNo()));
}
public
static
void
filterCondition(List<Student> stuList,Condition<Student> c) {
for
(Student stu:stuList) {
if
(c.test(stu)) {
System.out.println(stu);
}
}
}
interface
Condition<T> {
public
boolean
test(T t);
}
}
|
在该实例中我们通过Condition接口来处理条件的测试请求,如果要使用Lamdba表达式,我们将会使用到大量的接口,所以Java的开发团队也考虑到了这个问题,他们为我们提供了大量的Function的接口,供开发人员使用,所有的接口都在java.util.function包中,大家可以通过jdk的docs进行查看。其中Predicate 接口中就提供了test(T t)的 方法来完成对一个对象的比较操作。我们就可以进一步的简化以上的代码,可以不再需要Condition
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
|
public
static
void
filterCondition(List<Student> stuList,Predicate<Student> c) {
for
(Student stu:stuList) {
if
(c.test(stu)) {
System.out.println(stu);
}
}
}
|
1.6、更多的Java Function接口 出了Predicate外,Jdk还提供了几个非常好用的接口,这里简单进行了一些归纳 JDK其实已经基本把最基础的函数接口都提供,大家在使用的时候一定思考清楚函数接口是什么,需不需要自己再添加,接下来我会展示另外一个接口的应用。依然是上面的例子,我们在filterCondition中有一个非常不好的地方
[Java]
纯文本查看
复制代码
1
2
3
|
f(c.test(stu)) {
System.out.println(stu);
}
|
System.out.println(stu) 是典型的硬编码,我们不一定要打印整个对象,我们可能只会涉及到打印对象的某个值,或者我们的输出不一定是控制台,而是文件,由于上面的硬编码使得上述需求无法实现,接下来利用Consumer来解决该问题,Consumer接口提供的方法可以接受一个对象来进行处理,刚好满足这个要求,先看看如何改造filterCondition方法。
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
9
|
public
static
void
filterCondition(List<Student> stuList,Predicate<Student>
pre,Consumer<Student> con) {
for
(Student stu:stuList) {
if
(pre.test(stu)) {
con.accept(stu);
}
}
}
|
增加第三个参数Consumer来接受一个对象,此时即可在调用的地方通过Lamdba来完成操作
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
|
filterCondition(stuList,t‐>t.getAge()>
15
,t‐>System.out.println(t));
System.out.println(
"name like 张"
);
filterCondition(stuList,t‐>t.getName().startsWith(
"张"
),t‐>System.out.println(t.getName()));
System.out.println(
"all student"
);
filterCondition(stuList, t‐>
true
,t‐>System.out.println(t.getNo()));
|
通过第三个参数确定输出的结果,值得一提的是由于参数只有一个,我们可以将参数的括号去掉。完整代码如下
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public
class
TestStudentByLamdba3 {
public
static
void
main(String[] args) {
List<Student> stuList = Arrays.asList(
new
Student(
"张三"
,
"001"
,
19
),
new
Student(
"李四"
,
"005"
,
22
),
new
Student(
"王五"
,
"010"
,
14
),
new
Student(
"赵六"
,
"004"
,
18
),
new
Student(
"何琦"
,
"006"
,
12
)
);
sortByNo(stuList);
System.out.println(
"age > 15"
);
filterCondition(stuList,(t)‐>t.getAge()>
15
,t‐>System.out.println(t));
System.out.println(
"name like 张"
);
filterCondition(stuList,(t)‐>t.getName().startsWith(
"张"
),t‐
>System.out.println(t.getName()));
System.out.println(
"all student"
);
filterCondition(stuList, (t)‐>
true
,t‐>System.out.println(t.getNo()));
}
public
static
void
sortByNo(List<Student> list) {
Collections.sort(list,(s1,s2)‐>s1.getNo().compareTo(s2.getNo()));
}
public
static
void
filterCondition(List<Student> stuList,Predicate<Student>
pre,Consumer<Student> con) {
for
(Student stu:stuList) {
if
(pre.test(stu)) {
con.accept(stu);
}
}
}
}
|
通过这个例子,相信大家对Lamdba表达式有了更进一步的了解,而且应该也看到Lamdba表达式给我们带来的好处 1.7、Lamdba表达式处理异常和表达式封装模式 异常处理是Java中一个非常重要的错误处理手段,我们接下来通过实例来看一下Lamdba的异常处理,我们将会通过异常处理提出一中比较理想的封装模式,其实非常像设计模式中的代理模式,首先看如下实例
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public
class
TestLamdba {
public
static
void
main(String[] args) {
int
[] nums = {
1
,
2
,
3
,
4
};
int
key =
2
;
cal(nums,key,(v,k)‐>System.out.println(v+k));
}
private
static
void
cal(
int
[]nums,
int
key,BiConsumer<Integer, Integer> bc) {
for
(
int
n:nums) {
bc.accept(n, key);
}
}
}
|
以上程序主要完成对数组的处理方法,可以对数组里面的数据进行统一的运算,代码中统一加了key值,只要稍加改动就可以对数组进行另一种处理
[Java]
纯文本查看
复制代码
1
|
cal(nums,key,(v,k)‐>System.out.println(v/k));
|
此时就会完成数组的除法运算,当使用除法时,如果key的值为0,那就会出现ArithmeticException。那我们该如何来处理这个异常呢?第一种方案在cal方法中添加异常处理模块
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
9
|
private
static
void
cal(
int
[]nums,
int
key,BiConsumer<Integer, Integer> bc) {
for
(
int
n:nums) {
try
{
bc.accept(n, key);
}
catch
(ArithmeticException e) {
e.printStackTrace();
}
}
}
|
这种方案明显是不妥的,因为Biconsumer不一定会发生ArithmeticException,因为调用是在运行时刻才知道的,所以在这里捕获异常不合理,第二种方案是在cal的调用处处理异常
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
|
cal(nums,key,(v,k)‐>{
try
{
System.out.println(v/k);
}
catch
(ArithmeticException e) {
e.printStackTrace();
}
});
|
这种方式虽然可以在调用时捕获异常,但这种方式和Lamdba的简洁方式有所违背,这更像是匿名内部类的处理方式,所以依然不建议使用这种方式,下面我们将会介绍第三种解决方案,基于封装模式的解决方案。 我们首先再添加一个方法wrapperLamdba 该方法用来封装Consumer这个表达式,并且传入参数BiConsumer
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
|
/**
* 封装一个Lamdba,传入一个BiConsumer并且返回BiConsumer,没有对请求做任何处理
*/
private
static
BiConsumer<Integer, Integer>
wrapperLamdba(BiConsumer<Integer, Integer> bc) {
return
bc;
}
|
该方法没有做任何处理,但参数是一个BiConsumer类型,此时在调用时就可以用这个函数进行封装 cal(nums,key,wrapperLamdba((v,k)->System.out.println(v/k))); 由于wrapperLamda什么都没有做,所以此处不会进行任何处理,如果修改wrapperLamdba的代码如下
[Java]
纯文本查看
复制代码
1
2
3
4
|
private
static
BiConsumer<Integer, Integer>
wrapperLamdba(BiConsumer<Integer, Integer> bc) {
return
(v,k)‐> System.out.println(v+k);
}
|
如果这样处理,我们会发现cal中调用的Lamdba不起作用了,因为不管cal中的lamdba是什么类型,最后都会返回+的操作,这说明我们可以通过封装的方法来替换原有的表达式,这样我们就可以在这个封装方法中进行异常处理,这带来的好处就是可以在一个位置集中处理所有异常信息,在设计和使用上更加合理一些
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
|
private
static
BiConsumer<Integer, Integer>
wrapperLamdba(BiConsumer<Integer, Integer> bc) {
return
(v,k)‐> {
try
{
bc.accept(v, k);
}
catch
(ArithmeticException e) {
System.out.println(
"发生异常:"
+e.getMessage());
}
};
}
|
这种模式带来的好处是不仅仅可以处理异常,还可以加入一些限定条件,当不满足条件时,可以用新的表达式完成替换。完整代码如下:
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public
class
TestLamdba {
public
static
void
main(String[] args) {
int
[] nums = {
1
,
2
,
3
,
4
};
int
key =
0
;
cal(nums,key,wrapperLamdba((v,k)‐>System.out.println(v/k)));
}
private
static
void
cal(
int
[]nums,
int
key,BiConsumer<Integer, Integer> bc) {
for
(
int
n:nums) {
bc.accept(n, key);
}
}
/**
* 封装一个Lamdba,传入一个BiConsumer并且返回BiConsumer,没有对请求做任何处理
*/
private
static
BiConsumer<Integer, Integer>
wrapperLamdba(BiConsumer<Integer, Integer> bc) {
return
(v,k)‐> {
try
{
bc.accept(v, k);
}
catch
(ArithmeticException e) {
System.out.println(
"发生异常:"
+e.getMessage());
}
};
}
}
|
1.8、方法引用
Lamdba表达式对方法的引用有一种特殊的写法,先看看下面的实例
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
|
public
class
SimpleObject {
public
static
void
main(String[] args) {
Thread t =
new
Thread(()‐>printSth());
t.start();
}
private
static
void
printSth() {
System.out.println(
"hello world!"
);
}
}
|
例子很简单,创建了一个Runnable的Lambda表达式,并且启动了该线程,()->printSth() 有另外一种表示方法,此时printSth是静态方法可以通过SimpleObject::printSth 来替换
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public
static
void
main(String[] args) {
Thread t =
new
Thread(SimpleObject::printSth);
t.start();
}
对于方法引用还有另外一种表示方法
public
class
FooObject {
public
static
void
main(String[] args) {
printSth(
new
User(
1
,
"foo"
),System.out::println);
}
private
static
void
printSth(User u,Consumer<User> c) {
c.accept(u);
}
}
class
User {
private
int
id;
private
String name;
public
User(
int
id, String name) {
super
();
this
.id = id;
this
.name = name;
}
public
User() {
}
@Override
public
String toString() {
return
"User [id="
+ id +
", name="
+ name +
"]"
;
}
}
|
1.9、java8中的集合遍历
在java8中增加了一些新的遍历方法,在jdk5中提供了一种增强的for循环来遍历数组和集合类
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public
static
void
main(String[] args) {
List<Student> stuList = Arrays.asList(
new
Student(
"张三"
,
"001"
,
19
),
new
Student(
"李四"
,
"005"
,
22
),
new
Student(
"王五"
,
"010"
,
14
),
new
Student(
"赵六"
,
"004"
,
18
),
new
Student(
"何琦"
,
"006"
,
12
)
);
/**
* 增强的for循环
*/
for
(Student stu:stuList) {
System.out.println(stu);
}
}
|
Java中提供了基于Lamdba表达式的遍历方式,非常类似javascript函数的闭包,通过forEach来进行遍历,先看看forEach的源码
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
|
default
void
forEach(Consumer<?
super
T> action) {
Objects.requireNonNull(action);
for
(T t :
this
) {
action.accept(t);
}
}
|
参数是Consumer,所以只要以Lamdba的方法来编写就快速完成遍历
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
|
/**
* 等于执行了
for (T t : this) {
action.accept(t);
}
t就等于参数p
*/
stuList.forEach(p‐>System.out.println(p));
|
可以利用上一部分讲的函数引用方式来执行
[Java]
纯文本查看
复制代码
1
2
|
stuList.forEach(System.out::println);
|
完整代码:
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public
class
TestCollection {
public
static
void
main(String[] args) {
List<Student> stuList = Arrays.asList(
new
Student(
"张三"
,
"001"
,
19
),
new
Student(
"李四"
,
"005"
,
22
),
new
Student(
"王五"
,
"010"
,
14
),
new
Student(
"赵六"
,
"004"
,
18
),
new
Student(
"何琦"
,
"006"
,
12
)
);
/**
* 增强的for循环
*/
for
(Student stu:stuList) {
System.out.println(stu);
}
System.out.println(
"java8中新增的方法"
);
/**
* 等于执行了
for (T t : this) {
action.accept(t);
}
t就等于参数p
*/
stuList.forEach(p‐>System.out.println(p));
stuList.forEach(System.out::println);
}
}
|
这就是Lamdba表达式的基本用法,只要掌握熟练之后,会提供很多简化的方式来提高开发效率,特别是在Spring5中经常使用基于Function的表达式,下一部分将会介绍Stream,Stream在Hibernate5中已经提供支持了。
2、Java基础之Stream
Java8由于引入了Lamdba表达式这种非常方便的表示方式,使用Lambda可以简化整个集合类的遍历操作,所以也就为Stream提供了基础,Stream非常类似MongoDB中的集合处理,通过管道的形式来完成数据的遍历和聚合,读这一篇文章的前提条件是了解Java8的Lamdba表达式。
2.1、Stream预览
先看一个简单的例子。
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public
class
BaseStream {
public
static
void
main(String[] args) {
List<Student> stuList = Arrays.asList(
new
Student(
"张三"
,
"001"
,
19
),
new
Student(
"李四"
,
"005"
,
22
),
new
Student(
"王五"
,
"010"
,
14
),
new
Student(
"赵六"
,
"004"
,
18
),
new
Student(
"何琦"
,
"006"
,
12
)
);
stuList.stream()
.filter(s‐>s.getAge()>
16
)
.forEach(s‐>System.out.println(s.getName()));
}
}
|
首先创建了一个list的列表,通过stream()方法可以将列表转换为一个Stream对象,Stream就具备了管道的功能,代码中首先执行了filter,首先看filter的源码
Stream filter(Predicate<? super T> predicate); 该方法传入了Predicate这个Function接口,这个函数接口可以接受一个对象,返回一个boolean的值,所以我们返回了age大于16的人,此时就等于管道中只有年龄大于16岁的所有student,之后将这些数据向后提交,之后执行了forEach管道,forEach上一讲已经介绍过了,可以传入一个对象,没有返回值,在代码中我们通过输出的方式输出了这个对象。通过这个简单的例子我们可以快速了解java8的Stream,相信Stream会在未来成为java的一种重要的集合处理手段,下面我们将主要介绍Stream中常用的几个方法。
2.2、Stream的创建方式
只要是集合类都可以创建Stream,其中数组也可以支持,下面的代码显示了基于List的集合类和基于map的集合类操作,所有的Collection都有stream()方法直接将集合类转换为Stream
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
public
class
FirstStream {
public
static
void
main(String[] args) {
List<String> strs = Arrays.asList(
"a1"
,
"a2"
,
"a3"
,
"a4"
,
"a5"
);
strs.stream().forEach(System.out::println);
Map<String,String> maps =
new
HashMap<String,String>();
maps.put(
"001"
,
"str1"
);
maps.put(
"002"
,
"str2"
);
maps.put(
"003"
,
"str3"
);
maps.put(
"004"
,
"str4"
);
maps.entrySet().stream().forEach(System.out::println);
maps.keySet().stream().forEach(System.out::println);
maps.values().stream().forEach(System.out::println);
}
}
|
处理List和Map之外,还可以直接将字符串,字符数组等类型直接转换为Stream,主要使用Stream.of方法
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public
class
SecondStream {
public
static
void
main(String[] args) {
"abcdefg0"
.chars().forEach(System.out::println);
"abcdefg0"
.chars().forEach(c‐> System.out.println((
char
)c));
int
[] nums = {
1
,
2
,
3
,
4
,
5
,
6
};
Stream.of(nums).forEach(System.out::println);
Stream.of(
"hello,world,good,how,are,you"
.split(
","
))
.forEach(System.out::println);
Stream.of(
"a"
,
"b"
,
"c"
).forEach(System.out::println);
}
}
|
还可以直接使用Lamdba来创建Stream,使用Stream的generate方法,该方法会生成一个无限的Stream流,参数是一个Supplier的函数表达式,Supplier函数接口中的方法是获取一个对象,此时只要配合limit即可创建一些满足要求的随机数流
[Java]
纯文本查看
复制代码
1
|
Stream.generate(()‐>(
int
)(Math.random()*
100
)).limit(
20
).forEach(System.out::println);
|
通过iterate可以生成某种序列的流,iterate的第一个参数是种子,种子等于第一个数,之后按照第二个参数类推出去,例子如下,生成2的倍数的流
[Java]
纯文本查看
复制代码
1
|
Stream.iterate(
1
,i‐>i*
2
).limit(
10
).forEach(System.out::println);
|
Stream可以通过limit、distinct、sorted、filter等方法对流进行处理,这些处理完成之后都返回了流对象
[Java]
纯文本查看
复制代码
1
2
3
|
List<String> s = Arrays.asList(
"1"
,
"4"
,
"2"
,
"1"
,
"5"
,
"4"
,
"2"
,
"8"
,
"5"
);
s.stream().distinct().sorted().limit(
4
)
.forEach(System.out::println);
|
distinct表示取唯一值,sorted表示排序,limit表示取几个值。
2.3、Map、filter和toArray方法
首先看filter的源代码
[Java]
纯文本查看
复制代码
1
|
Stream<T> filter(Predicate<?
super
T> predicate);
|
里面的参数是Predicate,这个函数接口中的函数是test,传入一个参数返回一个boolean类型的值,filter此处就是用来进行条件过滤,然后看一下map函数的代码
[Java]
纯文本查看
复制代码
1
|
<R> Stream<R> map(Function<?
super
T, ?
extends
R> mapper);
|
[Java]
纯文本查看
复制代码
1
|
参数是Function,Function中的apply方法传入一个参数,返回另一个参数,这个函数用来重组对象
|
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public
class
FilterAndMap {
public
static
void
main(String[] args) {
List<Student> list = Arrays.asList(
new
Student(
"001"
,
"zhangsan"
,
20
),
new
Student(
"002"
,
"lisi"
,
30
),
new
Student(
"003"
,
"wangwu"
,
40
),
new
Student(
"004"
,
"jake"
,
23
),
new
Student(
"005"
,
"Leon"
,
21
)
);
list.stream().filter((t)‐
>t.getName().startsWith(
"z"
)).forEach(System.out::println);
list.stream().filter(t‐>t.getAge()>
25
)
.map((t)‐>t.getName()+
"‐"
+t.getAge()).forEach(System.out::println);
}
}
|
filter设置了条件过滤,而map把student转换为了字符串(name+age)的方式。看一下运行的结果
[Java]
纯文本查看
复制代码
1
2
3
4
|
Student{no=
'001'
, name=
'zhangsan'
, age=
20
}
lisi‐
30
wangwu‐
40
|
Stream中提供了toArray方法来将Stream转换为数组,转换的数组类型是Object[],接下来看看两个操作
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
|
Object[] objs = list.stream().toArray();
Stream.of(objs).forEach((obj)‐> System.out.println(((Student)obj).getName()));
Student[] stus = list.stream().toArray(Student[]::
new
);
Stream.of(stus).forEach(System.out::println);
|
2.4、收集和分组操作 Stream提供了分组操作,类似于数据库的groupby等操作,在介绍这些知识之前,我们首先需要给大家介绍一下Collectors,这是收集器,可以将stream中的东西收集到一个List、Set或者Map中。
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
|
Map<String,Integer> maps = list.stream()
.collect(Collectors.toMap(Student::getName,Student::getAge));
System.out.println(maps);
List<Student> list1 = list.stream().filter(t‐>t.getAge()>
22
)
.sorted((t1,t2)‐>(t1.getAge()‐t2.getAge()))
.collect(Collectors.toList());
System.out.println(list1);
|
大家看一下结果
[Java]
纯文本查看
复制代码
1
2
3
|
{jake=
23
, lisi=
30
, zhangsan=
20
, wangwu=
40
, Leon=
21
}
[Student{no=
'004'
, name=
'jake'
, age=
23
}, Student{no=
'002'
, name=
'lisi'
, age=
30
},
Student{no=
'003'
, name=
'wangwu'
, age=
40
}]
|
Collectors可以完成数据的收集工作,这是配合groupby的基础,接下来看一个groupby的实例,我们建一个Person的对象,看一下分组的实例
[Java]
纯文本查看
复制代码
1
2
3
4
5
|
System.out.println(persons.stream().collect(Collectors.groupingBy(p‐>p.getCountry())));
System.out.println(persons.stream().collect(
Collectors.groupingBy(Person::getCountry,
Collectors.counting())));
|
Collectors中还有一个partitioningBy,这个方法的参数是一个Precidate的函数接口,它用来统计是否满足要求的数据,返回的map中有一个是boolean,另一个是List或者Array,也可以是Collectors的集合数据(counting,max)等
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
|
System.out.println(persons.stream()
.collect(Collectors.partitioningBy(t‐>t.getCountry().equals(
"USA"
))));
System.out.println(persons.stream()
.collect(Collectors.partitioningBy(t‐
>t.getCountry().equals(
"USA"
),Collectors.counting())));
|
看看这四个的结果
[Java]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
|
{USA=[org.konghao.stream.Person
@15aeb7ab
, org.konghao.stream.Person
@7b23ec81
], China=
[org.konghao.stream.Person
@6acbcfc0
, org.konghao.stream.Person
@5f184fc6
], Germany=
[org.konghao.stream.Person
@3feba861
]}
{USA=
2
, China=
2
, Germany=
1
}
{
false
=[org.konghao.stream.Person
@6acbcfc0
, org.konghao.stream.Person
@5f184fc6
,
org.konghao.stream.Person
@3feba861
],
true
=[org.konghao.stream.Person
@15aeb7ab
,
org.konghao.stream.Person
@7b23ec81
]}
{
false
=
3
,
true
=
2
}
|
已上就是Stream的统计查询,这个功能非常的实用,虽然使用原来的方式可以实现,但工作量要大得多。
2.5、match操作
Stream提供了几种匹配操作,AllMatch表示要全部满足要求才返回true,noneMatch要所有不满足才返回真,anyMatch表示要有一个满足要求就返回true,看如下代码:
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public
class
TestAllMatch {
static
int
i =
0
;
public
static
void
main(String[] args) {
List<Student> list = Arrays.asList(
new
Student(
"001"
,
"zhangsan"
,
20
),
new
Student(
"002"
,
"lisi"
,
30
),
new
Student(
"003"
,
"wangwu"
,
40
),
new
Student(
"004"
,
"jake"
,
23
),
new
Student(
"005"
,
"Leon"
,
21
)
);
boolean
b1 = list.stream().allMatch(p‐>{
boolean
flag = p.getAge()>
20
;
System.out.println(
"#"
+(i++));
return
flag;
});
System.out.println(b1);
boolean
b2 = list.stream().allMatch(p‐>p.getAge()>=
20
);
System.out.println(b2);
System.out.println(list.stream().anyMatch(p‐>p.getName().startsWith(
"z"
)));
System.out.println(list.stream().noneMatch(p‐>p.getName().startsWith(
"k"
)));
}
}
|
看看结果
第一个才执行了一次就退出函数了,就表示只要满足条件就不会再做任何的判断了。
Lamdba的系列已经讲解完了,里面应该已经把入门的所有知识都讲解了,特别是Stream,目前可能很多人还不习惯这种操作方式,但是相信将来一定是一种主流的操作方式,因为它的确提供了非常便利的操作。希望这三部分的内容能够对大家有所帮助。
|