JDK8--函数式编程,Stream流
函数式接口:
概念:只有一个抽象方法的接口, 这种接口我们叫做函数式接口。
回顾一下 我们之前学过的函数式接口:
Runnable 接口 里面只有一个 run方法。
interface Runnable{
public abstract void run();
}
Comparable 接口 里面只有一个 compareTo方法
interface Comparable{
public abstract int compareTo(T t);
}
Comparator 接口 里面 compare方法。
函数式接口注解的作用:
情况: 项目经理让一个小白去写一个函数式接口。
@FunctionalInterface // 编译期起作用的注解。
interface Inter {
void show();
//void method(); //编译报错
//void fun(); // 编译报错
}
class Person {
public void show(){
}
}
class Student extends Person {
@Override // 编译期起作用的注解。
public void sh0w(){ //编译报错
}
}
函数式接口使用:
多态中去使用 --- 作为方法的参数 作为方法的返回值。
作为方法的参数 :
new Thread(
new Runnable(){
public void run(){
System.out.println("线程任务");
}
}
).start();
new Thread(()->System.out.println("线程任务") ).start();
new Thread(System.out::println ).start();
new Thread(
new Runnable(){
public void run(){
System.out.println();
}
}
).start();
作为方法的返回值 :
public class Test {
public static void main(String[] args){
TreeSet<Integer> ts = new TreeSet<>(getComparator());
ts.add(10);
ts.add(20);
ts.add(50);
ts.add(30);
ts.add(15);
}
public static Comparator<Integer> getComparator(){
return new Comparator<Integer>(){
public int compare(Integer in1, Integer in2){
//return in1 - in2 ; // 从小到大。
//return in2 - in1 ; // 从大到小。
return in2.compareTo(in1); // 从大到小。
}
};
return (in1,in2) -> in2-in1 ;
return (in1,in2) -> in2.compareTo(in1) ;
return Integer::compareTo ; // 从小到大。
return new Comparator<Integer>(){
public int compare(Integer in1, Integer in2){
return in1.compareTo(in2); / 从小到大。
}
};
}
}
jdk1.8的时候出现了一些新的函数式接口:
Supplier 供应商接口:Supplier<T>接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用
T get():获得结果
该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
练习:
定义一个类(SupplierTest),在类中提供两个方法
一个方法是:int getMax(Supplier<Integer> sup) 用于返回一个int数组中的最大值
一个方法是主方法,在主方法中定义一个数组,然后调用getMax方法 接收一个最大值。并打印
代码:
public class SupplierTest {
public static void main(String[] args) {
//定义一个int数组
int[] arr = {19, 50, 28, 37, 46};
int maxValue = getMax(()-> {
//arr = new int[]{2,4,7,1,3}; // 这是一个lambda表达式。 是不是一个匿名内部类的简写。 是不是一个局部的内部类。 局部内部类访问局部变量 局部变量前面有final修饰。 在jdk1.8的时候,final会默认给出 即使你不写 arr前面也默认有final
int max = arr[0];
for(int i=1; i<arr.length; i++) {
if(arr[i] > max) {
max = arr[i];
}
}
return max;
});
System.out.println(maxValue);
}
//返回一个int数组中的最大值
private static int getMax(Supplier<Integer> sup) {
return sup.get();
}
}
Consumer 消费型接口: Consumer<T>接口也被称为消费型接口,它消费的数据的数据类型由泛型指定
void accept(T t):对给定的参数执行此操作
default Consumer<T> andThen(Consumer after):返回一个组合的 Consumer,依次执行此操作,然后执行 after操作
练习:
String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33"};
字符串数组中有多条信息,请按照格式:"姓名:XX,年龄:XX"的格式将信息打印出来
要求:
把打印姓名的动作作为第一个Consumer接口的Lambda实例
把打印年龄的动作作为第二个Consumer接口的Lambda实例
将两个Consumer接口按照顺序组合到一起使用
代码:
public class ConsumerTest {
public static void main(String[] args) {
String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33"};
printInfo(strArray, str -> System.out.print("姓名:" + str.split(",")[0]),
str -> System.out.println(",年龄:" + Integer.parseInt(str.split(",")[1])));
}
private static void printInfo(String[] strArray, Consumer<String> con1, Consumer<String> con2) {
for (String str : strArray) {
con1.andThen(con2).accept(str);
}
}
}
Predicate<T>:常用的四个方法 Predicate<T>接口通常用于判断参数是否满足指定的条件
boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
default Predicate<T> negate():返回一个逻辑的否定,对应逻辑非
default Predicate<T> and(Predicate other):返回一个组合判断,对应短路与
default Predicate<T> or(Predicate other):返回一个组合判断,对应短路或
练习:
String[] strArray = {"林青霞,30", "柳岩,34", "张曼玉,35", "貂蝉,31", "王祖贤,33"};
字符串数组中有多条信息,请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,并遍历ArrayList集合
要求:同时满足如下要求
1:姓名长度大于2
2:年龄大于33
分析:
1:有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断
2:必须同时满足两个条件,所以可以使用and方法连接两个判断条件
代码:()
public class PredicateTest {
public static void main(String[] args) {
String[] strArray = {"林青霞,30", "柳岩,34", "张曼玉,35", "貂蝉,31", "王祖贤,33"};
ArrayList<String> array = myFilter(strArray, s -> s.split(",")[0].length() > 2,
s -> Integer.parseInt(s.split(",")[1]) > 33);
for (String str : array) {
System.out.println(str);
}
}
//通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中
private static ArrayList<String> myFilter(String[] strArray, Predicate<String> pre1, Predicate<String> pre2) {
//定义一个集合
ArrayList<String> array = new ArrayList<String>();
//遍历数组
for (String str : strArray) {
if (pre1.and(pre2).test(str)) {
array.add(str);
}
}
return array;
}
}
Function<T,R>:常用的两个方法 Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值
R apply(T t):将此函数应用于给定的参数
default <V> Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
练习:
String s = "林青霞,30";
请按照我指定的要求进行操作:
1:将字符串截取得到数字年龄部分
2:将上一步的年龄字符串转换成为int类型的数据
3:将上一步的int数据加70,得到一个int结果,在控制台输出
请通过Function接口来实现函数拼接
代码:(andThen方法源码执行流程详见录屏和画图)
public class FunctionTest {
public static void main(String[] args) {
String s = "林青霞,30";
convert(s, ss -> ss.split(",")[1], Integer::parseInt, i -> i + 70);
}
private static void convert(String s, Function<String, String> fun1, Function<String, Integer> fun2, Function<Integer, Integer> fun3) {
// Integer i = fun1.andThen(fun2).andThen(fun3).apply(s);
int i = fun1.andThen(fun2).andThen(fun3).apply(s);
System.out.println(i);
}
}
Stream 流 (☆☆):
和IO流 半毛钱关系都没有。
Stream流 是操作集合的。
之前200行代码能够通过操作集合搞定的功能
现在一行就搞定了。
Stream流的原理是基于:匿名内部类的延迟执行现象(详见录屏)
分为三类方法:
生成流方法(生成流其实就是生成一个匿名内部类对象)
中间操作方法(不断的去拼接其他的匿名内部类对象,生成更大的匿名内部类对象)
终结方法(最大的匿名内部类对象调用方法执行)
Stream流的常见生成方式
1:Collection体系的集合可以使用默认方法stream()生成流
default Stream<E> stream()
2:Map体系的集合间接的生成流
3:数组可以通过Stream接口的静态方法of(T... values)生成流
代码:
public class StreamDemo {
public static void main(String[] args) {
//Collection体系的集合可以使用默认方法stream()生成流
List<String> list = new ArrayList<String>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();
//Map体系的集合间接的生成流
Map<String,Integer> map = new HashMap<String, Integer>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
//数组可以通过Stream接口的静态方法of(T... values)生成流
String[] strArray = {"hello","world","java"};
Stream<String> strArrayStream = Stream.of(strArray);
Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
Stream<Integer> intStream = Stream.of(10, 20, 30);
}
}
Stream流的中间操作方法:
Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤
Predicate接口中的方法 boolean test(T t):对给定的参数进行判断,返回一个布尔值
代码:
public class StreamDemo01 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//需求1:把list集合中以张开头的元素在控制台输出
list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
System.out.println("--------");
//需求2:把list集合中长度为3的元素在控制台输出
list.stream().filter(s -> s.length() == 3).forEach(System.out::println);
System.out.println("--------");
//需求3:把list集合中以张开头的,长度为3的元素在控制台输出
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
}
}
Stream<T> limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据
Stream<T> skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
代码:
public class StreamDemo02 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//需求1:取前3个数据在控制台输出
list.stream().limit(3).forEach(System.out::println);
System.out.println("--------");
//需求2:跳过3个元素,把剩下的元素在控制台输出
list.stream().skip(3).forEach(System.out::println);
System.out.println("--------");
//需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
list.stream().skip(2).limit(2).forEach(System.out::println);
}
}
tatic <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object) )组成的流
代码:
public class StreamDemo03 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//需求1:取前4个数据组成一个流
Stream<String> s1 = list.stream().limit(4);
//需求2:跳过2个数据组成一个流
Stream<String> s2 = list.stream().skip(2);
//需求3:合并需求1和需求2得到的流,并把结果在控制台输出
Stream.concat(s1,s2).forEach(System.out::println);
//需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
Stream.concat(s1,s2).distinct().forEach(System.out::println);
}
}
Stream<T> sorted():返回由此流的元素组成的流,根据自然顺序排序
Stream<T> sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序
Comparator接口中的方法 int compare(T o1, T o2)
代码:
public class StreamDemo04 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("linqingxia");
list.add("zhangmanyu");
list.add("wangzuxian");
list.add("liuyan");
list.add("zhangmin");
list.add("zhangwuji");
//需求1:按照字母顺序把数据在控制台输出
list.stream().sorted().forEach(System.out::println);
//需求2:按照字符串长度把数据在控制台输出
list.stream().sorted((s1, s2) -> s1.length() - s2.length()).forEach(System.out::println);
list.stream().sorted((s1,s2) -> {
int num = s1.length()-s2.length();
int num2 = num==0?s1.compareTo(s2):num;
return num2;
}).forEach(System.out::println);
}
}
<R> Stream<R> map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流
Function接口中的方法 R apply(T t)
IntStream mapToInt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果
IntStream:表示原始 int 流
ToIntFunction接口中的方法 int applyAsInt(T value)
代码:
public class StreamDemo05 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("10");
list.add("20");
list.add("30");
list.add("40");
list.add("50");
//需求:将集合中的字符串数据转换为整数之后在控制台输出
list.stream().map(s -> Integer.parseInt(s)).forEach(System.out::println);
list.stream().map(Integer::parseInt).forEach(System.out::println);
list.stream().mapToInt(Integer::parseInt).forEach(System.out::println);
//int sum?() 返回此流中元素的总和
int result = list.stream().mapToInt(Integer::parseInt).sum();
System.out.println(result);
}
}
Stream流的常见终结操作方法
void forEach(Consumer action):对此流的每个元素执行操作
Consumer接口中的方法 void accept(T t):对给定的参数执行此操作
long count():返回此流中的元素数
代码:
public class StreamDemo {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//需求1:把集合中的元素在控制台输出
list.stream().forEach(System.out::println);
//需求2:统计集合中有几个以张开头的元素,并把统计结果在控制台输出
long count = list.stream().filter(s -> s.startsWith("张")).count();
System.out.println(count);
}
}
Stream流的收集方法
R collect(Collector collector)
它是通过工具类Collectors提供了具体的收集方式
public static <T> Collector toList():把元素收集到List集合中
public static <T> Collector toSet():把元素收集到Set集合中
public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中
代码:
public class CollectDemo {
public static void main(String[] args) {
//创建List集合对象
List<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
//需求1:得到名字为3个字的流
Stream<String> listStream = list.stream().filter(s -> s.length() == 3);
//需求2:把使用Stream流操作完毕的数据收集到List集合中并遍历
List<String> names = listStream.collect(Collectors.toList());
for(String name : names) {
System.out.println(name);
}
//创建Set集合对象
Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(20);
set.add(30);
set.add(33);
set.add(35);
//需求3:得到年龄大于25的流
Stream<Integer> setStream = set.stream().filter(age -> age > 25);
//需求4:把使用Stream流操作完毕的数据收集到Set集合中并遍历
Set<Integer> ages = setStream.collect(Collectors.toSet());
for(Integer age : ages) {
System.out.println(age);
}
//定义一个字符串数组,每一个字符串数据由姓名数据和年龄数据组合而成
String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33", "柳岩,25"};
//需求5:得到字符串中年龄数据大于28的流
Stream<String> arrayStream = Stream.of(strArray).filter(s -> Integer.parseInt(s.split(",")[1]) > 28);
//需求6:把使用Stream流操作完毕的数据收集到Map集合中并遍历,字符串中的姓名作键,年龄作值
Map<String, Integer> map = arrayStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));
Set<String> keySet = map.keySet();
for (String key : keySet) {
Integer value = map.get(key);
System.out.println(key + "," + value);
}
}
}
综合练习:
现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作:
1:男演员只要名字为3个字的前三人
2:女演员只要姓林的,并且不要第一个
3:把过滤后的男演员姓名和女演员姓名合并到一起
5:合并到一起之后,去掉重复的名字,然后返回把这些元素存储到一个新的List集合中。
6:把上一步操作后的集合里面的元素 每个元素 封装到 演员对象中,把所有的演员对象 存储到一个新的集合中。(这一步必须用Stream流)
7:统计这个新集合里面演员 名字 是两个字。 的人员个数。
演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法
练习题答案(分步做):
List<String> man = List.of("张敬轩", "张铁林", "承龙", "黄渤", "林心如", "冯小刚");
List<String> woman = List.of("林心如", "杨紫", "杨幂", "景甜", "林青霞", "林颖");
Stream<String> manStream = man.stream().filter(s -> s.length() == 3).limit(3);
Stream<String> womanStream = woman.stream().filter(s -> s.startsWith("林")).skip(1);
List<String> allPerson = Stream.concat(manStream, womanStream).distinct().collect(Collectors.toList());
//这是第一种做法
//ArrayList<Actor> al = new ArrayList<>();
//for (String name : allPerson) {
// al.add(new Actor(name));
//}
//这是第二种做法
//ArrayList<Actor> al = new ArrayList<>();
//AllPerson.stream().forEach(s-> al.add(new Actor(s)));
//这是第三种做法
List<Actor> collect = allPerson.stream().map(s -> new Actor(s)).collect(Collectors.toList());
long count = collect.stream().filter(a->a.getName().length()==2).count();
System.out.println(count);
练习题答案(合成一步做):
System.out.println(
Stream.concat(
List.of("张敬轩s", "张铁林", "承龙", "黄渤", "林心如", "冯小刚").stream().filter(s -> s.length() == 3).limit(3),
List.of("林心如", "杨紫", "杨幂", "景甜", "林青霞", "林颖").stream().filter(s -> s.startsWith("林")).skip(1)
).distinct().map(s -> new Actor(s)).filter(a->a.getName().length()==2).count()
);
浙公网安备 33010602011771号