lambda表达式介绍
Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。通过Lambda表达式,可以替代我们以前经常写的匿名内部类来实现接口。Lambda表达式本质是一个匿名函数
体验Lambda表达式
lambda表达式用得最多的场合就是替代匿名内部类,而实现Runnable接口是匿名内部类的经典例子
// Java7
new Thread(new Runnable()
{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}).start();
// Java8
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}).start();
使用lambda表达式对集合进行迭代
public class TestCollection
{
public static void main(String[] args) {
List<String> languages = Arrays.asList("java", "scala", "python");
//before java8
for (String each : languages) {
System.out.println(each);
}
//after java8
languages.forEach(x -> System.out.println(x));
languages.forEach(System.out::println);
}
}
自定义接口
public interface ICalculate
{
int calculate(int x, int y);
}
public class TestCalculate
{
public static void main(String[] args) {
//jdk7
ICalculate iCalculate = new ICalculate()
{
@Override
public int calculate(int x, int y) {
return x + y;
}
};
System.out.println(iCalculate.calculate(1, 3));
//jdk8
iCalculate = (x, y) -> x - y;
System.out.println(iCalculate.calculate(5, 6));
}
}
Lambda表达式语法
一般的函数类有返回值,方法名,参数列表,方法体
Lambda表达式函数的话,只有参数列表,和方法体;(函数类型不需要申明,可以由接口的方法签名自动推导出来)
( 参数列表 ) -> { 方法体 }
说明:
( ) :用来描述参数列表;
{ } : 用来描述方法体;
-> :Lambda运算符,可以叫做箭头符号,或者goes to
Lambda表达式精简语法
精简语法注意点:
- 参数类型可以省略
- 假如只有一个参数,()括号可以省略
- 如果方法体只有一条语句,{}大括号可以省略
- 如果方法体中唯一的语句是return返回语句,那省略大括号的同事return也要省略
方法引用
有时候多个lambda表达式实现函数是一样的话,我们可以封装成通用方法,以便于维护;
这时候可以用方法引用实现:
语法是:对象::方法
假如是static方法,可以直接 类名::方法
public class TestMethod
{
public static void main(String[] args) {
TestMethod testMethod = new TestMethod();
ICalculate iCalculate = testMethod::calculate;
ICalculate iCalculate1 = testMethod::calculate1;
System.out.println(iCalculate.calculate(5, 7));
System.out.println(iCalculate1.calculate(5, 7));
}
public int calculate(int x, int y) {
return x + y;
}
public int calculate1(int x, int y) {
return x - y;
}
}
构造方法引用
如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现,
那么就可以使用构造方法引用;
语法:类名::new
public Dog() {
System.out.println("无参构造方法");
}
public Dog(String name, int age) {
System.out.println("有参构造方法");
this.name = name;
this.age = age;
}
public class TestDog
{
public static void main(String[] args) {
// 普通方式
DogService dogService = () -> {
return new Dog();
};
dogService.getDog();
// 简化方式
DogService dogService2 = () -> new Dog();
dogService2.getDog();
// 构造方法引用
DogService dogService3 = Dog::new;
dogService3.getDog();
// 构造方法引用 有参
DogService2 dogService21 = Dog::new;
dogService21.getDog("小米", 11);
}
}
interface DogService
{
Dog getDog();
}
interface DogService2
{
Dog getDog(String name, int age);
}
@FunctionalInterface注解
这个注解是函数式接口注解,所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。
这种类型的接口也称为SAM接口,即Single Abstract Method interfaces
- 接口有且仅有一个抽象方法
- 允许定义静态方法
- 允许定义默认方法
- 允许java.lang.Object中的public方法
该注解不是必须的,如果一个接口符合”函数式接口”定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错
// 正确的函数式接口
@FunctionalInterface
public interface TestInterface {
// 抽象方法
public void sub();
// java.lang.Object中的public方法
public boolean equals(Object var1);
// 默认方法
public default void defaultMethod(){
}
// 静态方法
public static void staticMethod(){
}
}
// 错误的函数式接口(有多个抽象方法)
@FunctionalInterface
public interface TestInterface2 {
void add();
void sub();
}
Java8内置接口
Consumer(消费型)
接受一个输入参数并且无返回的操作;因为没有出参,常⽤于打印、发送短信等消费动作
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
/**
* 执行完再走一个消费型函数accept方法
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
其他Consumer扩展接口:
- BiConsumer:void accept(T t, U u);接受两个参数
- DoubleConsumer:void accept(double value);接受一个double参数
- IntConsumer:void accept(int value);接受一个int参数
- LongConsumer:void accept(long value);接受一个long参数
- ObjDoubleConsumer:void accept(T t, double value);接受一个泛型参数一个double参数
- ObjIntConsumer:void accept(T t, int value);接受一个泛型参数一个int参数
- ObjLongConsumer:void accept(T t, long value);接受一个泛型参数一个long参数
测试demo
public class TestConsumer
{
public static void main(String[] args) {
// Consumer 接收一个参数,不返回
Consumer<String> c = System.out::println;
c.accept("函数式接口:Consumer");
//做完本次后再继续执行一个
Consumer<String> count = x -> System.out.println((Integer.parseInt(x) + 1) + "");
count.andThen(c).accept("5");
//定义一个方法使用consumer作为参数
sendmessage("iphone13", x -> System.out.println(x + "发送短信"));
}
public static void sendmessage(String phone, Consumer<String> consumer) {
consumer.accept(phone);
}
}
Supplier(供给型)
无入参 有返回值 泛型⼀定和⽅法的返回值类型是⼀种类型,如果需要获得⼀个数据,并且不需要传⼊参数,可以使⽤Supplier接⼝,例如 ⽆参的⼯⼚⽅法,即⼯⼚设计模式创建对象,简单来说就是 提供者
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
其他Supplier扩展接口:
- BooleanSupplier:boolean getAsBoolean();返回boolean
- DoubleSupplier:double getAsDouble();返回double
- IntSupplier:int getAsInt();返回int
- LongSupplier:long getAsLong();返回long
测试demo
public class TestSupplier
{
public static void main(String[] args) {
//自定义
Supplier<Integer> supplier = () -> new Random().nextInt();
System.out.println(supplier.get());
System.out.println("********************");
//方法引用
Supplier<Double> supplier2 = Math::random;
System.out.println(supplier2.get());
//类似工厂创建一个对象
Supplier<String> supplier3 = () -> "5";
System.out.println(supplier3.get());
}
}
Predicate(断言型)
断⾔型接⼝:有⼊参,有返回值,返回值类型确定是boolean,接收⼀个参数,⽤于判断是否满⾜⼀定的条件,过滤数据
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.(断言)
*/
boolean test(T t);
/**
* 当前断言 与other断言进行 && 判断
*/
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);
}
/**
* targetRef为空的情况 会降级成校验T是否为空,不为空的情况下判断是否相等
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
其他Predicate扩展接口:
- BiPredicate:boolean test(T t, U u);接受两个参数的,判断返回bool
- DoublePredicate:boolean test(double value);入参为double的谓词函数
- IntPredicate:boolean test(int value);入参为int的谓词函数
- LongPredicate:boolean test(long value);入参为long的谓词函数
测试demo
public class TestPredicate
{
public static void main(String[] args) {
Predicate<Integer> predicate = (t) -> t > 5;
System.out.println(predicate.test(1));
//判断是否为空Objects 1.8新工具用来判空,判相等 等操作
Predicate<String> p = Objects::isNull;
System.out.println(p.test(""));
TestPredicate testPredicate = new TestPredicate();
//1.判断传入的字符串的长度是否大于5
System.out.println(testPredicate.judgeConditionByFunction(12345, value -> String.valueOf(value).length() > 5));
//2.判断传入的参数是否是奇数
System.out.println(testPredicate.judgeConditionByFunction(4, value -> value % 2 == 0));
//3.判断数字是否大于10
System.out.println(testPredicate.judgeConditionByFunction(-1, value -> value > 10));
//4.且断言
System.out.println(testPredicate.testAndMethod("zhangsan", stringOne -> stringOne.equals("zhangsan"),
stringTwo -> stringTwo.length() > 5));
//5.测试isEquals
System.out.println(testPredicate.testMethodIsEquals("zhangsan", "zhangsan"));
System.out.println("~~~ ~~~ ~~~ ~~~");
System.out.println(testPredicate.testMethodIsEquals("zhangsan", "lisi"));
System.out.println("~~~ ~~~ ~~~ ~~~");
System.out.println(testPredicate.testMethodIsEquals(null, "zhangsan"));
}
public boolean judgeConditionByFunction(int value, Predicate<Integer> predicate) {
return predicate.test(value);
}
/**
*
* @param stringOne 待判断的字符串
* @param predicateOne 断定表达式1
* @param predicateTwo 断定表达式2
* @return 是否满足两个条件
*/
public boolean testAndMethod(String stringOne, Predicate<String> predicateOne, Predicate<String> predicateTwo) {
return predicateOne.and(predicateTwo).test(stringOne);
}
public boolean testMethodIsEquals(String strValue, String strValue2) {
return Predicate.isEqual(strValue).test(strValue2);
}
}
Function(功能型)
Function 接口是一个功能型接口,它的一个作用就是转换作用,将输入数据转换成另一种形式的输出数据。
/**
* 代表这一个方法,能够接受参数,并且返回一个结果
* @since 1.8
*/
@FunctionalInterface
public interface Function<T, R> {
/**
* 将参数赋予给相应方法
*
* @param t
* @return
*/
R apply(T t);
/**
* 先执行参数(即也是一个Function)的,再执行调用者(同样是一个Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* 先执行调用者,再执行参数,和compose相反。
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* 返回当前正在执行的方法(输出自己,等于什么都不做,主要封装t -> t这种写法,部分场景并不能直接使用identity)
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
Function相关扩展接口:
- BiFunction :R apply(T t, U u);接受两个参数,返回一个值,代表一个二元函数;
- DoubleFunction :R apply(double value);只处理double类型的一元函数;
- ToDoubleFunction:double applyAsDouble(T value);返回double的一元函数;
- ToDoubleBiFunction:double applyAsDouble(T t, U u);返回double的二元函数;
- IntToLongFunction:long applyAsLong(int value);接受int返回long的一元函数;
测试demo
public class TestFunction
{
public static void main(String[] args) {
Function<Integer, Integer> times2 = i -> i * 2;
Function<Integer, Integer> squared = i -> i * i;
System.out.println(times2.apply(4));
System.out.println(squared.apply(4));
//32 先4×4然后16×2,先执行apply(4),在times2的apply(16),先执行参数,再执行调用者。
System.out.println(times2.compose(squared).apply(4));
//64 先4×2,然后8×8,先执行times2的函数,在执行squared的函数。
System.out.println(times2.andThen(squared).apply(4));
//16
System.out.println(Function.identity().compose(squared).apply(4));
}
}
UnaryOperator(一元算子)
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
/**
* 同function
*/
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
BinaryOperator(二元算子)
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
/**
* 传入比较器,主要不要传反
*/
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
/**
* 传入比较器,主要不要传反
*/
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
其他的Operator接口:
- LongUnaryOperator:long applyAsLong(long operand);
- LongBinaryOperator:long applyAsLong(long left, long right);
- …
测试demo
public class TestOperator
{
public static void main(String[] args) {
UnaryOperator<Integer> unaryOperator = x -> x + 10;
BinaryOperator<Integer> binaryOperator = (x, y) -> x + y;
//20
System.out.println(unaryOperator.apply(10));
//15
System.out.println(binaryOperator.apply(5, 10));
//继续看看BinaryOperator提供的两个静态方法 也挺好用的
BinaryOperator<Integer> min = BinaryOperator.minBy(Integer::compare);
BinaryOperator<Integer> max = BinaryOperator.maxBy(Integer::compareTo);
//10
System.out.println(min.apply(10, 20));
//20
System.out.println(max.apply(10, 20));
}
}
Stream
Java8中的Stream是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的 聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时,它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程
stream流的构成
当我们使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换 → 执行操作获取想要的结果。每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道
Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等
Stream可以由数组或集合创建,对流的操作分为两种:
- 中间操作,每次返回一个新的流,可以有多个。
- 终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。
另外,Stream有几个特性:
- stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
- stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
- stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
stream流的创建
直接看代码
public class TestCreate
{
public static void main(String[] args) {
String[] dd = {"a", "b", "c" };
//1.使用java.util.Arrays.stream(T[] array)方法用数组创建流
Arrays.stream(dd).forEach(System.out::print);
System.out.println();
//2.通过Stream的of静态方法,传入一个泛型数组,或者多个参数,创建一个流
Stream.of(dd).forEach(System.out::print);
System.out.println();
//3.通过 java.util.Collection.stream() 方法用集合创建流
// 使用这个方法,包括继承Collection的接口,如:Set,List,Map,SortedSet 等等,详细的,可以看Collection接口上的定义注释
Arrays.asList(dd).stream().forEach(System.out::print);
// 创建一个并行流
Arrays.asList(dd).parallelStream().forEach(System.out::print);
System.out.println();
//4.使用Stream的静态方法:iterate()、generate(),生成的是无限流,需要配合limit使用
Stream.iterate(0, x -> x + 1).limit(10).forEach(System.out::print);
System.out.println();
Stream.generate(() -> "x").limit(10).forEach(System.out::print);
System.out.println();
//5.从BufferedReader获得
BufferedReader bufferedReader = new BufferedReader(new StringReader("123456"));
System.out.println(bufferedReader.lines().count());
//6.静态工厂 IntStream.range, Files.walk
IntStream.range(6, 10).forEach(System.out::print);
System.out.println();
try (Stream<Path> walk = Files.walk(Paths.get("E:\\vscode"))) {
List<String> result = walk.filter(Files::isRegularFile).map(Path::toString).collect(Collectors.toList());
result.forEach(System.out::println);
}
catch (IOException e) {
e.printStackTrace();
}
System.out.println();
//7.Spliterator,接口,示例比较复杂,可专门研究
//8.其他random.ints()、BitSet.stream()、Pattern.splitAsStream、JarFile.stream()
new Random().ints(0, 100).limit(10).forEach(System.out::println);
}
}
stream流的操作类型
- Intermediate:一个流可以后面跟随零个或多个intermediate操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
- Terminal:一个流只能有一个terminal操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以,这必定是流的最后一个操作。Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个side effect
转换操作都是lazy的,多个转换操作只会在Terminal操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在Terminal 操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。此操作被称为short-circuiting
stream流的使用
- Intermediate 操作map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
- Terminal 操作forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
- Short-circuiting 操作anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
遍历/匹配(foreach/find/match)
Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。Stream的遍历、匹配非常简单。
//以下所有PersonList均复用此
public static List<Person> personList;
static {
personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 19, "male", "New York"));
personList.add(new Person("Jack", 7000, 23, "male", "Washington"));
personList.add(new Person("Lily", 7800, 45, "female", "Washington"));
personList.add(new Person("Anni", 8200, 32, "female", "New York"));
personList.add(new Person("Owen", 9500, 11, "male", "New York"));
personList.add(new Person("Alisa", 7900, 8, "female", "New York"));
}
public static void main(String[] args) {
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
// 遍历输出符合条件的元素
list.stream().filter(x -> x > 6).forEach(System.out::println);
// 匹配第一个
Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst();
// 匹配任意(适用于并行流)
Optional<Integer> findAny = list.parallelStream().filter(x -> x > 6).findAny();
// 是否包含符合特定条件的元素
boolean anyMatch = list.stream().anyMatch(x -> x > 6);
System.out.println("匹配第一个值:" + findFirst.get());
System.out.println("匹配任意一个值:" + findAny.get());
System.out.println("是否存在大于6的值:" + anyMatch);
}
match方法
Stream有三个match方法,从语义上说:
(1).allMatch:Stream 中全部元素符合传入的 predicate,返回 true;
(2).anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true;
(3).noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true.
它们都不是要遍历全部元素才能返回结果。例如allMatch只要一个元素不满足条件,就skip剩下的所有元素,返回false。对清单13中的Person类稍做修改,加入一个age属性和getAge方法
// 使用 Match
List<Person> persons = new ArrayList();
persons.add(new Person(1, "name" + 1, 10));
persons.add(new Person(2, "name" + 2, 21));
persons.add(new Person(3, "name" + 3, 34));
persons.add(new Person(4, "name" + 4, 6));
persons.add(new Person(5, "name" + 5, 55));
boolean isAllAdult = persons.stream().allMatch(p -> p.getAge() > 18);
System.out.println("All are adult? " + isAllAdult);
boolean isThereAnyChild = persons.stream().anyMatch(p -> p.getAge() < 12);
System.out.println("Any child? " + isThereAnyChild);
输出结果:
All are adult? false
Any child? true
筛选(filter)
筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作
//筛选出Integer集合中大于7的元素,并打印出来
list.stream().filter(x -> x > 7).forEach(System.out::println);
//筛选员工中工资高于8000的人,并形成新的集合
List<String> fiterList = personList.stream().filter(x -> x.getSalary() > 8000).map(Person::getName)
.collect(Collectors.toList());
System.out.print("高于8000的员工姓名:" + fiterList);
聚合(max/min/count)
//获取String集合中最长的元素
List<String> strList = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");
Optional<String> strMax = strList.stream().max(Comparator.comparing(String::length));
System.out.println("最长的字符串:" + strMax.get());
//获取Integer集合中的最大值
// 自然排序
Optional<Integer> max = list.stream().max(Integer::compareTo);
// 自定义排序
Optional<Integer> max2 = list.stream().max(new Comparator<Integer>()
{
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
System.out.println("自然排序的最大值:" + max.get());
System.out.println("自定义排序的最大值:" + max2.get());
//获取员工工资最高的人
Optional<Person> maxSalary = personList.stream().max(Comparator.comparingInt(Person::getSalary));
System.out.println("员工工资最大值:" + maxSalary.get().getSalary());
//计算Integer集合中大于6的元素的个数
long count = list.stream().filter(x -> x > 6).count();
System.out.println("list中大于6的元素个数:" + count);
映射(map/flatMap)
映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:
- map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
- flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
//将员工的薪资全部增加10000
// 不改变原来员工集合的方式(map内部新对象进行处理,旧的不受影响了)
List<Person> personListNew = personList.stream().map(person -> {
Person personNew = new Person(person.getName(), 0, 0, null, null);
personNew.setSalary(person.getSalary() + 10000);
return personNew;
}).collect(Collectors.toList());
System.out.println("一次改动前:" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
System.out.println("一次改动后:" + personListNew.get(0).getName() + "-->" + personListNew.get(0).getSalary());
// 改变原来员工集合的方式
List<Person> personListNew2 = personList.stream().map(person -> {
person.setSalary(person.getSalary() + 10000);
return person;
}).collect(Collectors.toList());
System.out.println("二次改动前:" + personList.get(0).getName() + "-->" + personListNew.get(0).getSalary());
System.out.println("二次改动后:" + personListNew2.get(0).getName() + "-->" + personListNew.get(0).getSalary());
将两个字符数组合并成一个新的字符数组
public class StreamTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
List<String> listNew = list.stream().flatMap(s -> {
// 将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
}).collect(Collectors.toList());
System.out.println("处理前的集合:" + list);
System.out.println("处理后的集合:" + listNew);
}
}
flatMap()的作用就是将多个集合(List)合并成一个大集合处理
String[] strs = { "aaa", "bbb", "ccc" };
Arrays.stream(strs).map(str -> str.split("")).forEach(System.out::println);// Ljava.lang.String;@53d8d10a
Arrays.stream(strs).map(str -> str.split("")).flatMap(Arrays::stream).forEach(System.out::println);// aaabbbccc
Arrays.stream(strs).map(str -> str.split("")).flatMap(str -> Arrays.stream(str)).forEach(System.out::println);// aaabbbccc
peek(debug用途)
peek和map的区别
Stream<T> peek(Consumer<? super T> action)
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
peek接收一个Consumer,而map接收一个Function。
Consumer是没有返回值的,它只是对Stream中的元素进行某些操作,但是操作之后的数据并不返回到Stream中,所以Stream中的元素还是原来的元素。
而Function是有返回值的,这意味着对于Stream的元素的所有操作都会作为新的结果返回到Stream中。
这就是为什么peek String不会发生变化而peek Object会发送变化的原因
// 改变原来员工集合的方式(改写为Peek方式)
List<Person> personListNew2 = personList.stream().peek(person -> person.setSalary(person.getSalary() + 10000)).collect(Collectors.toList());
System.out.println("二次改动前:" + personList.get(0).getName() + "-->" + personListNew.get(0).getSalary());
System.out.println("二次改动后:" + personListNew2.get(0).getName() + "-->" + personListNew.get(0).getSalary());
归约(reduce)
归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作
结合草图,要实现stream.reduce()方法,必须要告诉JDK
- 你有什么需求数据要汇聚?(Stream已经提供了数据源,对应上面草图的A元素)
- 最后要汇聚成怎样的一个数据类型(对应reduce方法的参数一,对应上面草图的B元素)
- 如何将需求数据处理或转化成一个汇聚数据(对应reduce方法的参数二,对应上面草图的汇聚方式1)
- 如何将多个汇聚数据进行合并(对应reduce方法的参数三,对应上面草图的汇聚方式2)
再结合你给的map方法,其实是要把I类数据的流,最后转化为一个O类数据的List,因此按照上面的步骤可以进行对照
- 你有什么需求数据要汇聚?(I类数据流)
- 最后要汇聚成怎样的一个数据类型(一个集合,new ArrayList())
- 如何将需求数据处理或转化成一个汇聚数据(根据mapper把I转化为O,再用List.add方法)
- 如何将多个汇聚数据进行合并(两个集合合并,用List.addAll())
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
// 求和方式1
Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
// 求和方式2
Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
// 求和方式3
Integer sum3 = list.stream().reduce(0, Integer::sum);
// 求乘积
Optional<Integer> product = list.stream().reduce((x, y) -> x * y);
// 求最大值方式1
Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
// 求最大值写法2
Integer max2 = list.stream().reduce(1, Integer::max);
System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3);
System.out.println("list求积:" + product.get());
System.out.println("list求和:" + max.get() + "," + max2);
// 求工资之和方式1:
Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum);
// 求工资之和方式2:
Integer sumSalary2 = personList.stream().reduce(0, (sumSal, p) -> sumSal += p.getSalary(),
(sumSal1, sumSal2) -> sumSal1 + sumSal2);
// 求工资之和方式3:
Integer sumSalary3 = personList.stream().reduce(0, (sumSal, p) -> sumSal += p.getSalary(), Integer::sum);
// 求最高工资方式1:
Integer maxSalary = personList.stream().reduce(0,
(maxSal, p) -> maxSal > p.getSalary() ? maxSal : p.getSalary(), Integer::max);
// 求最高工资方式2:
Integer maxSalary2 = personList.stream().reduce(0,
(maxSal, p) -> maxSal > p.getSalary() ? maxSal : p.getSalary(),
(maxSal1, maxSal2) -> maxSal1 > maxSal2 ? maxSal1 : maxSal2);
System.out.println("工资之和:" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3);
System.out.println("最高工资:" + maxSalary + "," + maxSalary2);
}
reduce方法有三个重载的方法
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
第三种签名的用法相较前两种稍显复杂,由于前两种实现有一个缺陷,它们的计算结果必须和stream中的元素类型相同。
分析下它的三个参数:
identity: 一个初始化的值;这个初始化的值其类型是泛型U,与Reduce方法返回的类型一致;注意此时Stream中元素的类型是T,与U可以不一样也可以一样,这样的话操作空间就大了;不管Stream中存储的元素是什么类型,U都可以是任何类型,如U可以是一些基本数据类型的包装类型Integer、Long等;或者是String,又或者是一些集合类型ArrayList等;后面会说到这些用法。
accumulator: 其类型是BiFunction,输入是U与T两个类型的数据,而返回的是U类型;也就是说返回的类型与输入的第一个参数类型是一样的,而输入的第二个参数类型与Stream中元素类型是一样的。
combiner: 其类型是BinaryOperator,支持的是对U类型的对象进行操作;
第三个参数combiner主要是使用在并行计算的场景下;如果Stream是非并行时,第三个参数实际上是不生效的。
因此针对这个方法的分析需要分并行与非并行两个场景。
比如我们要对一个一系列int值求和,但是求和的结果用一个int类型已经放不下,必须升级为long类型,此实第三签名就能发挥价值了,它不将执行结果与stream中元素的类型绑死。
List<Integer> numList = Arrays.asList(Integer.MAX_VALUE,Integer.MAX_VALUE);
long result = numList.stream().reduce(0L,(a,b) -> a + b, (a,b)-> 0L );
System.out.println(result);
再比如将一个int类型的ArrayList转换成一个String类型的ArrayList
List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6);
ArrayList<String> result = numList.stream().reduce(new ArrayList<String>(), (a, b) -> {
a.add("element-" + Integer.toString(b));
return a;
}, (a, b) -> null);
System.out.println(result);
收集(collect)
collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。
collect主要依赖java.util.stream.Collectors类内置的静态方法。
collector部分方法与stream重合了,优先使用stream中的
Collector是专门用来作为Stream的collect方法的参数的
public interface Stream<T> extends BaseStream<T, Stream<T>> {
<R, A> R collect(Collector<? super T, A, R> collector);
}
Collector主要包含五个参数,它的行为也是由这五个参数来定义的,如下所示
public interface Collector<T, A, R> {
// supplier参数用于生成结果容器,容器类型为A
Supplier<A> supplier();
// accumulator用于消费元素,也就是归纳元素,这里的T就是元素,它会将流中的元素一个一个与结果容器A发生操作
BiConsumer<A, T> accumulator();
// combiner用于两个两个合并并行执行的线程的执行结果,将其合并为一个最终结果A
BinaryOperator<A> combiner();
// finisher用于将之前整合完的结果R转换成为A
Function<A, R> finisher();
// characteristics表示当前Collector的特征值,这是个不可变Set
Set<Characteristics> characteristics();
}
Collector拥有两个of方法用于生成Collector实例,其中一个拥有上面所有五个参数,另一个四个参数,不包括finisher。
public interface Collector<T, A, R> {
// 四参方法,用于生成一个Collector,T代表流中的一个一个元素,R代表最终的结果
public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner,
Characteristics... characteristics) {/*...*/}
// 五参方法,用于生成一个Collector,T代表流中的一个一个元素,A代表中间结果,R代表最终结果,finisher用于将A转换为R
public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A, R> finisher,
Characteristics... characteristics) {/*...*/}
}
Characteristics:这个特征值是一个枚举,拥有三个值:CONCURRENT(多线程并行),UNORDERED(无序),IDENTITY_FINISH(无需转换结果)。其中四参of方法中没有finisher参数,所以必有IDENTITY_FINISH特征值。
归集(toList/toSet/toMap)
因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20);
//toList默认为ArrayList
List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
//toSet默认为HashSet
Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());
Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
.collect(Collectors.toMap(Person::getName, p -> p));
System.out.println("toList:" + listNew);
System.out.println("toSet:" + set);
System.out.println("toMap:" + map);
LinkedList<Integer> linkedList = list.stream().filter(x -> x % 2 == 0)
.collect(Collectors.toCollection(LinkedList::new));
System.out.println("tolinkedList:" + linkedList);
Map<?, Person> currentMap = personList.stream().filter(p -> p.getSalary() > 8000)
.collect(Collectors.toConcurrentMap(Person::getName, p -> p));
System.out.println("tocurrentMap:" + currentMap);
}
toMap 重载
toMap方法是根据给定的键生成器和值生成器生成的键和值保存到一个map中返回,键和值的生成都依赖于元素,可以指定出现重复键时的处理方案和保存结果的map
public final class Collectors {
// 指定键和值的生成方式keyMapper和valueMapper
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {/*...*/}
// 在上面方法的基础上增加了对键发生重复时处理方式的mergeFunction,比如上面的默认的处理方法就是抛出异常
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {/*...*/}
// 在第二个方法的基础上再添加了结果Map的生成方法。
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {/*...*/}
}
toMap实例
public class CollectorsTest {
public static void toMapTest(List<String> list){
Map<String,String> map = list.stream().limit(3).collect(Collectors.toMap(e -> e.substring(0,1),e -> e));
Map<String,String> map1 = list.stream().collect(Collectors.toMap(e -> e.substring(0,1),e->e,(a,b)-> b));
Map<String,String> map2 = list.stream().collect(Collectors.toMap(e -> e.substring(0,1),e->e,(a,b)-> b,HashMap::new));
System.out.println(map.toString() + "\n" + map1.toString() + "\n" + map2.toString());
}
public static void main(String[] args) {
List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
toMapTest(list);
}
}
统计(count/averaging)
Collectors提供了一系列用于数据统计的静态方法:
计数:count
平均值:averagingInt、averagingLong、averagingDouble
最值:maxBy、minBy
求和:summingInt、summingLong、summingDouble
统计以上所有:summarizingInt、summarizingLong、summarizingDouble
public static void main(String[] args) {
// 求总数(但没有这样的用法,直接personList.size)
Long count1 = personList.stream().collect(Collectors.counting());
Long count2 = personList.stream().count();
// 求平均工资
Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
// 求最高工资
Optional<Integer> max1 = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
Optional<Integer> max2 = personList.stream().map(Person::getSalary).max(Integer::compare);
// 求工资之和
//summing**生成一个用于求元素和的Collector,首先通过给定的mapper将元素转换类型,然后再求和
Integer sum1 = personList.stream().collect(Collectors.summingInt(Person::getSalary));
Integer sum2 = personList.stream().mapToInt(Person::getSalary).sum();
// 一次性统计所有信息
DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));
System.out.println("员工总数:" + count1 + "," + count2);
System.out.println("员工平均工资:" + average);
System.out.println("员工最高工资:" + max1.get() + "," + max2.get());
System.out.println("员工工资总和:" + sum1 + "," + sum2);
System.out.println("员工工资所有统计:" + collect);
}
分组(partitioningBy/groupingBy)
- 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
- 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
public static void main(String[] args) {
// 将员工按薪资是否高于8000分组
Map<Boolean, List<Person>> part = personList.stream()
.collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
// 将员工按性别分组
Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
// 将员工先按性别分组,再按地区分组
Map<String, Map<String, List<Person>>> group2 = personList.stream()
.collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
System.out.println("员工按薪资是否大于8000分组情况:" + part);
System.out.println("员工按性别分组情况:" + group);
System.out.println("员工按性别、地区:" + group2);
}
partitioningBy 重载
该方法将流中的元素按照给定的校验规则的结果分为两个部分,放到一个map中返回,map的键是Boolean类型,值为元素的列表List
public final class Collectors {
// 只需一个校验参数predicate
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {/*...*/}
// 在上面方法的基础上增加了对流中元素的处理方式的Collector,比如上面的默认的处理方法就是Collectors.toList()
public static <T, D, A>
Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
Collector<? super T, A, D> downstream) {/*...*/}
}
partitioningBy实例
public class CollectorsTest {
public static void partitioningByTest(List<String> list){
Map<Boolean,List<String>> map = list.stream().collect(Collectors.partitioningBy(e -> e.length()>5));
Map<Boolean,Set<String>> map2 = list.stream().collect(Collectors.partitioningBy(e -> e.length()>6,Collectors.toSet()));
System.out.println(map.toString() + "\n" + map2.toString());
}
public static void main(String[] args) {
List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
partitioningByTest(list);
}
}
groupingby重载
public final class Collectors {
// 只需一个分组参数classifier,内部自动将结果保存到一个map中,每个map的键为?类型(即classifier的结果类型),值为一个list,这个list中保存在属于这个组的元素。
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(
Function<? super T, ? extends K> classifier) {/*...*/}
// 在上面方法的基础上增加了对流中元素的处理方式的Collector,比如上面的默认的处理方法就是Collectors.toList()
public static <T, K, A, D>Collector<T, ?, Map<K, D>> groupingBy(
Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream) {/*...*/}
// 在第二个方法的基础上再添加了结果Map的生成方法。
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream) {/*...*/}
}
groupingby实例
public class CollectorsTest {
public static void groupingByTest(List<String> list){
Map<Integer,List<String>> s = list.stream().collect(Collectors.groupingBy(String::length));
Map<Integer,List<String>> ss = list.stream().collect(Collectors.groupingBy(String::length, Collectors.toList()));
Map<Integer,Set<String>> sss = list.stream().collect(Collectors.groupingBy(String::length,HashMap::new,Collectors.toSet()));
System.out.println(s.toString() + "\n" + ss.toString() + "\n" + sss.toString());
}
public static void main(String[] args) {
List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
groupingByTest(list);
}
}
接合(joining)
joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
public static void main(String[] args) {
String names = personList.stream().map(Person::getName).collect(Collectors.joining(","));
System.out.println("所有员工的姓名:" + names);
List<String> list = Arrays.asList("A", "B", "C");
String string = list.stream().collect(Collectors.joining("-"));
//可替换为 String string1 = String.join("-", list);
System.out.println("拼接后的字符串:" + string);
}
归约(reducing)
Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。
public final class Collectors {
// 无初始值的情况,返回一个可以生成Optional结果的Collector
public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) {/*...*/}
// 有初始值的情况,返回一个可以直接产生结果的Collector
public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) {/*...*/}
// 有初始值,还有针对元素的处理方案mapper,生成一个可以直接产生结果的Collector,元素在执行结果操作op之前需要先执行mapper进行元素转换操作
public static <T, U> Collector<T, ?, U> reducing(U identity,
Function<? super T, ? extends U> mapper,
BinaryOperator<U> op) {/*...*/}
}
public class CollectorsTest {
public static void reducingTest(List<String> list){
System.out.println(list.stream().limit(4).map(String::length).collect(Collectors.reducing(Integer::sum)));
System.out.println(list.stream().limit(3).map(String::length).collect(Collectors.reducing(0, Integer::sum)));
System.out.println(list.stream().limit(4).collect(Collectors.reducing(0,String::length,Integer::sum)));
}
public static void main(String[] args) {
List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
reducingTest(list);
}
}
public static void main(String[] args) {
// 每个员工减去起征点后的薪资之和(这个例子并不严谨,但一时没想到好的例子)
Integer sum1 = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> (i + j - 5000)));
Integer sum2 = personList.stream().map(Person::getSalary).reduce(0, (i, j) -> (i + j - 5000));
System.out.println("员工扣税薪资总和:" + sum1 + "," + sum2);
// stream的reduce
Optional<Integer> sum3 = personList.stream().map(Person::getSalary).reduce(Integer::sum);
System.out.println("员工薪资总和:" + sum3.get());
}
排序(sorted)
sorted,中间操作。有两种排序:
- sorted():自然排序,流中元素需实现Comparable接口
- sorted(Comparator com):Comparator排序器自定义排序
public static void main(String[] args) {
// 按工资升序排序(自然排序)
List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
.collect(Collectors.toList());
// 按工资倒序排序
List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
.map(Person::getName).collect(Collectors.toList());
// 先按工资再按年龄升序排序
List<String> newList3 = personList.stream()
.sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
.collect(Collectors.toList());
// 先按工资再按年龄自定义排序(降序)
List<String> newList4 = personList.stream().sorted((p1, p2) -> {
if (p1.getSalary() == p2.getSalary()) {
return p2.getAge() - p1.getAge();
}
else {
return p2.getSalary() - p1.getSalary();
}
}).map(Person::getName).collect(Collectors.toList());
System.out.println("按工资升序排序:" + newList);
System.out.println("按工资降序排序:" + newList2);
System.out.println("先按工资再按年龄升序排序:" + newList3);
System.out.println("先按工资再按年龄自定义降序排序:" + newList4);
}
提取/组合
流也可以进行合并、去重、限制、跳过等操作
public static void main(String[] args) {
String[] arr1 = {"a", "b", "c", "d" };
String[] arr2 = {"d", "e", "f", "g" };
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
// concat:合并两个流 distinct:去重
List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
// limit:限制从流中获得前n个数据
List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
// skip:跳过前n个数据
List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());
System.out.println("流合并:" + newList);
System.out.println("limit:" + collect);
System.out.println("skip:" + collect2);
}
映射(mapping)
这个映射是首先对流中的每个元素进行映射,即类型转换,然后再将新元素以给定的Collector进行归纳
public class CollectorsTest {
public static void mapingTest(List<String> list){
List<Integer> ll = list.stream().limit(5).collect(Collectors.mapping(Integer::valueOf,Collectors.toList()));
}
public static void main(String[] args) {
List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
mapingTest(list);
}
}
实例中截取字符串列表的前5个元素,将其分别转换为Integer类型,然后放到一个List中返回
collect后处理(collectingAndThen)
该方法是在归纳动作结束之后,对归纳的结果进行再处理
public class CollectorsTest {
public static void collectingAndThenTest(List<String> list){
int length = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(),e -> e.size()));
System.out.println(length);
}
public static void main(String[] args) {
List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
collectingAndThenTest(list);
}
}