Java Lambda 表达式详解及示例代码
JavaLambda表达式是Java8引入的一个新的功能,可以说是模拟函数式编程的一个语法糖,类似于Javascript中的闭包,但又有些不同,主要目的是提供一个函数化的语法来简化我们的编码。
Lambda基本语法
Lambda的基本结构为(arguments)->body,有如下几种情况:
- 参数类型可推导时,不需要指定类型,如(a)->System.out.println(a)
- 当只有一个参数且类型可推导时,不强制写(),如a->System.out.println(a)
- 参数指定类型时,必须有括号,如(inta)->System.out.println(a)
- 参数可以为空,如()->System.out.println(“hello”)
body需要用{}包含语句,当只有一条语句时{}可省略
常见的写法如下:
(a)->a*a
(inta,intb)->a+b
(a,b)->{returna-b;}
()->System.out.println(Thread.currentThread().getId())
函数式接口FunctionalInterface
概念
JavaLambda表达式以函数式接口为基础。什么是函数式接口(FunctionalInterface)?简单说来就是只有一个方法(函数)的接口,这类接口的目的是为了一个单一的操作,也就相当于一个单一的函数了。常见的接口如:Runnable,Comparator都是函数式接口,并且都标注了注解@FunctionalInterface。
举例
以Thread为例说明很容易理解。Runnable接口是我们线程编程时常用的一个接口,就包含一个方法voidrun(),这个方法就是线程的运行逻辑。按照以前的语法,我们新建线程一般要用到Runnable的匿名类,如下:
newThread(newRunnable(){ @Override publicvoidrun(){ System.out.println(Thread.currentThread().getId()); } }).start();
如果写多了,是不是很无聊,而基于Lambda的写法则变得简洁明了,如下:
newThread(()->System.out.println(Thread.currentThread().getId())).start();
注意Thread的参数,Runnable的匿名实现就通过一句就实现了出来,写成下面的更好理解
Runnabler=()->System.out.println(Thread.currentThread().getId());
newThread(r).start();
当然Lambda的目的不仅仅是写起来简洁,更高层次的目的等体会到了再总结。
再看一个比较器的例子,按照传统的写法,如下:
Integer[]a={1,8,3,9,2,0,5}; Arrays.sort(a,newComparator<Integer>(){ @Override publicintcompare(Integero1,Integero2){ returno1-o2; } });
Lambda表达式写法如下:
Integer[]a={1,8,3,9,2,0,5};
Arrays.sort(a,(o1,o2)->o1-o2);
JDK中的函数式接口
为了现有的类库能够直接使用Lambda表达式,Java8以前存在一些接口已经被标注为函数式接口的:
- java.lang.Runnable
- java.util.Comparator
- java.util.concurrent.Callable
- java.io.FileFilter
- java.security.PrivilegedAction
- java.beans.PropertyChangeListener
Java8中更是新增加了一个包java.util.function,带来了常用的函数式接口:
- Function<T,R>-函数:输入T输出R
- BiFunction<T,U,R>-函数:输入T和U输出R对象
- Predicate<T>-断言/判断:输入T输出boolean
- BiPredicate<T,U>-断言/判断:输入T和U输出boolean
- Supplier<T>-生产者:无输入,输出T
- Consumer<T>-消费者:输入T,无输出
- BiConsumer<T,U>-消费者:输入T和U无输出
- UnaryOperator<T>-单元运算:输入T输出T
- BinaryOperator<T>-二元运算:输入T和T输出T
另外还对基本类型的处理增加了更加具体的函数是接口,包括:BooleanSupplier,DoubleBinaryOperator,DoubleConsumer,DoubleFunction<R>,DoublePredicate,DoubleSupplier,DoubleToIntFunction,DoubleToLongFunction,DoubleUnaryOperator,IntBinaryOperator,IntConsumer,IntFunction<R>,IntPredicate,IntSupplier,IntToDoubleFunction,IntToLongFunction,IntUnaryOperator,LongBinaryOperator,LongConsumer,LongFunction<R>,LongPredicate,LongSupplier,LongToDoubleFunction,LongToIntFunction,LongUnaryOperator,ToDoubleBiFunction<T,U>,ToDoubleFunction<T>,ToIntBiFunction<T,U>,ToIntFunction<T>,ToLongBiFunction<T,U>,ToLongFunction<T>。结合上面的函数式接口,对这些基本类型的函数式接口通过类名就能一眼看出接口的作用。
创建函数式接口
有时候我们需要自己实现一个函数式接口,做法也很简单,首先你要保证此接口只能有一个函数操作,然后在接口类型上标注注解@FunctionalInterface即可。
类型推导
类型推导是Lambda表达式的基础,类型推导的过程就是Lambda表达式的编译过程。以下面的代码为例:
Function<String,Integer>strToInt=str->Integer.parseInt(str);
编译期间,我理解的类型推导的过程如下:
- 先确定目标类型Function
- Function作为函数式接口,其方法签名为:Integerapply(Stringt)
- 检测str->Integer.parseInt(str)是否与方法签名匹配(方法的参数类型、个数、顺序和返回值类型)
- 如果不匹配,则报编译错误
这里的目标类型是关键,通过目标类型获取方法签名,然后和Lambda表达式做出对比。
方法引用
方法引用(MethodReference)的基础同样是函数式接口,可以直接作为函数式接口的实现,与Lambda表达式有相同的作用,同样依赖于类型推导。方法引用可以看作是只调用一个方法的Lambda表达式的简化。
方法引用的语法为:Type::methodName或者instanceName::methodName,构造函数对应的methodName为new。
例如上面曾用到例子:
Function<String,Integer>strToInt=str->Integer.parseInt(str);
对应的方法引用的写法为
Function<String,Integer>strToInt=Integer::parseInt;
根据方法的类型,方法引用主要分为一下几种类型,构造方法引用、静态方法引用、实例上实例方法引用、类型上实例方法引用等
构造方法引用
语法为:Type::new。如下面的函数为了将字符串转为数组
方法引用写法
Function<String,Integer>strToInt=Integer::new;
Lambda写法
Function<String,Integer>strToInt=str->newInteger(str);
传统写法
Function<String,Integer>strToInt=newFunction<String,Integer>(){ @Override publicIntegerapply(Stringstr){ returnnewInteger(str); } };
数组构造方法引用
语法为:Type[]::new。如下面的函数为了构造一个指定长度的字符串数组
方法引用写法
Function<Integer,String[]>fixedArray=String[]::new;
方法引用写法
Function<Integer,String[]>fixedArray=length->newString[length];
传统写法
Function<Integer,String[]>fixedArray=newFunction<Integer,String[]>(){ @Override publicString[]apply(Integerlength){ returnnewString[length]; } };
静态方法引用
语法为:Type::new。如下面的函数同样为了将字符串转为数组
方法引用写法
Function<String,Integer>strToInt=Integer::parseInt;
Lambda写法
Function<String,Integer>strToInt=str->Integer.parseInt(str);
传统写法
Function<String,Integer>strToInt=newFunction<String,Integer>(){ @Override publicIntegerapply(Stringstr){ returnInteger.parseInt(str); } };
实例上实例方法引用
语法为:instanceName::methodName。如下面的判断函数用来判断给定的姓名是否在列表中存在
List<String>names=Arrays.asList(newString[]{"张三","李四","王五"});
Predicate<String>checkNameExists=names::contains;
System.out.println(checkNameExists.test("张三"));
System.out.println(checkNameExists.test("张四"));
类型上实例方法引用
语法为:Type::methodName。运行时引用是指上下文中的对象,如下面的函数来返回字符串的长度
Function<String,Integer>calcStrLength=String::length; System.out.println(calcStrLength.apply("张三")); List<String>names=Arrays.asList(newString[]{"zhangsan","lisi","wangwu"}); names.stream().map(String::length).forEach(System.out::println);
又比如下面的函数已指定的分隔符分割字符串为数组
BiFunction<String,String,String[]>split=String::split;
String[]names=split.apply("zhangsan,lisi,wangwu",",");
System.out.println(Arrays.toString(names));
Stream对象
概念
什么是Stream?这里的Stream不同于io中的InputStream和OutputStream,Stream位于包java.util.stream中,也是java8新加入的,Stream只的是一组支持串行并行聚合操作的元素,可以理解为集合或者迭代器的增强版。什么是聚合操作?简单举例来说常见的有平均值、最大值、最小值、总和、排序、过滤等。
Stream的几个特征:
单次处理。一次处理结束后,当前Stream就关闭了。
支持并行操作
常见的获取Stream的方式
从集合中获取
Collection.stream();
Collection.parallelStream();
静态工厂
Arrays.stream(array)
Stream.of(T…)
IntStream.range()
这里只对Stream做简单的介绍,下面会有具体的应用。要说Stream与Lambda表达式有什么关系,其实并没有什么特别紧密的关系,只是Lambda表达式极大的方便了Stream的使用。如果没有Lambda表达式,使用Stream的过程中会产生大量的匿名类,非常别扭。
举例
以下的demo依赖于Employee对象,以及由Employee对象组成的List对象。
publicclassEmployee{ privateStringname; privateStringsex; privateintage; publicEmployee(Stringname,Stringsex,intage){ super(); this.name=name; this.sex=sex; this.age=age; } publicStringgetName(){ returnname; } publicStringgetSex(){ returnsex; } publicintgetAge(){ returnage; } @Override publicStringtoString(){ StringBuilderbuilder=newStringBuilder(); builder.append("Employee{name=").append(name).append(",sex=").append(sex).append(",age=").append(age) .append("}"); returnbuilder.toString(); } } List<Employee>employees=newArrayList<>(); employees.add(newEmployee("张三","男",25)); employees.add(newEmployee("李四","女",24)); employees.add(newEmployee("王五","女",23)); employees.add(newEmployee("周六","男",22)); employees.add(newEmployee("孙七","女",21)); employees.add(newEmployee("刘八","男",20));
打印所有员工
Collection提供了forEach方法,供我们逐个操作单个对象。
employees.forEach(e->System.out.println(e));
或者
employees.stream().forEach(e->System.out.println(e));
按年龄排序
Collections.sort(employees,(e1,e2)->e1.getAge()-e2.getAge());
employees.forEach(e->System.out.println(e));
或者
employees.stream().sorted((e1,e2)->e1.getAge()-e2.getAge()).forEach(e->System.out.println(e));
打印年龄最大的女员工
max/min返回指定排序条件下最大/最小的元素
EmployeemaxAgeFemaleEmployee=employees.stream() .filter(e->"女".equals(e.getSex())) .max((e1,e2)->e1.getAge()-e2.getAge()) .get(); System.out.println(maxAgeFemaleEmployee);
打印出年龄大于20的男员工
filter可以过滤出符合条件的元素
employees.stream()
.filter(e->e.getAge()>20&&"男".equals(e.getSex()))
.forEach(e->System.out.println(e));
打印出年龄最大的2名男员工
limit方法截取有限的元素
employees.stream() .filter(e->"男".equals(e.getSex())) .sorted((e1,e2)->e2.getAge()-e1.getAge()) .limit(2) .forEach(e->System.out.println(e));
打印出所有男员工的姓名,使用,分隔
map将Stream中所有元素的执行给定的函数后返回值组成新的Stream
StringmaleEmployeesNames=employees.stream() .map(e->e.getName()) .collect(Collectors.joining(",")); System.out.println(maleEmployeesNames);
统计信息
IntSummaryStatistics,DoubleSummaryStatistics,LongSummaryStatistics包含了Stream中的汇总数据。
IntSummaryStatisticsstat=employees.stream() .mapToInt(Employee::getAge).summaryStatistics(); System.out.println("员工总数:"+stat.getCount()); System.out.println("最高年龄:"+stat.getMax()); System.out.println("最小年龄:"+stat.getMin()); System.out.println("平均年龄:"+stat.getAverage());
总结
Lambda表达式确实可以减少很多代码,能提高生产力,当然也有弊端,就是复杂的表达式可读性会比较差,也可能是还不是很习惯的缘故吧,如果习惯了,相信会喜欢上的。凡事都有两面性,就看我们如何去平衡这其中的利弊了,尤其是在一个团队中。
以上就是对Java8JavaLambda的资料整理,后续继续补充相关资料谢谢大家对本站的支持!