文章目录
1. Lambda表达式
在Java8开始引入的语法规则,这个属于基础语法,这个语法可以理解成是一个语法糖,是对匿名内部类的包装实现,这是非常重要的使用,在Java8中许多地方使用了这个特性,这个使用一般不能单独使用,要依靠接口函数来实现,上面说了是匿名内部类的包装实现,这个理解对下面的局部变量的理解的非常重要的。
函数式接口要求这个接口只能有一个必须实现的函数,其他方法要么是默认方法,要么是静态方法,用lambda表达式实现的只会是这个必须实现的函数。
表达式组成由()
标识参数的接收参数的口子,用->
连接函数体{}
;这里有一个特性,当入参只有一个参数的时候,()
可以省略,例如 a->{return a;};如果函数体只有一行,{}
也可以省略,如果还有返回值,修饰词return也可以省略,例如:a-> a。
1.1. 四大基础函数式接口
所在路径:java.util.function
1.1.1. Function <T,R> 函数接口
这个接口是以接收T类型的参数,返回R类型的结果,只接受一个参数
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
由上面的源码,可以知道,这个接口的使用需要实现apply
方法,原来使用的方式:
public class TestFunction {
public static void main(String[] args) {
Function<String, Integer> function = new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.valueOf(s);
}
};
System.out.println(function.apply("123"));
}
}
使用lambda表达式:
public class TestFunction {
public static void main(String[] args) {
Function<String, Integer> function = s -> Integer.valueOf(s);
System.out.println(function.apply("123"));
}
}
这样大大简化了我们的写法,同时他们是等价的;
1.1.2. Predicate<T> 断言接口
接收一个T类型参数,返回一个布尔型返回值
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
通过源码,这个接口实现的是test
函数;
原来的写法:
public class TestPredicate {
public static void main(String[] args) {
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return StringUtils.isNotBlank(s);
}
};
System.out.println(predicate);
}
}
使用lambda表达式:
public class TestPredicate {
public static void main(String[] args) {
Predicate<String> predicate = s -> StringUtils.isNotBlank(s);
System.out.println(predicate);
}
}
这就简化了我们的写法,上下两种是等价的,这个方法可以用来校验。
1.1.3. Consumer<T> 消费者接口
这个接口接收一个T类型的参数,没有返回值:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
通过上面的源码,要实现的方法是accept
.
原来接口的使用:
public class TestConsumer {
public static void main(String[] args) {
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("测试 consumer");
}
}
使用lambda表达式:
public class TestConsumer {
public static void main(String[] args) {
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("测试 consumer");
}
}
这个消费者接口一般用来处理任务,不用返回值。
1.1.4. Supplier<T> 供应商接口
这是一个无参的接口函数,返回值是T类型。
@FunctionalInterface
public interface Supplier<T> {
T get();
}
通过源码,这里要实现的是get
方法
使用原来的方法来实现:
public class TestSupplier {
public static void main(String[] args) {
Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return String.valueOf(Math.random());
}
};
System.out.println(supplier.get());
}
}
使用lambda表达式:
public class TestSupplier {
public static void main(String[] args) {
Supplier<String> supplier = () -> String.valueOf(Math.random());
System.out.println(supplier.get());
}
}
这些就是四个标准接口函数,在学习Java8的时候这些是基础的。
1.2. 自定义接口方法
我们可以根据上面的接口来自己定义自己的函数接口,首先定义一个自定义接口,接口要求有三个泛型,两个参数,一个返回值,效果如下;
@FunctionalInterface
interface CustomizeFunction<T, V, R> {
R customize(T t, V v);
}
自定义接口的使用:
public class TestCustomize {
public static void main(String[] args) {
CustomizeFunction<Integer,Float, BigDecimal> customizeFunction = (t,v)->{
BigDecimal tt = BigDecimal.valueOf(t);
BigDecimal vv = BigDecimal.valueOf(v);
return tt.add(vv).setScale(2, RoundingMode.HALF_UP);
};
System.out.println(customizeFunction.customize(2,0.2f));
}
}
这样就完成了对自定义接口的使用了。
2. 函数式参数
说完了函数式接口,在Java里,可以把这些函数当作参数来传入使用,例如多线的Thread类的使用,例子如下:
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName());
}).start();
}
从这里可以看到Thread接收的是一个lambda表达式,这样的表达式是可以当作参数使用的;这里理解也是,比较简单的,上面说到这个表达式是为了处理匿名类的,我们把上面的写法拆开看;
public class test {
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName());
};
new Thread(runnable).start();
}
}
这样就可以看出,本质传递的就是一个对象,但是上面的写法表象就是传递了一个表达式,一个函数。
这里我们使用自定义接口来测试。
public class TestCustomize {
public static void main(String[] args) {
CustomizeClass<Integer, Float, BigDecimal> integerFloatCustomizeClass = new CustomizeClass<>();
integerFloatCustomizeClass.task((t, v) -> {
BigDecimal tt = BigDecimal.valueOf(t);
BigDecimal vv = BigDecimal.valueOf(v);
return tt.add(vv).setScale(2, RoundingMode.HALF_UP);
}, 2, 1.11f);
}
}
@FunctionalInterface
interface CustomizeFunction<T, V, R> {
R customize(T t, V v);
}
class CustomizeClass<T, V, R> {
void task(CustomizeFunction<T, V, R> c, T t, V v) {
System.out.println(c.customize(t, v));
}
}
从上面可以知道,定义了一个自定义类,里面有一个方法,这个方法有三个参数,一个是自定义的函数式接口,两个运行参数,这两个参数又是函数式参数的入参,然后调用这个函数式参数的方法,完成任务,打印计算结果。
3. Stream流运算和链式编程
在Java8中使用流元素对数据进行操作,可以快速完成运算结果,这里要知道一个点,就是操作是对数据计算的,并不会改变数据本身,调用赋值方法除外。下面给个例子:
public class testA {
public static void main(String[] args) {
List<User> list = Arrays.asList(
new User("1", User.SexEnum.Female, 12)
, new User("2", User.SexEnum.Female, 22)
, new User("3", User.SexEnum.Female, 17)
, new User("4", User.SexEnum.Female, 32)
, new User("5", User.SexEnum.Female, 45)
, new User("6", User.SexEnum.Female, 23)
, new User("7", User.SexEnum.Female, 14)
, new User("8", User.SexEnum.Female, 11));
list.stream().map(e -> e.setSex(User.SexEnum.Male)).forEach(System.out::println);
list.forEach(System.out::println);
}
}
这里可以对List的数据进行批量赋值,上面我们说了,链式是不能改变数据源的,但是后面却改变了,是不对吗,当然不是,这里要知道,赋值不是流操作导致的,是User的set方法导致的,这个一定要区分出来,否则后面会难以理解一些操作,例如:
public class testA {
public static void main(String[] args) {
List<User> list = Arrays.asList(
new User("1", User.SexEnum.Female, 12)
, new User("2", User.SexEnum.Female, 22)
, new User("3", User.SexEnum.Female, 17)
, new User("4", User.SexEnum.Female, 32)
, new User("5", User.SexEnum.Female, 45)
, new User("6", User.SexEnum.Female, 23)
, new User("7", User.SexEnum.Female, 14)
, new User("8", User.SexEnum.Female, 11));
list.stream().filter(e -> e.getAge() >= 18).forEach(e -> System.out.println("stream" + e));
list.forEach(e -> System.out.println("list" + e));
}
}
这个操作是通过筛选来处理年龄大于等于18的操作,操作结果:
streamUser{name='2', sex=Female, age=22}
streamUser{name='4', sex=Female, age=32}
streamUser{name='5', sex=Female, age=45}
streamUser{name='6', sex=Female, age=23}
listUser{name='1', sex=Female, age=12}
listUser{name='2', sex=Female, age=22}
listUser{name='3', sex=Female, age=17}
listUser{name='4', sex=Female, age=32}
listUser{name='5', sex=Female, age=45}
listUser{name='6', sex=Female, age=23}
listUser{name='7', sex=Female, age=14}
listUser{name='8', sex=Female, age=11}
从上面可以知道,数据源没有变化,输出结果是处理后的,这里就要注意,要使用处理后的结果,一定要注意接收。
上面不难看出,这些操作十分类似于SQL语法,所以我们可以通过这个操作来处理流数据,因为优化的缘故,流运算效果要比普通的逻辑写法效率高很多。同时这里也看出来,这里的许多写法使用到了上面的lambda表达式,和函数式参数,同时通过.
来连接的编程方式,被称为链式编程。
下面简单的介绍一些常用的方法。
3.1. map 和 forEach、flatMap
这两个方法使用的比较高,两个都有遍历的效果,但是也有不同,他们的入参不同导致了性质不同:
3.1.1. map
这个方法的入参是这样的
<R> Stream<R> map(Function<? super T,? extends R> mapper)
这个不难看出,这个方法是有返回值的,同时他的返回结果是一个流数据。遍历后处理的结果集合会被返回。
3.1.2. forEach
这个方法如下:
void forEach(Consumer<? super T> action)
这个可以看出,方法是没有返回值的,同时对处理结果不会收集,使用的是消费者接口。
通过上面可以看出一个有对返回的操作收集结果,一个不会。
3.1.3. flatMap
这个方法如下:
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
这个方法也会一个一个遍历,同时还会把处理结果返回出来:但是看map会发现函数式参数返回的结果是不同的,一个返回的是元素,一个返回的是集合,表现如下,也就是说map无论返回的是什么都当作一个元素里替换原来的值,flatMap无论返回的是什么,都当做集合来合并原来的集合,然后剔除原来的数。下面一个例子来说明:
public class testB {
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1,2,3,4);
System.out.println(JSON.toJSONString(list1.stream().map(e -> {
List<Integer> list2 = new ArrayList<>();
list2.add(e);
list2.add(e + 10);
return list2;
}).collect(Collectors.toList())));
System.out.println(JSON.toJSONString(list1.stream().flatMap(e -> {
List<Integer> list2 = new ArrayList<>();
list2.add(e);
list2.add(e + 10);
return list2.stream();
}).collect(Collectors.toList())));
}
}
为了好看出关系,这里用JSON来输出:
[[1,11],[2,12],[3,13],[4,14]]
[1,11,2,12,3,13,4,14]
这样就好理解了,例如1来说,map是把返回的值作为元素来替换1的,从对象类型理解就是用List替换原来Integer类型的值,但是flatMap是把值插入了原来的位置,是集合的合并。
一个是单纯的替换,一个是数据的扁平化处理。
3.2. filter
这个方法是筛选功能:
Stream<T> filter(Predicate<? super T> predicate)
这个方法会遍历流数据,把符合条件的数据组建成新的集合,这个入参的断言接口函数;
public class testB {
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1,2,3,4);
System.out.println(JSON.toJSONString(list1.stream().filter(e->e>2).collect(Collectors.toList())));
}
}
返回结果:
[3,4]
3.3. limit
方法如下,是限制数量的函数,类似于mysql的limit,但是这个参数只有一个参数:
Stream<T> limit(long maxSize)
使用方法:
public class testB {
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1,2,3,4);
System.out.println(JSON.toJSONString(list1.stream().limit(2).collect(Collectors.toList())));
}
}
输出结果:
[1,2]
3.4. sorted
这个方法用来排序使用的。无参以自然排序,有参的可以使用比较大小的参数来比较
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
案例如下:
public class testB {
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1,5,2,6,3,4);
System.out.println(JSON.toJSONString(list1.stream().sorted().collect(Collectors.toList())));
System.out.println(JSON.toJSONString(list1.stream().sorted((e1,e2)->e2.compareTo(e1)).collect(Collectors.toList())));
}
}
运行结果:
[1,2,3,4,5,6]
[6,5,4,3,2,1]
但是推荐使用Comparator的方法实现,这个也是推荐的方法:
public class testB {
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1,5,2,6,3,4);
System.out.println(JSON.toJSONString(list1.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList())));
System.out.println(JSON.toJSONString(list1.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList())));
}
}
这样也可以实现,同时推荐使用这个方法,实现正反排序。当然自定义也是可以的,例如先排序奇数再排序偶数,这就要自定义了。
public class testB {
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1,5,2,6,3,4);
System.out.println(JSON.toJSONString(list1.stream().sorted((e1,e2)->{
Integer ee1;
Integer ee2;
if (e1%2==0){
ee1 = e1+10;
}else {
ee1 = e1- 10;
}
if (e2%2==0){
ee2 = e2+10;
}else {
ee2 = e2- 10;
}
return ee1.compareTo(ee2);
}).collect(Collectors.toList())));
}
}
结果如下:
[1,3,5,2,4,6]
其他的高级使用就不去一个个介绍了。
3.5. 综合案例
有一组用户信息,先男女性别互换,获取年龄大于18岁的,按年龄排序倒序,按性别分组;
测试代码:
public class testA {
public static void main(String[] args) {
List<User> list = Arrays.asList(
new User("1", User.SexEnum.Female, 12)
, new User("2", User.SexEnum.Female, 22)
, new User("3", User.SexEnum.Female, 17)
, new User("4", User.SexEnum.Male, 32)
, new User("5", User.SexEnum.Female, 45)
, new User("6", User.SexEnum.Male, 23)
, new User("7", User.SexEnum.Male, 14)
, new User("8", User.SexEnum.Male, 11));
System.out.println(JSON.toJSONString(
list.stream().map(e->e.setSex(User.SexEnum.Female.equals(e.getSex())?User.SexEnum.Male:User.SexEnum.Female))
.filter(e->e.getAge()>18)
.sorted((e1,e2)->e2.getAge().compareTo(e1.getAge()))
.collect(Collectors.groupingBy(User::getSex))
));
}
}
测试结果如下:
{
"Male": [
{
"age": 45,
"name": "5",
"sex": "Male"
},
{
"age": 22,
"name": "2",
"sex": "Male"
}
],
"Female": [
{
"age": 32,
"name": "4",
"sex": "Female"
},
{
"age": 23,
"name": "6",
"sex": "Female"
}
]
}
这样就完成了案例的测试了。在Java8里,这样的链式计算使用是非常频繁的,也是一种非常基础的使用。