Java基础 之 lambda、stream概念及实际使用举例
lambda
什么是lambda
函数式编程,将代码块作为参数传入方法,用更简洁的方法创建一个函数式接口的实例。
什么是函数式接口
一个有且只含有一个抽象方法的接口(可以有多个默认方法)。
举个栗子:
@FunctionalInterface
public interface Convert {Integer test(Integer aString);
}
声明一个接口,接口中有一个抽象方法test()。这个接口就叫函数式接口。
@FunctionalInterface这个注解用于检测该接口是否为一个 函数式接口,如果不是IDE会报错。说这个接口不是一个函数是接口,无法通过编译
概念解释完了。那么lambda就很好理解了。
lambda表达式写法规则
(type1 a,type2 b)->{do something...}
整个表达式分3个部分:
1,(type1 a,type2 b)——左侧这是函数式接口中的抽象方法的参数列表。
这里:类型可以省略,lambda会根据上下文去自动判断;没有参数列表时写一个()即可;一个参数时可以省略()
2,-> lambda表达式符号。
3,{
do something...
}
右侧为抽象方法的具体实现的代码块。一行代码时可省略return及花括号,直接将这行代码结果作为返回值,多行代码时,如果抽象方法需要返回值,则要写return语句。
lambda典型示例
java中典型的可以使用lambda的有一个接口是runnable接口。用它可以来新建一个线程:
举个栗子:
先看下runnable代码:
@FunctionalInterface //表明该接口是一个函数式接口
public interface Runnable {/*** When an object implementing interface <code>Runnable</code> is used* to create a thread, starting the thread causes the object's* <code>run</code> method to be called in that separately executing* thread.* <p>* The general contract of the method <code>run</code> is that it may* take any action whatsoever.** @see java.lang.Thread#run()*/public abstract void run(); 唯一一个抽象方法,参数列表为空。
新建一个线程:
//原始方式new Thread(new Runnable() {@Overridepublic void run() {System.out.println("原始方法新建线程1");}}).start();//lambdanew Thread(()->System.out.println("lambda方式建新线程1")).start();
可见lambda方式会简便很多。
其实lambda表达式的直接返回值是一个函数是接口类型的变量,所以如果自定义时,需要使用函数是接口接受lambda表达式,并且在调用时,调用的方法参数列表中的一个参数,应该是此函数式接口的类型。
JDK提供给lambda使用的函数式接口
JDK默认给我们提供了很多函数式接口,都在java.util.function包下。大体分为以下几种类型:
级联
级联就是如果有多个->符号连续时的运算。这时候我们只要将每一个->后面的当做一个lambda进行计算,然后再将结果一一返回即可。
科里化
函数标准化,把多个参数的函数转换成只有一个参数的函数。
下面贴一段代码,是我之前写的关于上面几个点的例子:
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.DoublePredicate;
import java.util.function.DoubleUnaryOperator;
import java.util.function.Function;
import java.util.function.IntBinaryOperator;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.IntUnaryOperator;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;import org.codehaus.groovy.util.StringUtil;import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;@AllArgsConstructor
@NoArgsConstructor
class MyAge{private int age;public void printMyAge(Function<Integer, String> function){System.out.println("我今年"+function.apply(this.age)+"岁");}
}/*** JDK提供的几种函数式接口测试* @author maybe**/
@Slf4j
public class TestFunction {public static void main(String[] args) {Function<Integer, String> wer = (Integer x)-> x.toString();System.out.println(wer.apply(6534));//Predicate断言函数 booleanPredicate<Integer> predicate = i->i>0;log.info("断言函数:{}",predicate.test(-9));DoublePredicate doublePredicate = i->i>0;log.info("基本类型断言函数:{}",doublePredicate.test(2.5));//Consumer消费者函数 一个输入 无输出Consumer<String> consumer = i -> log.info("消费者函数消费 一个数字字符串,{}",i);consumer.accept("20");IntConsumer intConsumer = i ->log.info("基本类型消费者函数消费了一个int数字.{}",String.valueOf(i));intConsumer.accept(50);//funciton函数,一个输入一个输出MyAge myAge = new MyAge(20);myAge.printMyAge(i->i.toString());Function<Integer, String> function = i->i.toString();myAge.printMyAge(function.andThen(i->i+"周"));//Supplier提供者函数 一个输出 无输入Supplier<String> supplier = () -> "普通提供者函数提供了这段话";log.info(supplier.get());IntSupplier intSupplier = () -> 666;log.info("基本类型int提供者函数提供了{}这段数字",intSupplier.getAsInt());//一元函数UnaryOperator<String> operator = i -> i+"666";log.info("一元函数{}",operator.apply("SC"));DoubleUnaryOperator doubleUnaryOperator = i -> 2*i;log.info("double一元函数{}",doubleUnaryOperator.applyAsDouble(50));//BiFunction函数 两个输入 一个输出BiFunction<String, Boolean, String> biFunction = (x,y)->y==true?x+"-yes":x+"-no"; log.info("两个输入有一个输出的BiFunciton函数true,{}",biFunction.apply("两个输入", true));log.info("两个输入有一个输出的BiFunciton函数false,{}",biFunction.apply("两个输入", false));//二元函数BinaryOperator<Integer> binaryOperator = (x,y) -> x+y;log.info("二元函数结果{}",binaryOperator.apply(2, 5));IntBinaryOperator intBinaryOperator = (x,y) -> x*y;log.info("int二元函数结果{}",intBinaryOperator.applyAsInt(5, 10));/** 级联表达式与科里化* 科里化:函数标准化,吧多个参数的函数转换成只有一个参数的函数*/Function<Integer, IntUnaryOperator> fun = x->y->x+y;System.out.println(fun.apply(2).applyAsInt(5));}}
stream流
stream是一个高级迭代器,一般用于数据处理,配合lambda使用的一种流水线式的处理。特性包括:内部迭代,中间操作和中止操作,惰性求值,方法引用以及并行流。
stream的中间操作
stream的终止操作
惰性求值
调用终止操作前,中间操作不会执行。
并行流
再开启并行流之后,JDK会自动帮我们开启多线程模式进行数据处理。并能保证数据的完整性及排序。
下面直接看一下使用例子
public class TestStream1 {public static void main(String[] args) {//外部迭代int[] nums = {2,3,4};int sum = 0;for(int i=0;i<nums.length;i++) {sum += nums[i];}System.out.println("外部迭代"+sum);//内部迭代int sum2 = IntStream.of(nums).sum();System.out.println("内部迭代"+sum2);//中间操作,返回一个stream流int sum3 = IntStream.of(nums).map(i->i*2).sum();System.out.println("内部迭代加map中间操作"+sum3);//惰性求值int sum4 = IntStream.of(nums).map(TestStream1::doubleNum).sum();System.out.println("调用了终止操作,结果:"+sum4);IntStream.of(nums).map(TestStream1::doubleNum);System.out.println("没调用终止操作,不会执行");}public static int doubleNum(int i) {System.out.println("乘以2");return i*2;}
}
public class TestStream2 {public static void main(String[] args) {/*** 流的创建*/List<String> list = new ArrayList<>();//通过collection创建一个流Stream<String> stream = list.stream();//并行流Stream<String> stream2 = list.parallelStream();int[] nums = {5,6,7,8};//通过数组创建一个int流IntStream stream3 = Arrays.stream(nums);String str1 = "my name is wm1";String str2 = "my name is wm2";String str3 = "my name is wm3";System.out.println("流的中间操作map");//流的中间操作mapSystem.out.println(stream3.map(i->i+1).sum());System.out.println("======================================");/** 通过int直接创建int流* 中间操作过滤,排序,去重* 终止操作循环*/System.out.println("流的中间操作filter,sorted,distnct,peek,终止操作sum");System.out.println("结果:"+IntStream.of(5,6,1,2,1,-5).filter(i->i>0).sorted().distinct().peek(System.out::println).sum());System.out.println("======================================");/** flatMap,得到A中的B的全部列表 */System.out.println("流的中间操作flatMap");Stream.of(str1.split(" ")).flatMap(i->i.chars().boxed()).forEach(i->{System.out.println((char)i.intValue());});System.out.println("======================================");/*** 流的终止操作*///forEachOrdered并行流,保证顺序System.out.println("流的终止操作,并行流foreach,第一种,不建议");Stream.of(str2.split(" ")).parallel().flatMap(i->i.chars().boxed()).forEachOrdered(i->System.out.println((char)i.intValue()));//第二种方法,建议System.out.println("流的终止操作,并行流foreach,第二种,建议");str3.chars().parallel().forEachOrdered(i->System.out.println((char)i));System.out.println("去空格版本");str3.replace(" ", "").chars().parallel().forEachOrdered(i->System.out.println((char)i));System.out.println("======================================");System.out.println("collect操作");Stream.of(str3.split(" ")).collect(Collectors.toList()).forEach(System.out::println);System.out.println("toArray");Stream.of(Stream.of(str3.split(" ")).toArray()).forEach(System.out::println);System.out.println("reduce组合数据");Optional<String> reduce = Stream.of(str3.split(" ")).reduce((s1,s2)->s1+"|"+s2);System.out.println(reduce.orElse(""));System.out.println("MAX");Optional<String> max = Stream.of(str1.split(" ")).max((s1,s2)->s1.length()-s2.length());System.out.println(max.orElse(""));System.out.println("min");Optional<String> min = Stream.of(str1.split(" ")).min((s1,s2)->s1.length()-s2.length());System.out.println(min.orElse(""));}
}
/*** 并行流* @author maybe**/
public class TestStream3 {public static void main(String[] args) {//使用JDK自带的线程池 ForkJoinPool.commonPool
// System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "3");
// IntStream.range(1, 100).parallel().peek(i->{
// System.out.println(Thread.currentThread().getName()+":"+i);
// try {
// TimeUnit.SECONDS.sleep(3);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }).count();//使用自己写的线程池 ForkJoinPool-1ForkJoinPool forkJoinPool = new ForkJoinPool(6);forkJoinPool.submit(()->{IntStream.range(1, 100).parallel().peek(i->{System.out.println(Thread.currentThread().getName()+":"+i);try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}}).count();;});forkJoinPool.shutdown();synchronized (forkJoinPool) {try {forkJoinPool.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}
实践示例
有关效率:其实对于arraylist而言,传统for循环的随机遍历,效率更高一些。迭代器遍历对于LinkedList这类链表遍历更有效率。这段其实我也是略懂。可能有错误,这里不多说无关的了。有空再深入研究一下相关源码。
下面以我做所的业务架设了一个场景来做一组栗子:
一个实体javabean:
public class Flight {@JSONField(ordinal = 1)private int id; //id@JSONField(ordinal = 2)private String flightNo; //航班号@JSONField(format = "yyyy-MM-dd",ordinal = 3)private Date qf; //起飞时间@JSONField(format = "yyyy-MM-dd",ordinal = 4)private Date dd;//到达时间private String remarks; //航班备注private String money;//航班金额public Flight() {}public Flight(int id,String flightNo,Date qf,Date dd) {this.id = id;this.flightNo = flightNo;this.qf = qf;this.dd = dd;}@Overridepublic String toString() {return JSONObject.toJSONString(this,true);}省略get set方法......
}
这里简单讲一下:框架使用spring boot,并且使用fastjson,json解析框架。回头有时间会单独写有关fastjson相关内容。
@JSONField(ordinal = 1) 指定输出json格式时排序,低的在前。
@JSONField(format = "yyyy-MM-dd",ordinal = 3) 指定date类型输出格式
JSONObject.toJSONString(this,true); fastjson 方法,输出json格式字符串,true代表使用标准的json格式输出
下面开始先生成一个list集合来模拟查询出来的航班信息:
Flight[] flights = {new Flight(1,"sc111",new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-02"),new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-03")),new Flight(2,"sc222",new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-04"),new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-05")),new Flight(3,"sc333",new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-06"),new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-07"))};
List<Flight> flights2 = Arrays.asList(flights);
需求1:遍历出来所有的航班
//for循环for(Flight f :flights2) {System.out.println(f.toString());}//foreachflights2.forEach((f)->System.out.println(f.toString()));
简单解释一下:
这里调用的foreach方法,查看源码:
void forEach(Consumer<? super T> action);
找到Consumer接口:
@FunctionalInterface
public interface Consumer<T> {/*** Performs this operation on the given argument.** @param t the input argument*/void accept(T t);/*** Returns a composed {@code Consumer} that performs, in sequence, this* operation followed by the {@code after} operation. If performing either* operation throws an exception, it is relayed to the caller of the* composed operation. If performing this operation throws an exception,* the {@code after} operation will not be performed.** @param after the operation to perform after this operation* @return a composed {@code Consumer} that performs in sequence this* operation followed by the {@code after} operation* @throws NullPointerException if {@code after} is null*/default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}
}
所以我们的lambda表达式实现的是Consumer接口的accept方法。参数列表为(T t),T为该类动态指定的泛型。关于泛型之后也会专门写文章来记录。
所以这个lambda写时需要一个参数列表,可以省略类型。 后面是遍历之后的抽象方法的具体实现。
结果都是:
{"id":1,"flightNo":"sc111","qf":"2018-01-02","dd":"2018-01-03"
}
{"id":2,"flightNo":"sc222","qf":"2018-01-04","dd":"2018-01-05"
}
{"id":3,"flightNo":"sc333","qf":"2018-01-06","dd":"2018-01-07"
}
需求2:遍历出起飞时间晚于2018-01-03的航班
//for
for(Flight f :flights2) {if(f.getQf().after(date)) {System.out.println(f.toString());}
}
//lambda
flights2.stream().filter((f)->f.getQf().after(date)).forEach((p)->System.out.println(p.toString()));
先将这个list转入stream流处理。调用filter过滤器,查看源码:
Stream<T> filter(Predicate<? super T> predicate);
找到Predicate接口(部分):
@FunctionalInterface
public interface Predicate<T> {/*** Evaluates this predicate on the given argument.** @param t the input argument* @return {@code true} if the input argument matches the predicate,* otherwise {@code false}*/boolean test(T t);
所以,我们这里实现filter方法,需要返回值为boolean类型。然后进行foreach遍历。
结果:
{"id":2,"flightNo":"sc222","qf":"2018-01-04","dd":"2018-01-05"
}
{"id":3,"flightNo":"sc333","qf":"2018-01-06","dd":"2018-01-07"
}
需求3:遍历出起飞时间晚于2018-01-03的航班,并转成list,通过controller返回给前台json数据。
//for
List<Flight> flights3 = new ArrayList<Flight>();
for(Flight f :flights2) {if(f.getQf().after(date)) {flights3.add(f);}
}
return flights3;
//lambda
return flights2.stream().filter((f)->f.getQf().after(date)).collect(Collectors.toList());
collect方法用于收集一个流的结果。这里我们将这个流转为list
结果这里就不打印了。和需求2一致。
需求4:对这个list的数据进行处理。比如id为偶数的航班为中转航班,现在需要给这类航班添加一个备注信息。
//for
for(Flight a :flights2) {if(a.getId()%2==0) {a.setRemarks("这是一个中转航班");}System.out.println(a.toString());
}
//lambda
flights2.stream().filter((f)->f.getId()%2==0).forEach((a)->a.setRemarks("这是一个中转航班"));
flights2.forEach(System.out::println);
注意:使用了filter方法,这个流中就不再包含不符合这个filter的数据了。所以后面的foreach方法,是将过滤后符合的数据全部赋值,这时候如果同时遍历打印出所有的数据。那么这个例子中只有一条记录,因为只有2,符合条件。所以这时候要全部遍历打印集合内容,要在赋值后再写foreach方法遍历打印。
这里只有一行代码时,可以使用java8中的方法引用(::)
简单举个例子:有一个类,类里有1个方法,有如下情况:
1,类::静态方法,则函数式接口的抽象方法中的参数列表就是要传入调用的方法的参数列表。
2,实例:普通方法,参数列表同1;
3,类:实例方法,函数式接口的抽象方法的参数列表的额第一个参数为调用者,后面的为传入的参数列表。
4,构造器:类:new 参数列表同1;
这里,就是调用的的System.out的println方法。并且把一个参数(遍历后的对象)传入println中,默认调用toString来打印。
结果:
{"id":1,"flightNo":"sc111","qf":"2018-01-02","dd":"2018-01-03"
}
{"remarks":"这是一个中转航班","id":2,"flightNo":"sc222","qf":"2018-01-04","dd":"2018-01-05"
}
{"id":3,"flightNo":"sc333","qf":"2018-01-06","dd":"2018-01-07"
}
由于remarks没有加入排序,顺序乱了。这里先不管他了。
需求5:现在有一个模块。只需要航班和该航班的起飞时间。所以我们要把航班id和起飞时间对应起来。组合一个map
//for
Map<Integer, Date> map = new HashMap<Integer, Date>();
for(Flight a :flights2) {map.put(a.getId(), a.getQf());
}
for(Map.Entry<Integer, Date> entry : map.entrySet()) {System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
//lambda
flights2.stream().collect(Collectors.toMap(Flight::getId, Flight::getQf)).forEach((k,v)->System.out.println("k:"+k+",V:"+v));
结果:
key:1,value:Tue Jan 02 00:00:00 CST 2018
key:2,value:Thu Jan 04 00:00:00 CST 2018
key:3,value:Sat Jan 06 00:00:00 CST 2018
k:1,V:Tue Jan 02 00:00:00 CST 2018
k:2,V:Thu Jan 04 00:00:00 CST 2018
k:3,V:Sat Jan 06 00:00:00 CST 2018
栗子大概就这么多。关于lambda表达式我觉得用处还是挺多的。有空继续深入学习吧。
Java基础 之 lambda、stream概念及实际使用举例相关推荐
- java基础之lambda表达式
java基础之lambda表达式 1 什么是lambda表达式 lambda表达式是一个匿名函数,允许将一个函数作为另外一个函数的参数,将函数作为参数传递(可理解为一段传递的代码). 2 为什么要用l ...
- Java基础之面向对象的概念 继承---组合----枚举类
Java基础之面向对象的概念 继承---组合----枚举类 本章作为面向对象概念的最后一篇,但是作为一名java程序员在面向对象的这条路上还很长. 一.继承与组合简介 继承是实现类重用的重要手段,但是 ...
- JAVA基础知识|lambda与stream
lambda与stream是java8中比较重要两个新特性,lambda表达式采用一种简洁的语法定义代码块,允许我们将行为传递到函数中.之前我们想将行为传递到函数中,仅有的选择是使用匿名内部类,现在我 ...
- Java基础知识(一) 基本概念
Java基础知识 基本概念 1. Java语言的优点 2. Java与c/c++有什么异同 3. 为什么需要public static void main(String[] args)这个方法 4. ...
- Java基础学习Lambda与Stream(续)
/* 中间操作 : 映射 -> 将stream操作的每个元素转换成另外一个种形式|提取信息 <R> Stream<R> map(Function& ...
- java基础知识——面向对象基本概念
文章目录 Java基本概念 源文件声明规则 Java包 Import语句 继承类型 继承的特性 继承关键字 super 与 this 关键字 构造器 方法的重写规则 重载(Overload) 重写与重 ...
- Java基础教程(4)--面向对象概念
如果你之前从来没有使用过面向对象编程语言,那么在学习Java之前需要先理解几个有关面向对象编程的基本概念.这篇教程将会向你介绍对象.类.集成.接口和包的概念,以及这些概念是如何与现实世界相关联,并 ...
- [Java基础]体验Lambda表达式
普通写法: 代码如下: package LambdaPack01;public class MyRunnable implements Runnable{@Overridepublic void ru ...
- Stream 流 【学习笔记】Java 基础
若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 写这篇博客旨在制作笔记,方便个人在线阅览,巩固知识,无其他用途. 学习视频 [黑马 Java 基础教程] ...
最新文章
- Windows搭建以太坊的私有链环境
- 机器人过程自动化的10个秘密
- 2020携程“BOSS直播”大数据发布:GMV累计超11亿
- python四大高阶函数_详谈Python高阶函数与函数装饰器(推荐)
- 为什么互联网能创造商业奇迹——我的互联网产品观
- java每秒向mysql写一条记录_【Java】mysql一条记录在高并发场景下读写?
- 获取数组名称 php,php 获取美国50个州的名称、简写对应数组用法示例
- python 硬件模拟_如何编写一个硬件模拟器?
- 一些自己常用的linux命令笔记
- oneinstack 部署vue项目
- chrony时间同步配置
- 最大子列和问题(PTA)
- 简单家用nas搭建,只需要这个路由器
- qq(q音乐)扫码授权登陆分析及python实现
- word2010中“不包含”、“不存在”等特殊字符的输入方法
- 爬虫【3】URL地址编码
- 颜值开路,带货千万,看小霸宠如何低成本运作
- Java大数据学习路线图
- JavaScript面向对象入门
- Apache APISIX 扩展指南
热门文章
- 济南信息安全服务资质认证申请流程
- 全球与中国搅拌站燃烧器市场现状及未来发展趋势
- continue和break的区别(continual和continuous的区别)
- 地级市GDP地级市一二三产业GDP面板数据(1999-2021年)
- java毕业生设计在线玩具租赁系统计算机源码+系统+mysql+调试部署+lw
- 【java】 SHIYAN5.java使用了未经检查或不安全的操作。注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
- 一个小巧的PDF阅读器
- 【微信小程序】下载并预览文档——pdf、word、excel等多种类型
- Microsoft Virtual Academy 提取视频字幕
- 基于asp.net的社区人口信息管理系统