赞
踩
Java8是Java语言自JDK1.5以后的一个重大的里程碑版本,因为它增加了很多新特性(比如一些语法糖,简化程序的书写格式),这些新特性会改变编程的风格和解决问题的方式。
例如:日期时间API、Lambda表达式(λ)、Stream API(操作集合、数组)、函数式编程、方法引用、CompletableFuture(异步编程)等。
之前我们写的代码都是面向对象的编程思想,该思想强调的是通过对象来做事情。例如 我们想要线程执行任务就必须创建一个实现Runnable接口的实现类对象,但是我们真正要做的事情实际上就是执行run方法中的代码从而来执行线程的任务。
函数式编程思想省略了创建对象的复杂语法,然后通过 lambda表达式 直接传递代码来执行线程任务。而不需要创建Runnable接口的实现类对象。
函数式编程思想强调的是做什么,而不是以什么方式去做。
下面列举面向对象编程和函数式编程两种编程风格的对比:(函数式编程相比oop语法更加简洁)
package com.baidou;
// 定义游泳接口,并只提供一个抽象方法
public interface Swimming {
// 游泳方法
void swimm();
}
package com.baidou; /** * 面向对象编程和函数式编程两种编程风格的对比 */ public class LambdaTest1 { public static void main(String[] args) { // 1、调用方法传递匿名内部类对象 // 面向对象编程风格 goSwimm(new Swimming() { //多态:编译看左边(父类),运行看右边(子类) @Override public void swimm() { System.out.println("小明在游泳..."); } }); System.out.println("----------------------------"); // 2、使用lambda表达式简化上面的匿名内部类(接口中只提供一个抽象方法) // 调用方法传递lambda表达式 goSwimm(() -> { System.out.println("王五在游泳..."); }); } /** * 定义一个静态方法,使用Swimming接口作为方法参数,然后调用的时候我们只需传递接口实现类对象便可执行具体方法 * * @param swimm 匿名内部类对象、接口实现类对象 */ public static void goSwimm(Swimming swimm) { swimm.swimm(); } }
运行结果:
Lambda的标准语法格式由3个部分组成:一些参数、一个箭头、一段代码{}。
Lambda表达式的标准语法格式如下:
(参数列表)->{代码块}
解释:
示例:使用lambda表达式创建并开启线程
package com.baidou; /** * Lambda表达式标准语法格式 * * @author 白豆五 * @version 2023/04/15 * @since JDK8 */ public class LambdaTest2 { public static void main(String[] args) { // 使用匿名内部类对象的方式创建并开启线程 new Thread(new Runnable() { @Override public void run() { System.out.println("使用匿名内部类对象的方式创建并开启线程A"); } }).start(); //使用lambda表达式创建并开启线程 new Thread(()->{ System.out.println("使用lambda表达式创建并开启线程B"); }).start(); } }
运行结果:
在Lambda标准格式的基础上,使用简化写法的规则如下:
{}
和 ;
以及 return
都可以省略。(注意:要么全省略,要么全保留)示例:lambda的简写—省略参数
package com.baidou; /** * 汽车类 * * @author 白豆五 * @version 2023/04/16 * @since JDK8 */ public class Car { private String name; private String color; private Integer price; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } public Car(String name, String color, Integer price) { this.name = name; this.color = color; this.price = price; } public Car() { } }
package com.baidou; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * Lambda表达式简化格式使用 * * @author 白豆五 * @version 2023/04/15 * @since JDK8 */ public class LambdaTest3 { public static void main(String[] args) { // 创建list集合对象,并使用Collections工具类往list集合添加一些数据 List<Car> list = new ArrayList<>(); Collections.addAll(list, new Car("法拉利拉法", "红色", 100), new Car("保时捷", "蓝色", 86), new Car("宾利", "白色", 50) ); // 集合初始元素顺序为:[Car{name='法拉利拉法', color='红色', price=100}, Car{name='保时捷', color='蓝色', price=86}, Car{name='宾利', color='白色', price=50}] // System.out.println("集合初始元素顺序为:" + list); // 1. 匿名内部方式,按照价格从小到大排序 /* Collections.sort(list, new Comparator<Car>() { @Override public int compare(Car c1, Car c2) { return c1.getPrice() - c2.getPrice(); //升序:c1-c2,降序:c2-c1 } } ); System.out.println("按照价格升序的集合为:"+list); */ // 2.使用lambda标准格式,按照价格从小到大排序 /* Collections.sort(list, (Car c1, Car c2) -> { return c1.getPrice() - c2.getPrice(); }); System.out.println("按照价格升序的集合为:" + list); */ // 3.使用lambda简化格式---省略参数,按照价格降序排序 // Collections.sort(list, (c1, c2) -> { // return c2.getPrice() - c1.getPrice(); // }); // 最简化写法 Collections.sort(list, (c1, c2) -> c2.getPrice() - c1.getPrice()); // 按照价格降序的集合为:[Car{name='法拉利拉法', color='红色', price=100}, Car{name='保时捷', color='蓝色', price=86}, Car{name='宾利', color='白色', price=50}] System.out.println("按照价格降序的集合为:" + list); } }
运行结果:
1、所需类型不同:
2、使用限制不同:
3、实现原理不同:
.class
字节码文件;.class
字节码文件,对应的字节码会在运行的时候动态生成。函数式接口:有且仅有一个抽象方法的接口,称为函数式接口。
Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用Lambda表达式有2个前提条件:
1、必须要有接口(函数式接口),要求该接口中有且仅有一个抽象方法,可以有默认方法、静态方法;
在接口上添加@FunctionalInterface
注解,表示当前接口是函数式接口,可以对函数式接口进行检测;
一旦使用@FunctionalInterface注解定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
2、必须要有方法使用函数式接口作为方法参数;
@verride
注解,检测子类方法是否对父类方法进行覆盖重写;示例:Lambda表达式的前提条件演示
函数式接口:
package com.baidou; /** * 定义一个函数式接口 * * @author 白豆五 * @version 2023/04/16 * @since JDK8 */ @FunctionalInterface // 标记的接口就是函数式接口,可以对函数式接口进行检测 public interface MyFunctionalInterface { // 必须要被覆盖重写的抽象方法 void method(); // 不强制要求实现类必须覆盖重写该方法 // 原因:所有实现类最终都会继承Object类,而Object类已经提供了该方法 boolean equals(Object obj); // 默认方法 // default void defaultMethod(){} // 静态方法 // static void staticMethod(){} }
测试:
package com.baidou; /** * Lambda表达式的前提条件 * * @author 白豆五 * @version 2023/04/16 * @since JDK8 */ public class LambdaTest4 { public static void main(String[] args) { // 1. 调用方法传递匿名内部类对象 fun(new MyFunctionalInterface() { @Override public void method() { System.out.println("A..."); } }); // 2. 调用方法传递lambda表达式标准格式 fun(() -> { //按照抽象方法推导 System.out.println("B..."); }); // 3. 调用方法传递lambda表达式简化格式 fun(() -> System.out.println("C...")); // lambda表达式可以直接给接口变量赋值 (这种写法用的少) MyFunctionalInterface mFun = () -> { System.out.println("D..."); }; mFun.method(); } public static void fun(MyFunctionalInterface mfi) { // 调用抽象方法 mfi.method(); } }
运行结果:
1、变量(使用极少)
package com.baidou.java.jdk.feature.java8;
/**
* 自定义函数式接口
*
* @author baidou 1433021114@qq.com
* @version 2022/6/5 14:56
* @since JDK8
*/
@FunctionalInterface
public interface CustomFunctionalInterface {
// 实现两个整数相加
int add(int a, int b);
}
/**
* lambda表达式的应用场景
*/
@Test
public void testLambdaExpressionUsage() {
// 将lambda表达式赋值给一个接口变量
CustomFunctionalInterface c = (int a, int b) -> {
return a + b;
};
int result = c.add(12, 34);
System.out.println("result = " + result);
}
2、方法的参数
Collections.sort(list, (value1,value2) -> value2 - value1);
3、方法的返回值
/**
* lambda表达式可以作为方法的返回值
*/
public CustomFunctionalInterface getCustomFunctionalInterface(){
return (int a,int b)->{return a+b;};
}
CustomFunctionalInterface c = getCustomFunctionalInterface();
System.out.println("result = " + c.add(11, 22));
除了我们自定的函数式接口外,Java也为我们提供了相关的函数式接口,方便我们后续使用Lambda表达式;
函数式接口:有且仅有一个抽象方法的接口,称为函数式接口。
常用的函数式接口位于java.util.funcation
包下,例如 Supplier接口、Consumer接口、Function接口、Predicate接口。
java.util.function.Supplier<T>
接口,表示供给型接口,生产一个数据的 ,对应的Lambda表达式需要对外提供一个符合泛型类型的对象数据。
get()
,用来获取一个指定的泛型数据。Supplier接口源码如下:
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
T get(); //返回一个泛型对象
}
示例: 匿名内部类的方式,返回拼接后的字符串
package com.baidou.supplier; import java.util.function.Supplier; public class SupplierTest1 { public static void main(String[] args) { String s1 = "hello"; String s2 = "java"; // 1. 匿名内部类的方式,返回拼接后的字符串 fun(new Supplier<String>() { @Override public String get() { return s1 + s2; } }); } /** * 定义方法,使用函数式接口Supplier作为参数 * * @param supplier Supplier接口实现类对象、匿名内部类对象、lambda表达式 */ public static void fun(Supplier<String> supplier) { // 调用抽象方法 // 至于str存的内容是什么以及怎么获取,这里暂时说不清楚,谁调用谁去解决这个问题 String str = supplier.get(); System.out.println(str);//hellojava } }
运行结果:
示例:使用lambda表达式,返回拼接后的大写字符串
package com.baidou.supplier; import java.util.function.Supplier; public class SupplierTest2 { public static void main(String[] args) { String s1 = "hello"; String s2 = "java"; // 2. 使用lambda表达式,返回拼接后的大写字符串 fun(() -> (s1 + s2).toUpperCase()); } /** * 定义方法,使用函数式接口Supplier作为参数 * * @param supplier Supplier接口实现类对象、匿名内部类对象、lambda表达式 */ public static void fun(Supplier<String> supplier) { // 调用抽象方法 // 至于str存的内容是什么以及怎么获取,这里暂时说不清楚,谁调用谁去解决这个问题 String str = supplier.get(); System.out.println(str); } }
运行结果:
java.util.function.Consumer<T>
接口,与Supplier接口正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。(消费接口,只进不出)
accept(T t)
,用于消费(处理)一个指定泛型的数据。package java.util.function; import java.util.Objects; @FunctionalInterface public interface Consumer<T> { // 用于消费(处理)一个指定泛型的数据 // 什么是消费呢?只要给accept方法添加方法体就叫做消费,不管{}里写的是什么,都叫做消费 void accept(T t); //需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费 default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
示例:Consumer接口的accept()方法
package com.baidou.consumer; import java.util.function.Consumer; public class ConsumerTest1 { public static void main(String[] args) { String str = "hello Springboot"; // 1.匿名内部类的方式,按照字符串大写格式输出 fun(str, new Consumer<String>() { @Override public void accept(String s) { System.out.println(s.toUpperCase()); //HELLO SPRINGBOOOT } }); // 2.使用lambda表达式,按照字符串小写格式输出 fun(str, (String s) -> { System.out.println(s.toLowerCase());//hello springboot }); // 3.使用lambda表达式,按照字符串长度消费 fun(str, s -> System.out.println(s.split(" ")[0]));// hello } /** * 定义方法,使用函数式接口Consumer作为参数 * * @param ss 待传入的字符串参数 * @param consumer consumer接口实现类对象、匿名内部类对象、lambda表达式 */ public static void fun(String ss, Consumer<String> consumer) { // 调用抽象方法,处理字符串ss // 如何处理字符串ss,这里说不清楚,谁调用了谁处理 consumer.accept(ss); } }
运行结果:
java.util.function.Function<T,R>
接口,用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有进有出,所以称为函数Function。
R apply(T t)
,表示根据类型T的参数获取类型R的结果。源码如下:
package java.util.function; import java.util.Objects; @FunctionalInterface public interface Function<T, R> { // 根据类型T的参数获取类型R的结果 (数据转换) // 示例:将String类型转换为Integer类型 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; } }
示例:使用Function接口的apply()方法,将String数字转成int类型数字
package com.baidou.function; import java.util.function.Consumer; import java.util.function.Function; /** * 演示Function接口的apply方法 * * @author 白豆五 * @version 2023/04/17 * @since JDK8 */ public class FunctionTest { public static void main(String[] args) { String str = "12300"; // 1.匿名内部类的方式,将String数字转成int类型数字 fun(str, new Function<String, Integer>() { @Override public Integer apply(String s) { return Integer.parseInt(s); } }); // 2.使用lambda表达式,将String数字转成int类型数字后,再扩大10倍 fun(str, (String s) -> { return Integer.parseInt(s) * 10; }); // 3.使用lambda表达式,将String数字转成int类型数字后,再缩小10倍 fun(str, s -> Integer.parseInt(s) / 10); } /** * 定义方法,使用函数式接口Function作为参数 * * @param str 待传入的字符串数字 * @param function */ public static void fun(String str, Function<String, Integer> function) { // 调用抽象方法,将字符串转成Integer类型数据 // 具体功能让子类去实现 System.out.println(function.apply(str)); } }
运行结果:
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T>
接口。
Predicate接口提供了一个抽象方法boolean test(T t)
。用于条件判断的场景,条件判断的标准是传入的Lambda表达式逻辑。
Predicate接口源码如下:
package java.util.function; import java.util.Objects; /** * Represents a predicate (boolean-valued function) of one argument. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #test(Object)}. * * @param <T> the type of the input to the predicate * * @since 1.8 */ @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); } }
示例:使用Predicate接口的test()方法进行条件判断
package com.baidou.predicate; import java.util.function.Predicate; /** * @author 白豆五 * @version 2023/04/17 * @since JDK8 */ public class PredicateTest { public static void main(String[] args) { String str = "helloWorld"; // 1.使用内名内部类方式,判断字符串长度是否大于5 fun(str, new Predicate<String>() { @Override public boolean test(String s) { return s.length() > 5; } }); // 2.使用lambda方式,判断字符串是否包含W fun(str, (s) -> { return s.contains("W"); }); // 3.使用lambda方式,判断字符串是否以ld结尾 fun(str, s -> s.endsWith("ld")); } // 定义方法,使用Predicate接口作为方法参数 public static void fun(String str, Predicate<String> predicate) { // 调用抽象方法 System.out.println(predicate.test(str)); } }
运行结果:
ok,到这里我们已经把四个常用的函数式接口演示完毕了,接下来就是最重要的环节Stream流。
Stream流可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。(流式编程思想)
在Java8中,得益于Lambda所带来的函数式编程,引入了一个全新的概念:Stream流。(可以理解为流水线作业)
作用:简化了操作集合和数组的API,结合lambda表达式。
Stream流的思想和使用步骤:
1、先拿到集合或者数组的Stream流(Stream流相当于一根传送带)
2、把元素放上去。
3、然后就用这个Stream流的API操作元素。
java.util.stream.Stream<T>
是Java 8新加入的最常用的流接口。(它并不是一个函数式接口)
Stream是接口,要想按照Stream流式编程,必须获取接口的实现类对象,然后才能使用流的功能。
1、集合获取Stream流的方式:
集合获取Stream的方式是通过调用stream()方法实现的。
名称 | 说明 |
---|---|
default Stream stream() | 获取当前集合对象的stream流 |
2、数组获取Stream流的方式:
名称 | 说明 |
---|---|
public static Stream stream(T[ ] array) | 获取当前数组的Stream流(Stream接口提供) |
public static Stream of(T… vilues) | 获取当前数组/可变数据的stream流(Arrays类提供) |
示例:
package com.baidou.get_stream; import java.util.*; import java.util.stream.Stream; /** * 分别获取集合和数组的Stream流 * * @author 白豆五 * @version 2023/04/17 * @since JDK8 */ public class GetStreamTest { public static void main(String[] args) { /* 1. 获取单列集合的Stream流对象 */ // 1.1 获取List集合对象对应的stream流 List<String> list = new ArrayList<>(); Stream<String> listStream = list.stream(); System.out.println(listStream); //java.util.stream.ReferencePipeline$Head@14ae5a5 // 1.2 获取set集合对象对应的stream流 Set<String> set = new HashSet<>(); Stream<String> setStream = set.stream(); System.out.println(setStream); //java.util.stream.ReferencePipeline$Head@7f31245a /* 2. 获取双列集合的Stream流对象 map内部没有直接定义stream方法获取stream流,需要先转换为单列集合,然后再获取stream流 - 可以先通过keySet或者entrySet获取一个set集合,然后再获取steam流 */ Map<String, String> map = new HashMap<>(); // 2.1 获取存储键的set集合 Set<String> keySet = map.keySet(); Stream<String> keyStream = keySet.stream();//键流 // 2.2 获取存储值的collection集合,然后再拿到对应的值流 Stream<String> valuestream = map.values().stream(); // 2.3 获取存储键值对对象的set集合 Set<Map.Entry<String, String>> entrySet = map.entrySet(); Stream<Map.Entry<String, String>> entryStream = entrySet.stream(); //键值对流 /* 3. 获取数组的Stream流对象 */ String[] arr = {"太白金猩", "张丝锐", "李斯", "王武"}; // 3.1 通过java.utils.Arrays工具类的stream静态方法,把数组转换称stream流对象 Stream<String> arrstream1 = Arrays.stream(arr); // 3.2 获取数组对应的stream流对象 // Stream接口提供 Stream<String> arrStream2 = Stream.of(arr); Stream<String> arrStream3 = Stream.of("太白金猩", "张丝锐", "李斯", "王武"); } }
Stream流的三类方法:获取stream流、中间方法、终结方法。
void forEach(Consumer action)
:遍历流中的元素,Consumer消费型接口;
long count()
:统计流中的元素个数,返回值long类型;
Optional<T> max(Comparator<? super T> comparator)
: 获取此流运算后的最大值元素。
Optional<T> min(Comparator<? super T> comparator)
: 获取此流运算后的最小值元素。
注意:终结操作方法,调用完成之后流就无法继续使用了,原因是它不会返回Stream流。
示例: Stream流常见的终结方法
package com.baidou.stream_endmethod; import java.util.function.Consumer; import java.util.stream.Stream; /** * 测试Stream流结束方法:forEach()、count() * * @author 白豆五 * @version 2023/04/18 * @since JDK8 */ public class StreamDemo01 { public static void main(String[] args) { // 1.获取stream流 Stream<String> s = Stream.of("大娃", "二娃", "三娃", "四娃", "五娃", "六娃", "七娃"); // 2.遍历stream流 // 2.1匿名内部类方式 /* s.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } });*/ // 2.2lambda表达式方式,(如果不会写lambda,就先把匿名内部类写出来,然后通过覆盖重写的抽象方法推导出lambda) s.forEach(str-> System.out.println(str)); // 报错:java.lang.IllegalStateException: stream has already been operated upon or closed(流已经被操作或关闭) // 调用终结方法后流就不可以继续使用了,原因是它不会返回Stream流。 System.out.println(s.count()); } }
运行结果:
小结: 终结方法和非终结方法的含义?
Stream流常见的中间操作方法有:
Stream<T> filter(Predicate predicate)
:过滤元素(过滤条件);
Stream<T> limit(long maxSize)
:获取前几个元素;Stream<T> skip(long n)
:跳过前几个元素;static <T> Stream<T> concat(Stream a, Stream b)
:合并流;Stream<T> distinct()
:去除流中重复的元素。(依赖hashCode和equals方法);<R> Stream<R> map(Function<T, R> mapper)
:加工方法。(转换操作)Stream<T> sorted(Comparator comparator)
,指定排序规则,并返回新的stream流。注意事项:
中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
在Stream流中无法直接修改集合、数组中的数据。
示例:filter方法使用,主要还是依赖test方法的条件判断
package com.baidou.stream_centermethd; import java.util.function.Predicate; import java.util.stream.Stream; /** * 测试中间方法 filter(过滤操作) * * @author 白豆五 * @version 2023/04/18 * @since JDK8 */ public class StreamDemo2 { public static void main(String[] args) { // 获取stream流 Stream<String> s = Stream.of("大娃", "二娃", "三娃", "四娃", "五娃", "六娃", "七娃", "白豆五"); // 匿名内部类方式,过滤所有带娃的元素(中间方法) // 返回一个新的stream流--s2,存放带娃的元素 Stream<String> s2 = s.filter(new Predicate<String>() { @Override public boolean test(String name) { return name.contains("娃"); } }); // 遍历输出(终结方法) s2.forEach(name -> System.out.println(name)); System.out.println("------"); // lambda表达式方式,过滤所有不带娃的元素(中间方法) s = Stream.of("大娃", "二娃", "三娃", "四娃", "五娃", "六娃", "七娃", "白豆五"); Stream<String> s3 = s.filter(name -> !name.contains("娃")); s3.forEach(name -> System.out.println(name)); } }
运行结果:
示例2:limit方法使用,获取前7个元素
package com.baidou.stream_centermethd; import java.util.function.Predicate; import java.util.stream.Stream; /** * 测试中间方法 limit(获取前n个数据) * * @author 白豆五 * @version 2023/04/18 * @since JDK8 */ public class StreamDemo3 { public static void main(String[] args) { // 获取stream流 Stream<String> s = Stream.of("大娃", "二娃", "三娃", "四娃", "五娃", "六娃", "七娃", "白豆五"); // 获取前7个数据 Stream<String> s1 = s.limit(7); // 遍历输出 s1.forEach(str-> System.out.println(str)); } }
运行结果:
示例3:skip方法使用,跳过前3个元素
package com.baidou.stream_centermethd; import java.util.stream.Stream; /** * 测试中间方法 skip(跳过前n个数据) * * @author 白豆五 * @version 2023/04/18 * @since JDK8 */ public class StreamDemo4 { public static void main(String[] args) { // 获取stream流 Stream<String> s = Stream.of("大娃", "二娃", "三娃", "四娃", "五娃", "六娃", "七娃", "白豆五"); // 跳过前3个数据 Stream<String> s1 = s.skip(3); // 遍历输出 s1.forEach(str-> System.out.println(str)); } }
运行结果:
示例4:concat、distinct方法使用
package com.baidou.stream_centermethd; import java.util.stream.Stream; /** * 测试中间方法 concat(合并流)、distinct(去重) * * @author 白豆五 * @version 2023/04/18 * @since JDK8 */ public class StreamDemo5 { public static void main(String[] args) { // 获取stream流 Stream<String> s1 = Stream.of("唐僧", "孙悟空", "沙和尚", "猪八戒", "白龙马"); Stream<String> s2 = Stream.of("白骨精", "金角大王", "银角大王", "黑熊精", "白骨精"); // 去除流中重复元素 Stream<String> s3 = s2.distinct(); // 合并流并遍历 Stream.concat(s1, s3).forEach(str -> System.out.println(str)); } }
运行结果:
示例:加工方法map
package com.baidou.stream_centermethd; import java.util.function.Function; import java.util.stream.Stream; /** * 测试中间方法 map(加工方法) * * @author 白豆五 * @version 2023/04/18 * @since JDK8 */ public class StreamDemo6 { public static void main(String[] args) { // 获取stream流 Stream<String> s1 = Stream.of("java", "springboot", "springcloud", "mybatis", "php"); /* map加工方法 map(new Function<原材料类型, 加工后的结果类型>()) */ // 为流中每一个元素前面添加hello // 匿名内部类方式 // s1.map(new Function<String, Object>() { // @Override // public Object apply(String s) { // // return "hello " + s; // } // }).forEach(System.out::println); // lambda表达式方式 s1.map(s -> "hello " + s).forEach(System.out::println); } }
运行结果:
示例:使用sorted方法对stream数据排序
package com.baidou.stream_collect; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; /** * 使用sorted方法对stream流排序 * * @author 白豆五 * @version 2023/04/18 * @since JDK8 */ public class StreamDemo8 { public static void main(String[] args) { List<Student> list = new ArrayList<>(); Collections.addAll(list, new Student("张三", 18), new Student("李四", 38), new Student("王武", 28)); // 获取list集合对应的stream流对象 Stream<Student> s1 = list.stream(); // 使用sorted方法对流进行升序排列 s1.sorted(((o1, o2) -> o1.getAge() - o2.getAge())).forEach(System.out::println); } } class Student { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Student(String name, Integer age) { this.name = name; this.age = age; } public Student() { } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
运行结果:
练习1:先筛选所有姓张的人,然后再次对名字有三个字的人进行筛选,最后对结果进行打印输出。
package com.baidou.test; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; /** * 先筛选所有姓张的人,然后再次对名字有三个字的人进行筛选,最后对结果进行打印输出。(过滤方法) * * @author 白豆五 * @version 2023/04/18 * @since JDK8 */ public class StreamTest1 { public static void main(String[] args) { List<String> list = new ArrayList<String>() {{ add("张三"); add("张思锐"); add("李斯"); add("王武"); add("赵赛克斯"); }}; // 1.获取list集合对应的Stream流 Stream<String> s = list.stream(); // 2.筛选所有姓张的人,然后再对名字有三个字的人进行筛选,最后对结果进行打印输出 // Stream<String> aStream = s.filter(name -> name.startsWith("张")); // Stream<String> bStream = aStream.filter(name -> name.length() == 3); // bStream.forEach(name -> System.out.println(name)); // 简化写法 // s.filter(name -> name.startsWith("张") && name.length() == 3).forEach(name -> System.out.println(name)); s.filter(name -> name.startsWith("张") && name.length() == 3).forEach(System.out::println); //输出用到了方法引用 } }
运行结果:
练习2:现在有两个
ArrayList
集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
- 第一个队伍只要名字为3个字的成员姓名;
- 第一个队伍筛选之后只要前3个人;
- 第二个队伍只要姓张的成员姓名;
- 第二个队伍筛选之后不要前2个人;
- 将两个队伍合并为一个队伍;
package com.baidou.test; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; /* 练习2:现在有两个`ArrayList`集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤: 1. 第一个队伍只要名字为3个字的成员姓名; 2. 第一个队伍筛选之后只要前3个人; 3. 第二个队伍只要姓张的成员姓名; 4. 第二个队伍筛选之后不要前2个人; 5. 将两个队伍合并为一个队伍; */ public class StreamTest2 { public static void main(String[] args) { List<String> one = new ArrayList<>(); one.add("迪丽凉巴"); one.add("宋近桥"); one.add("苏星河"); one.add("土豆"); one.add("茄子"); one.add("芒果"); one.add("洪七公"); List<String> two = new ArrayList<>(); two.add("古力大山"); two.add("张无忌"); two.add("张三丰"); two.add("张二狗"); two.add("张天天"); two.add("张三"); // 1. 第一个队伍只要名字为3个字的成员姓名 (过滤filter) // 2. 第一个队伍筛选之后只要前3个人 (限定limit) // one.stream().filter(name->name.length()==3).limit(3).forEach(System.out::println); Stream<String> s1 = one.stream().filter(name -> name.length() == 3).limit(3); // 3. 第二个队伍只要姓张的成员姓名 // 4. 第二个队伍筛选之后不要前2个人 // two.stream().filter(name -> name.startsWith("张")).skip(2).forEach(System.out::println); Stream<String> s2 = two.stream().filter(name -> name.startsWith("张")).skip(2); // 5. 将两个队伍合并为一个队伍 Stream.concat(s1,s2).forEach(System.out::println); } }
运行结果:
即使我们使用流的方式操作数据,但是数据始终保存在流中,并没有持久化到集合或数组中,这时需要我们把操作的结果数据恢复到集合或者数组中去。
Stream流的收集方法:
R collect(Collector collector)
,收集Stream流,需要指定收集器。Collectors工具类提供了具体的收集方式:
public static <T> Collector toList()
,把元素收集到List集合中;public static <T> Collector toSet()
,把元素收集到Set集合中;public static Collector<> toMap(Function keyMapper,Function valueMapper)
,把元素收集到Map集合中。示例:收集stream流
package com.baidou.stream_collect; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; /** * 使用collect方法收集stream流 * * @author 白豆五 * @version 2023/04/18 * @since JDK8 */ public class StreamDemo7 { public static void main(String[] args) { List<Person> list = new ArrayList<>(); Collections.addAll(list, new Person("张三", 18), new Person("李四", 28), new Person("王武", 38)); // 获取list集合对应的stream流对象 Stream<Person> s1 = list.stream(); // 利用map方法,对每个person对象的年龄增加两岁后,存储到新的Stream流对象中 // Stream<Person> s2 = s1.map(person -> { // person.setAge(person.getAge() + 2); // return person; // }); // 利用collect方法把stream流对象,转换为List集合 // list = s2.collect(Collectors.toList()); // 简化写法 list = s1.map(person -> { person.setAge(person.getAge() + 2); return person; }).collect(Collectors.toList()); // 遍历集合 list.forEach(System.out::println); } } class Person { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Person(String name, Integer age) { this.name = name; this.age = age; } public Person() { } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
运行结果:
完结撒花
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。