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() {
@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 [email protected]
* @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
接口,表示供给型接口,生产一个数据的 ,对应的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
接口,与Supplier接口正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
accept(T t)
,用于消费(处理)一个指定泛型的数据。Consumer接口源码如下:
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
接口,用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有进有出,所以称为函数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
接口。
Predicate接口提供了一个抽象方法boolean test(T t)
。用于条件判断的场景,条件判断的标准是传入的Lambda表达式逻辑。
Predicate接口源码如下:
package java.util.function;
import java.util.Objects;
/**
* Represents a predicate (boolean-valued function) of one argument.
*
* This is a functional interface
* whose functional method is {@link #test(Object)}.
*
* @param 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
是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类型;注意:终结操作方法,调用完成之后流就无法继续使用了,原因是它不会返回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() {
@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 filter(Predicate predicate)
:过滤元素(过滤条件);
Stream limit(long maxSize)
:获取前几个元素;Stream skip(long n)
:跳过前几个元素;static Stream concat(Stream a, Stream b)
:合并流;Stream distinct()
:去除流中重复的元素。(依赖hashCode和equals方法); Stream map(Function mapper)
:加工方法。Stream 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() {
// @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 aStream = s.filter(name -> name.startsWith("张"));
// Stream 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 Collector toList()
,把元素收集到List集合中;public static 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 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 +
'}';
}
}
运行结果:
完结撒花