(一)常用函数接口
(二)Stream流式思想概述
(三)Stream流的获取方式&基本使用
(四)方法引用
函数式接口的概念
函数式接口在Java中是指:有且仅有一个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口
而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口
只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导
备注:
“语法糖”是指使用更加方便,但是原理不变的代码语法
例如在遍历集合时使用的for-each语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”
从应用层面来讲,Java中的Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的
注解:
@FunctionalInterface注解
作用:可以检测接口是否是一个函数式接口
是:编译成功
否:编译失败(接口中没有抽象方法抽象方法的个数多余1个)
@FunctionalInterface
public interface MyFunctionalInterface {
//定义一个抽象方法
public abstract void method();
}
函数式接口的基本使用
函数式接口一般可以作为方法的参数和返回值类型
public class Demo {
//定义一个方法,参数使用函数式接口MyFunctionalInterface
public static void show(MyFunctionalInterface myInter){
myInter.method();
}
public static void main(String[] args) {
//调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
show(new MyFunctionalInterfaceImpl());
//调用show方法,方法的参数是一个接口,所以我们可以传递接口的匿名内部类
show(new MyFunctionalInterface() {
@Override
public void method() {
System.out.println("使用匿名内部类重写接口中的抽象方法");
}
});
//调用show方法,方法的参数是一个函数式接口,所以我们可以Lambda表达式
show(()->{
System.out.println("使用Lambda表达式重写接口中的抽象方法");
});
//简化Lambda表达式
show(()-> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
}
}
性能浪费的日志案例
概述:
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费
而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能
代码实现:
public class Demo01Logger {
//定义一个根据日志的级别,显示日志信息的方法
public static void showLog(int level, String message){
//对日志的等级进行判断,如果是1级别,那么输出日志信息
if(level==1){
System.out.println(message);
}
}
public static void main(String[] args) {
//定义三个日志信息
String msg1 = "Hello";
String msg2 = "World";
String msg3 = "Java";
//调用showLog方法,传递日志级别和日志信息
showLog(2,msg1+msg2+msg3);
}
}
发现以上代码存在的一些性能浪费的问题
调用showLog方法,传递的第二个参数是一个拼接后的字符串
先把字符串拼接好,然后在调用showLog方法
showLog方法中如果传递的日志等级不是1级
那么就不会是如此拼接后的字符串
所以感觉字符串就白拼接了,存在了浪费
使用Lambda优化日志案例
Lambda的特点:延迟加载
Lambda的使用前提,必须存在函数式接口
代码实现:
@FunctionalInterface
public interface MessageBuilder {
//定义一个拼接消息的抽象方法,返回被拼接的消息
public abstract String builderMessage();
}
public class Demo02Lambda {
//定义一个显示日志的方法,方法的参数传递日志的等级和MessageBuilder接口
public static void showLog(int level, MessageBuilder mb) {
//对日志的等级进行判断,如果是1级,则调用MessageBuilder接口中的builderMessage方法
if (level == 1) {
System.out.println(mb.builderMessage());
}
}
public static void main(String[] args) {
//定义三个日志信息
String msg1 = "Hello";
String msg2 = "World";
String msg3 = "Java";
/*
使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中
只有满足条件,日志的等级是1级
才会调用接口MessageBuilder中的方法builderMessage
才会进行字符串的拼接
如果条件不满足,日志的等级不是1级
那么MessageBuilder接口中的方法builderMessage也不会执行
所以拼接字符串的代码也不会执行
所以不会存在性能的浪费
*/
showLog(1, () -> {
System.out.println("满足条件才执行"); //当level=2时,这一句都不会执行
//返回一个拼接好的字符串
return msg1 + msg2 + msg3;
});
}
}
函数式接口作为方法的参数案例
例如java.lang.Runnable
接口就是一个函数式接口
假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参
这种情况其实和Thread类的构造方法参数为Runnable没有本质区别,但是底层原理是不一样的
代码实现:
public class Demo01Runnable {
//定义一个方法startThread,方法的参数使用函数式接口Runnable
public static void startThread(Runnable run){
//开启多线程
new Thread(run).start();
}
public static void main(String[] args) {
//调用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");
}
});
//调用startThread方法,方法的参数是一个函数式接口,所以可以传递Lambda表达式
startThread(()->{
System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");
});
//优化Lambda表达式
startThread(()->System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了"));
}
}
函数式接口作为方法的返回值类型
如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式
当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调该方法获取
代码实现:
public class Demo02Comparator {
//定义一个方法,方法的返回值类型使用函数式接口Comparator
public static Comparator<String> getComparator(){
//方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
/*return new Comparator() {
@Override
public int compare(String o1, String o2) {
//按照字符串的降序排序
return o2.length()-o1.length();
}
};*/
//方法的返回值类型是一个函数式接口,所有我们可以返回一个Lambda表达式
/*return (String o1, String o2)->{
//按照字符串的降序排序
return o2.length()-o1.length();
};*/
//继续优化Lambda表达式
return (o1, o2)->o2.length()-o1.length();
}
public static void main(String[] args) {
//创建一个字符串数组
String[] arr = {"aaa","b","cccccc","dddddddddddd"};
//输出排序前的数组
System.out.println(Arrays.toString(arr));//[aaa, b, cccccc, dddddddddddd]
//调用Arrays中的sort方法,对字符串数组进行排序
Arrays.sort(arr,getComparator());
//输出排序后的数组
System.out.println(Arrays.toString(arr));//[dddddddddddd, cccccc, aaa, b]
}
}
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景
它们主要在java.util.function
包中被提供
下面是最简单的几个接口及使用示例
常用的函数式接口:Supplier接口(一)
java.util.function.Supplier接口仅包含一个无参的方法:T get()。
用来获取一个泛型参数指定类型的对象数据
Supplier接口被称之为生产型接口
指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据
代码实现:
public class Demo01Supplier {
//定义一个方法,方法的参数传递Supplier接口,泛型执行String,get方法就会返回一个String
public static String getString(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args) {
//调用getString方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
String s = getString(()->{
//生产一个字符串,并返回
return "胡歌";
});
System.out.println(s);
//优化Lambda表达式
String s2 = getString(()->"胡歌");
System.out.println(s2);
}
}
常用的函数式接口:Supplier接口(二)
练习:求数组元素最大值
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值
提示:接口的泛型请使用java.lang.Integer类
代码实现:
public class Demo02Test {
//定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用Integer
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
//定义一个int类型的数组,并赋值
int[] arr = {100,0,-50,880,99,33,-30};
//调用getMax方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
int maxValue = getMax(()->{
//获取数组的最大值,并返回
//定义一个变量,把数组中的第一个元素赋值给该变量,记录数组中元素的最大值
int max = arr[0];
//遍历数组,获取数组中的其他元素
for (int i : arr) {
//使用其他的元素和最大值比较
if(i>max){
//如果i大于max,则替换max作为最大值
max = i;
}
}
//返回最大值
return max;
});
System.out.println("数组中元素的最大值是:"+maxValue);
}
}
常用的函数式接口:Consumer接口(一)
java.util.function.Consumer接口则正好与Supplier接口相反
它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定
Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据
Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
至于具体怎么消费(使用),需要自定义(输出,计算....)
代码实现:
public class Demo01Consumer {
/*
定义一个方法
方法的参数传递一个字符串的姓名
方法的参数传递Consumer接口,泛型使用String
可以使用Consumer接口消费字符串的姓名
*/
public static void method(String name, Consumer<String> con){
con.accept(name);
}
public static void main(String[] args) {
//调用method方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
method("赵丽颖",(String name)->{
//对传递的字符串进行消费
//消费方式:直接输出字符串
//System.out.println(name);
//消费方式:把字符串进行反转输出
String reName = new StringBuffer(name).reverse().toString();
System.out.println(reName);
});
}
}
常用的函数式接口:Consumer接口(二)
Consumer接口的默认方法andThen()
作用:
需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费
例如:
Consumer con1
Consumer con2
String s = "hello";
con1.accept(s);
con2.accept(s);
连接两个Consumer接口 再进行消费
con1.andThen(con2).accept(s); 谁写前边谁先消费
代码实现:
public class Demo02AndThen {
//定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串
public static void method(String s, Consumer<String> con1 ,Consumer<String> con2){
//con1.accept(s);
//con2.accept(s);
//使用andThen方法,把两个Consumer接口连接到一起,在消费数据
con1.andThen(con2).accept(s);//con1连接con2,先执行con1消费数据,在执行con2消费数据
}
public static void main(String[] args) {
//调用method方法,传递一个字符串,两个Lambda表达式
method("Hello",
(t)->{
//消费方式:把字符串转换为大写输出
System.out.println(t.toUpperCase());
},
(t)->{
//消费方式:把字符串转换为小写输出
System.out.println(t.toLowerCase());
});
}
}
常用的函数式接口:Consumer接口(三)
练习:
字符串数组当中存有多条信息,请按照格式“姓名:XX。性别:XX。”的格式将信息打印出来
要求将打印姓名的动作作为第一个Consumer接口的Lambda实例
将打印性别的动作作为第二个Consumer接口的Lambda实例
将两个Consumer接口按照顺序“拼接”到一起
代码实现:
public class Demo03Test {
//定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
public static void printInfo(String[] arr, Consumer<String> con1,Consumer<String> con2){
//遍历字符串数组
for (String message : arr) {
//使用andThen方法连接两个Consumer接口,消费字符串
con1.andThen(con2).accept(message);
}
}
public static void main(String[] args) {
//定义一个字符串类型的数组
String[] arr = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
//调用printInfo方法,传递一个字符串数组,和两个Lambda表达式
printInfo(arr,(message)->{
//消费方式:对message进行切割,获取姓名,按照指定的格式输出
String name = message.split(",")[0];
System.out.print("姓名: "+name);
},(message)->{
//消费方式:对message进行切割,获取年龄,按照指定的格式输出
String age = message.split(",")[1];
System.out.println("。年龄: "+age+"。");
});
}
}
常用的函数式接口:Predicate接口(一)
java.util.function.Predicate接口
作用:对某种数据类型的数据进行判断,结果返回一个boolean值
Predicate接口中包含一个抽象方法:
boolean test(T t):用来对指定数据类型数据进行判断的方法
结果:
符合条件,返回true
不符合条件,返回false
代码实现:
public class Demo01Predicate {
/*
定义一个方法
参数传递一个String类型的字符串
传递一个Predicate接口,泛型使用String
使用Predicate中的方法test对字符串进行判断,并把判断的结果返回
*/
public static boolean checkString(String s, Predicate<String> pre){
return pre.test(s);
}
public static void main(String[] args) {
//定义一个字符串
String s = "abcdef";
//调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式
/*boolean b = checkString(s,(String str)->{
//对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回
return str.length()>5;
});*/
//优化Lambda表达式
boolean b = checkString(s,str->str.length()>5);
System.out.println(b);
}
}
常用的函数式接口:Predicate接口(二) and
逻辑表达式:可以连接多个判断的条件
&&:与运算符,有false则false
||:或运算符,有true则true
!:非(取反)运算符,非真则假,非假则真
需求:判断一个字符串,有两个判断的条件
1.判断字符串的长度是否大于5
2.判断字符串中是否包含a
两个条件必须同时满足,我们就可以使用&&运算符连接两个条件
Predicate接口中有一个方法and,表示并且关系,也可以用于连接两个判断条件
default Predicate and(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> this.test(t) && other.test(t);
}
方法内部的两个判断条件,也是使用&&运算符连接起来的
代码实现:
public class Demo02Predicate_and {
/*
定义一个方法,方法的参数,传递一个字符串
传递两个Predicate接口
一个用于判断字符串的长度是否大于5
一个用于判断字符串中是否包含a
两个条件必须同时满足
*/
public static boolean checkString(String s, Predicate<String> pre1,Predicate<String> pre2){
//return pre1.test(s) && pre2.test(s);
return pre1.and(pre2).test(s);//等价于return pre1.test(s) && pre2.test(s);
}
public static void main(String[] args) {
//定义一个字符串
String s = "abcdef";
//调用checkString方法,参数传递字符串和两个Lambda表达式
boolean b = checkString(s,(String str)->{
//判断字符串的长度是否大于5
return str.length()>5;
},(String str)->{
//判断字符串中是否包含a
return str.contains("a");
});
System.out.println(b);
}
}
常用的函数式接口:Predicate接口(三) or
需求:判断一个字符串,有两个判断的条件
1.判断字符串的长度是否大于5
2.判断字符串中是否包含a
满足一个条件即可,我们就可以使用||运算符连接两个条件
Predicate接口中有一个方法or,表示或者关系,也可以用于连接两个判断条件
default Predicate or(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
方法内部的两个判断条件,也是使用||运算符连接起来的
代码实现:
public class Demo03Predicate_or {
/*
定义一个方法,方法的参数,传递一个字符串
传递两个Predicate接口
一个用于判断字符串的长度是否大于5
一个用于判断字符串中是否包含a
满足一个条件即可
*/
public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2){
//return pre1.test(s) || pre2.test(s);
return pre1.or(pre2).test(s);//等价于return pre1.test(s) || pre2.test(s);
}
public static void main(String[] args) {
//定义一个字符串
String s = "bc";
//调用checkString方法,参数传递字符串和两个Lambda表达式
boolean b = checkString(s,(String str)->{
//判断字符串的长度是否大于5
return str.length()>5;
},(String str)->{
//判断字符串中是否包含a
return str.contains("a");
});
System.out.println(b);
}
}
常用的函数式接口:Predicate接口(四) negate
需求:判断一个字符串长度是否大于5
如果字符串的长度大于5,那返回false
如果字符串的长度不大于5,那么返回true
所以我们可以使用取反符号!对判断的结果进行取反
Predicate接口中有一个方法negate,也表示取反的意思
default Predicate negate() {
return (t) -> !test(t);
}
代码实现:
public class Demo04Predicate_negate {
/*
定义一个方法,方法的参数,传递一个字符串
使用Predicate接口判断字符串的长度是否大于5
*/
public static boolean checkString(String s, Predicate<String> pre){
//return !pre.test(s);
return pre.negate().test(s);//等效于return !pre.test(s);
}
public static void main(String[] args) {
//定义一个字符串
String s = "abc";
//调用checkString方法,参数传递字符串和Lambda表达式
boolean b = checkString(s,(String str)->{
//判断字符串的长度是否大于5,并返回结果
return str.length()>5;
});
System.out.println(b);
}
}
常用的函数式接口:Predicate接口(五)
练习:集合信息筛选
数组当中有多条“姓名+性别”的信息如下,
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,
需要同时满足两个条件:
1. 必须为女生;
2. 姓名为4个字。
分析:
1.有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断
2.必须同时满足两个条件,所以可以使用and方法连接两个判断条件
代码实现:
public class Demo05Test {
/*
定义一个方法
方法的参数传递一个包含人员信息的数组
传递两个Predicate接口,用于对数组中的信息进行过滤
把满足条件的信息存到ArrayList集合中并返回
*/
public static ArrayList<String> filter(String[] arr,Predicate<String> pre1,Predicate<String> pre2){
//定义一个ArrayList集合,存储过滤之后的信息
ArrayList<String> list = new ArrayList<>();
//遍历数组,获取数组中的每一条信息
for (String s : arr) {
//使用Predicate接口中的方法test对获取到的字符串进行判断
boolean b = pre1.and(pre2).test(s);
//对得到的布尔值进行判断
if(b){
//条件成立,两个条件都满足,把信息存储到ArrayList集合中
list.add(s);
}
}
//把集合返回
return list;
}
public static void main(String[] args) {
//定义一个储存字符串的数组
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
//调用filter方法,传递字符串数组和两个Lambda表达式
ArrayList<String> list = filter(array,(String s)->{
//获取字符串中的性别,判断是否为女
return s.split(",")[1].equals("女");
},(String s)->{
//获取字符串中的姓名,判断长度是否为4个字符
return s.split(",")[0].length()==4;
});
//遍历集合
for (String s : list) {
System.out.println(s);
}
}
}
常用的函数式接口:Function接口(一)
java.util.function.Function接口用来根据一个类型的数据得到另一个类型的数据,
前者称为前置条件,后者称为后置条件。
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
使用的场景例如:将String类型转换为Integer类型。
代码实现:
public class Demo01Function {
/*
定义一个方法
方法的参数传递一个字符串类型的整数
方法的参数传递一个Function接口,泛型使用
使用Function接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数
*/
public static void change(String s, Function<String,Integer> fun){
//Integer in = fun.apply(s);
int in = fun.apply(s);//自动拆箱 Integer->int
System.out.println(in);
}
public static void main(String[] args) {
//定义一个字符串类型的整数
String s = "1234";
//调用change方法,传递字符串类型的整数,和Lambda表达式
change(s,(String str)->{
//把字符串类型的整数,转换为Integer类型的整数返回
return Integer.parseInt(str);
});
//优化Lambda
change(s,str->Integer.parseInt(str));
}
}
常用的函数式接口:Function接口(二)
Function接口中的默认方法andThen:用来进行组合操作
需求:
把String类型的"123",转换为Inteter类型,把转换后的结果加10
把增加之后的Integer类型的数据,转换为String类型
分析:
转换了两次
第一次是把String类型转换为了Integer类型
所以我们可以使用Function fun1
Integer i = fun1.apply("123")+10;
第二次是把Integer类型转换为String类型
所以我们可以使用Function fun2
String s = fun2.apply(i);
我们可以使用andThen方法,把两次转换组合在一起使用
String s = fun1.andThen(fun2).apply("123");
fun1先调用apply方法,把字符串转换为Integer
fun2再调用apply方法,把Integer转换为字符串
代码实现:
public class Demo02Function_andThen {
/*
定义一个方法
参数串一个字符串类型的整数
参数再传递两个Function接口
一个泛型使用Function
一个泛型使用Function
*/
public static void change(String s, Function<String,Integer> fun1,Function<Integer,String> fun2){
String ss = fun1.andThen(fun2).apply(s);
System.out.println(ss);
}
public static void main(String[] args) {
//定义一个字符串类型的整数
String s = "123";
//调用change方法,传递字符串和两个Lambda表达式
change(s,(String str)->{
//把字符串转换为整数+10
return Integer.parseInt(str)+10;
},(Integer i)->{
//把整数转换为字符串
return i+"";
});
//优化Lambda表达式
change(s,str->Integer.parseInt(str)+10,i->i+"");
}
}
使用传统的方式遍历集合
对集合中的数据进行过滤
代码实现:
public class Demo01List {
public static void main(String[] args) {
//创建一个List集合,存储姓名
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
List<String> listA = new ArrayList<>();
for(String s : list){
if(s.startsWith("张")){
listA.add(s);
}
}
//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
List<String> listB = new ArrayList<>();
for (String s : listA) {
if(s.length()==3){
listB.add(s);
}
}
//遍历listB集合
for (String s : listB) {
System.out.println(s);
}
}
}
说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?
在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念
用于解决已有集合类库既有的弊端
使用Stream流的方式遍历集合
对集合中的数据进行过滤
Stream流是JDK1.8之后出现的, 关注的是做什么,而不是怎么做
代码实现:
public class Demo02Stream {
public static void main(String[] args) {
//创建一个List集合,存储姓名
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
//遍历listB集合
list.stream()
.filter(name->name.startsWith("张"))
.filter(name->name.length()==3)
.forEach(name-> System.out.println(name));
}
}
流式思想概述
注意:请暂时忘记对传统IO流的固有印象!
整体来看,流式思想类似于工厂车间的"生产流水线"
和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining:
中间操作都会返回流对象本身。
这样多个操作可以串联成一个管道, 如同流式风格这样做可以对操作进行优化
比如延迟执行(laziness)和短路( short-circuiting)
内部迭代:
以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代,这叫做外部迭代
Stream提供了内部迭代的方式,流可以直接调用遍历方法
两种获取Stream流的方式
java.util.stream.Stream
是Java 8新加入的最常用的流接口(这并不是一个函数式接口)
获取一个流非常简单,有以下几种常用的方式:
所有的Collection集合都可以通过stream默认方法获取流:
default Stream stream()
Stream接口的静态方法of可以获取数组对应的流:
static Stream of(T... values)
参数是一个可变参数,那么我们就可以传递一个数组
代码实现:
public class Demo01GetStream {
public static void main(String[] args) {
//把集合转换为Stream流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String,String> map = new HashMap<>();
//获取键,存储到一个Set集合中
Set<String> keySet = map.keySet();
Stream<String> stream3 = keySet.stream();
//获取值,存储到一个Collection集合中
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
//获取键值对(键与值的映射关系 entrySet)
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String, String>> stream5 = entries.stream();
//把数组转换为Stream流
Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5);
//可变参数可以传递数组
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream7 = Stream.of(arr);
String[] arr2 = {"a","bb","ccc"};
Stream<String> stream8 = Stream.of(arr2);
}
}
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
count()
和forEach()
方法)Stream流中的常用方法:forEach
虽然方法名字叫 forEach ,但是与for循环中的“for-each”昵称不同
void forEach(Consumer super T> action);
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理
Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,消费数据
Consumer接口的抽象方法:void accept(T t); 意为消费一个指定泛型的数据
简单记:
forEach方法,用来遍历流中的数据
该方法是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
代码实现:
public class Demo02Stream_forEach {
public static void main(String[] args) {
//获取一个Stream流
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");
//使用Stream流中的方法forEach对Stream流中的数据进行遍历
/*stream.forEach((String name)->{
System.out.println(name);
});*/
stream.forEach(name->System.out.println(name));
}
}
Stream流中的常用方法:filter
Stream
filter方法的参数Predicate是一个函数式接口,可以对数据进行过滤
Predicate接口是一个判断型的函数式接口,可以传递Lambda表达式,判断数据
Predicate接口的抽象方法:boolean test(T t); 意为判断一个指定泛型的数据
简单记:
filter方法,用来对Stream流中的数据进行过滤
该方法是一个延迟方法,可以链式调用
代码实现:
public class Demo03Stream_filter {
public static void main(String[] args) {
//创建一个Stream流
Stream<String> stream = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
//对Stream流中的元素进行过滤,只要姓张的人
Stream<String> stream2 = stream.filter((String name)->{return name.startsWith("张");});
//遍历stream2流
stream2.forEach(name-> System.out.println(name));
/*
Stream流属于管道流,只能被消费(使用)一次
第一个Stream流调用完毕方法,数据就会流转到下一个Stream上
而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个Stream流就不能再调用方法了
IllegalStateException: stream has already been operated upon or closed
ps:可以链式编程
*/
//遍历stream流
stream.forEach(name-> System.out.println(name));
}
}
Stream流中的常用方法:map
如果需要将流中的元素映射到另一个流中,可以使用map方法
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
Function中的抽象方法:R apply(T t); 根据类型T的参数获取类型R的结果
简单记:
map方法,用于类型转换
该方法是一个延迟方法,可以链式调用
比如说:
上面存储“圆型”形状的是一个流,通过map()
方法之后,把所有的“圆形”转成“菱形”
并用存储“菱形”的新的流去存储它们
代码实现:
public class Demo04Stream_map {
public static void main(String[] args) {
//获取一个String类型的Stream流
Stream<String> stream = Stream.of("1", "2", "3", "4");
//使用map方法,把字符串类型的整数,转换(映射)为Integer类型的整数
Stream<Integer> stream2 = stream.map((String s)->{
return Integer.parseInt(s);
});
//遍历Stream2流
stream2.forEach(i-> System.out.println(i));
}
}
Stream流中的常用方法:count
long count();
该方法返回值是一个long类型的整数
简单记:
count方法,用于统计Stream流中元素的个数
该方法是一个终结方法,调用之后就不能继续调用Stream流中的其他方法
代码实现:
public class Demo05Stream_count {
public static void main(String[] args) {
//获取一个Stream流
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
Stream<Integer> stream = list.stream();
long count = stream.count();
System.out.println(count);//7
}
}
Stream流中的常用方法:limit
Stream
limit方法可以对流进行截取,只取用前n个
参数是一个long型,如果集合当前长度大于参数则进行截取,否则不进行操作
简单记:
limit方法,用于截取流中的元素
该方法是一个延迟方法,只是对流中的元素进行截取,可以继续调用Stream流中的其他方法
代码实现:
public class Demo06Stream_limit {
public static void main(String[] args) {
//获取一个Stream流
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
//使用limit对Stream流中的元素进行截取,只要前3个元素
Stream<String> stream2 = stream.limit(3);
//遍历stream2流
stream2.forEach(name-> System.out.println(name));
}
}
Stream流中的常用方法:skip
Stream
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流
简单记:
skip方法,用于跳过元素
该方法是一个延迟方法,可以继续调用Stream流中的其他方法
如果流的当前长度大于n,则跳过前n个,否则将会得到一个长度为0的空流
代码实现:
public class Demo07Stream_skip {
public static void main(String[] args) {
//获取一个Stream流
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
//使用skip方法跳过前3个元素
Stream<String> stream2 = stream.skip(3);
//遍历stream2流
stream2.forEach(name-> System.out.println(name));
}
}
Stream流中的常用方法:concat
static
如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat
简单记:
concat方法,用于把流组合到一起
该方法是一个延迟方法,可以继续调用Stream流中的其他方法
代码实现:
public class Demo08Stream_concat {
public static void main(String[] args) {
//创建一个Stream流
Stream<String> stream1 = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
//获取一个Stream流
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream2 = Stream.of(arr);
//把以上两个流组合为一个流
Stream<String> concat = Stream.concat(stream1, stream2);
//遍历concat流
concat.forEach(name-> System.out.println(name));
}
}
练习:集合元素处理(传统方式)
现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中
2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中
3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中
4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中
5. 将两个队伍合并为一个队伍;存储到一个新集合中
6. 根据姓名创建Person对象;存储到一个新集合中
7. 打印整个队伍的Person对象信息
代码实现:
public class Demo01StreamTest {
public static void main(String[] args) {
//第一支队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("庄子");
one.add("洪七公");
//1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
ArrayList<String> one1 = new ArrayList<>();
for (String name : one) {
if(name.length()==3){
one1.add(name);
}
}
//2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
ArrayList<String> one2 = new ArrayList<>();
for (int i = 0; i <3 ; i++) {
one2.add(one1.get(i));//i = 0,1,2
}
//第二支队伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
//3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
ArrayList<String> two1 = new ArrayList<>();
for (String name : two) {
if(name.startsWith("张")){
two1.add(name);
}
}
//4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
ArrayList<String> two2 = new ArrayList<>();
for (int i = 2; i <two1.size() ; i++) {
two2.add(two1.get(i)); //i 不包含0 1
}
//5. 将两个队伍合并为一个队伍;存储到一个新集合中。
ArrayList<String> all = new ArrayList<>();
all.addAll(one2);
all.addAll(two2);
//6. 根据姓名创建Person对象;存储到一个新集合中。
ArrayList<Person> list = new ArrayList<>();
for (String name : all) {
list.add(new Person(name));
}
//7. 打印整个队伍的Person对象信息。
for (Person person : list) {
System.out.println(person);
}
}
}
练习:集合元素处理(Stream流方式)
将上一题当中的传统for循环写法更换为Stream流式处理方式。
两个集合的初始内容不变,Person类的定义也不变。
代码实现:
public class Demo02StreamTest {
public static void main(String[] args) {
//第一支队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("庄子");
one.add("洪七公");
//1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
//2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
Stream<String> oneStream = one.stream().filter(name -> name.length() == 3).limit(3);
//第二支队伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
//3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
//4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
Stream<String> twoStream = two.stream().filter(name -> name.startsWith("张")).skip(2);
//5. 将两个队伍合并为一个队伍;存储到一个新集合中。
//6. 根据姓名创建Person对象;存储到一个新集合中。
//7. 打印整个队伍的Person对象信息。
Stream.concat(oneStream,twoStream).map(name->new Person(name)).forEach(p-> System.out.println(p));
}
}
方法引用的作用式:优化Lambda表达式
方法引用基本介绍
/*
定义一个打印的函数式接口
*/
@FunctionalInterface
public interface Printable {
//打印字符串的抽象方法
void print(String s);
}
public class Demo01Printable {
//定义一个方法,参数传递Printable接口,对字符串进行打印
public static void printString(Printable p) {
p.print("HelloWorld");
}
public static void main(String[] args) {
//调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda
printString((s) -> {
System.out.println(s);
});
/*
分析:
Lambda表达式的目的,打印参数传递的字符串
把参数s,传递给了System.out对象,调用out对象中的方法println对字符串进行了输出
注意:
1.System.out对象是已经存在的
2.println方法也是已经存在的
所以我们可以使用方法引用来优化Lambda表达式
可以使用System.out方法直接引用(调用)println方法
*/
printString(System.out::println);
}
}
原理:
双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用
如果Lambda要表达的函数方案已经存在于某个方法的实现中
那么则可以通过双冒号来引用该方法作为Lambda的替代者
对比:
Lambda表达式写法: s -> System.out.println(s);
方法引用写法: System.out::println
通过对象名引用成员方法
使用前提是对象名是已经存在的,成员方法也是已经存在
就可以使用对象名来引用成员方法
代码实现:
/*
定义一个打印的函数式接口
*/
@FunctionalInterface
public interface Printable {
//定义字符串的抽象方法
void print(String s);
}
public class MethodRerObject {
//定义一个成员方法,传递字符串,把字符串按照大写输出
public void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
public class Demo01ObjectMethodReference {
//定义一个方法,方法的参数传递Printable接口
public static void printString(Printable p){
p.print("Hello");
}
public static void main(String[] args) {
//调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda表达式
printString((s)->{
//创建MethodRerObject对象
MethodRerObject obj = new MethodRerObject();
//调用MethodRerObject对象中的成员方法printUpperCaseString,把字符串按照大写输出
obj.printUpperCaseString(s);
});
/*
使用方法引用优化Lambda
对象是已经存在的MethodRerObject
成员方法也是已经存在的printUpperCaseString
所以我们可以使用对象名引用成员方法
*/
//创建MethodRerObject对象
MethodRerObject obj = new MethodRerObject();
printString(obj::printUpperCaseString);
}
}
通过类名引用静态成员
类已经存在,静态成员方法也已经存在
就可以通过类名直接引用静态成员方法
代码实现:
@FunctionalInterface
public interface Calcable {
//定义一个抽象方法,传递一个整数,对整数进行绝对值计算并返回
int calsAbs(int number);
}
public class Demo01StaticMethodReference {
//定义一个方法,方法的参数传递要计算绝对值的整数,和函数式接口Calcable
public static int method(int number,Calcable c){
return c.calsAbs(number);
}
public static void main(String[] args) {
//调用method方法,传递计算绝对值得整数,和Lambda表达式
int number = method(-10,(n)->{
//对参数进行绝对值得计算并返回结果
return Math.abs(n);
});
System.out.println(number);
/*
使用方法引用优化Lambda表达式
Math类是存在的
abs计算绝对值的静态方法也是已经存在的
所以我们可以直接通过类名引用静态方法
*/
int number2 = method(-10,Math::abs);
System.out.println(number2);
}
}
通过super引用父类的成员方法
代码实现:
/*
定义见面的函数式接口
*/
@FunctionalInterface
public interface Greetable {
//定义一个见面的方法
void greet();
}
/*
定义父类
*/
public class Human {
//定义一个sayHello的方法
public void sayHello(){
System.out.println("Hello 我是Human!");
}
}
/*
定义子类
*/
public class Man extends Human{
//子类重写父类sayHello的方法
@Override
public void sayHello() {
System.out.println("Hello 我是Man!");
}
//定义一个方法参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
//调用method方法,方法的参数Greetable是一个函数式接口,所以可以传递Lambda
/*method(()->{
//创建父类Human对象
Human h = new Human();
//调用父类的sayHello方法
h.sayHello();
});*/
//因为有子父类关系,所以存在的一个关键字super,代表父类,所以我们可以直接使用super调用父类的成员方法
/* method(()->{
super.sayHello();
});*/
/*
使用super引用类的成员方法
super是已经存在的
父类的成员方法sayHello也是已经存在的
所以我们可以直接使用super引用父类的成员方法
*/
method(super::sayHello);
}
public static void main(String[] args) {
new Man().show();
}
}
通过this引用本类的成员方法
代码实现:
/*
定义一个富有的函数式接口
*/
@FunctionalInterface
public interface Richable {
//定义一个想买什么就买什么的方法
void buy();
}
public class Husband {
//定义一个买房子的方法
public void buyHouse(){
System.out.println("北京二环内买一套四合院!");
}
//定义一个结婚的方法,参数传递Richable接口
public void marry(Richable r){
r.buy();
}
//定义一个非常高兴的方法
public void soHappy(){
//调用结婚的方法,方法的参数Richable是一个函数式接口,传递Lambda表达式
/* marry(()->{
//使用this.成员方法,调用本类买房子的方法
this.buyHouse();
});*/
/*
使用方法引用优化Lambda表达式
this是已经存在的
本类的成员方法buyHouse也是已经存在的
所以我们可以直接使用this引用本类的成员方法buyHouse
*/
marry(this::buyHouse);
}
public static void main(String[] args) {
new Husband().soHappy();
}
}
类的构造器(构造方法)引用
代码实现:
/*
定义一个创建Person对象的函数式接口
*/
@FunctionalInterface
public interface PersonBuilder {
//定义一个方法,根据传递的姓名,创建Person对象返回
Person builderPerson(String name);
}
/*
类的构造器(构造方法)引用
*/
public class Demo {
//定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
public static void printName(String name,PersonBuilder pb){
Person person = pb.builderPerson(name);
System.out.println(person.getName());
}
public static void main(String[] args) {
//调用printName方法,方法的参数PersonBuilder接口是一个函数式接口,可以传递Lambda
printName("迪丽热巴",(String name)->{
return new Person(name);
});
/*
使用方法引用优化Lambda表达式
构造方法new Person(String name) 已知
创建对象已知 new
就可以使用Person引用new创建对象
*/
printName("古力娜扎",Person::new);//使用Person类的带参构造方法,通过传递的姓名创建对象
}
}
数组的构造器引用
代码实现:
/*
定义一个创建数组的函数式接口
*/
@FunctionalInterface
public interface ArrayBuilder {
//定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
int[] builderArray(int length);
}
/*
数组的构造器引用
*/
public class Demo {
/*
定义一个方法
方法的参数传递创建数组的长度和ArrayBuilder接口
方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
*/
public static int[] createArray(int length, ArrayBuilder ab){
return ab.builderArray(length);
}
public static void main(String[] args) {
//调用createArray方法,传递数组的长度和Lambda表达式
int[] arr1 = createArray(10,(len)->{
//根据数组的长度,创建数组并返回
return new int[len];
});
System.out.println(arr1.length);//10
/*
使用方法引用优化Lambda表达式
已知创建的就是int[]数组
数组的长度也是已知的
就可以使用方法引用
int[]引用new,根据参数传递的长度来创建数组
*/
int[] arr2 =createArray(10,int[]::new);
System.out.println(Arrays.toString(arr2));
System.out.println(arr2.length);//10
}
}