JAVA8-Java8的流式编程-预知篇

本文最后更新于:September 24, 2022 pm

JAVA8 是一个有里程碑的一个版本,提供了很多的新特性。Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。

目录

函数式接口

有且只有一个抽象方法的接口被称为函数式接口,该接口中可以包含其他的方法(默认,静态,私有)。函数式接口用@FunctionalInterface注解。

关于函数式接口只有一个抽象方法的理解:

  • 一个函数式接口有且只有一个抽象方法。
  • 默认方法不是抽象方法,因为它们已经实现了。
  • 重写了超类Object类中任意一个public方法的方法并不算接口中的抽象方法。如果一个接口中声明的抽象方法是重写了超类Object类中任意一个public方法,那么这些抽象方法并不会算入接口的抽象方法数量中。因为任何接口的实现都会从其父类Object或其它地方获得这些方法的实现。
    • 比如Comparator接口,有两个抽象方法compare和equals,但equals并不算入接口中的抽象方法,所以Comparator接口还是满足函数式接口的要求,Comparator接口是一个函数式接口

方法引用

方法的引用可以重复使用现有的方法定义。有三种使用方式:

类::静态方法名。指向静态方法的方法引用,例如Integer的compare方法 ,可以写成Integer::compare。

1
Comparator<Integer> compare = Integer::compare;
  • 使用前提:在重写时,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候可以引用类的静态方法。(如果所重写的方法是没有参数的,调用的方法也是没有参数的,那也相当于符合情况)
1
2
3
4
List<Stu> list = new ArrayList<>();
Stream<Stu> stream = list.stream();
stream.map(it->it.getAge())
.map(it->String.valueOf(it)); // 主要看这个的区别

优化后:

1
2
3
4
List<Stu> list = new ArrayList<>();
Stream<Stu> stream = list.stream();
stream.map(Stu::getAge)
.map(String::valueOf);

类::实例方法名。指向任意类型实例方法的方法引用,例如String的equals方法,写成String::equals。

1
BiPredicate<String, Object> biPredicate = String::equals;
  • 在重写方法时,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且把要重写的抽象方法中剩余的所有参数都按照顺序传入这个成员方法中,这时候就可以引用类的实例方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
interface UseString{
String use(String str,int start,int end);
}
public static String setName(String str,UseString useString){
return useString.use(str,0,1);
}

setName("tothefor", new UseString() {
@Override
public String use(String str, int start, int end) {
return str.substring(start,end);
}
});

优化为:

1
2
3
4
5
6
7
interface UseString{
String use(String str,int start,int end);
}
public static String setName(String str,UseString useString){
return useString.use(str,0,1);
}
setName("tothefor", String::substring);

对象::实例方法名。指向现有对象的实例方法的方法引用,例如System.out的println方法,写成System.out::println。

1
2
Consumer runnable = System.out::println;
Consumer<String> setName = s1::setName;
  • 在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这时候就可以引用对象的实例方法。
1
2
3
4
5
6
List<Stu> list = new ArrayList<>();
Stream<Stu> stream = list.stream();
StringBuilder sb = new StringBuilder();

stream.map(Stu::getName)
.forEach(it->sb.append(it)); // 区别

优化后:

1
2
3
4
5
6
List<Stu> list = new ArrayList<>();
Stream<Stu> stream = list.stream();
StringBuilder sb = new StringBuilder();

stream.map(Stu::getName)
.forEach(sb::append);

构造器引用

构造器的引用:ClassName::new。对于一个现有构造函数,可以利用名称和关键字new来创建它的一个引用,例如:String::new。

1
Supplier<ArrayList> runnable = ArrayList::new;
  • 在重写方法时,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且把要重写的抽象方法中的所有参数都按照顺序传入了这个构造方法中,这时候就可以引用构造器。
1
2
3
4
5
6
List<Stu> list = new ArrayList<>();
list.stream()
.map(it -> it.getName())
.map(it->new StringBuilder(it))
.map(it->it.append("TTF").toString())
.forEach(it-> System.out.println(it));

优化后:

1
2
3
4
5
6
List<Stu> list = new ArrayList<>();
list.stream()
.map(Stu::getName)
.map(StringBuilder::new)
.map(it->it.append("TTF").toString())
.forEach(System.out::println);

JAVA8新增函数式接口

Java8新增的java.util.function包定义的函数式接口。

Supplier(生产)

指定接口的泛型是什么类型,那么接口的get方法就会生产什么类型的数据。

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface Supplier<T> {

/**
* Gets a result.
*
* @return a result
*/
T get(); // 用来获取一个泛型参数指定类型的对象数据
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Stu {
String name;
int age;
long weight;
double money;
}

List<Stu> stus = new ArrayList<>();
Stu s1 = new Stu("bbbbb", 23, 24, 23.24);
Stu s2 = new Stu("ddddd", 11, 124, 133.124);
Stu s3 = new Stu("aaaaa", 43, 34, 13.14);
Stu s4 = new Stu("eeeee", 13, 34, 13.14);
Stu s5 = new Stu("ccccc", 33, 34, 13.14);
stus.add(s1);
stus.add(s2);
stus.add(s3);
stus.add(s4);
stus.add(s5);

// 最大值
Stu max = getM(()->{
return stus.stream().max((o1, o2) -> o1.getAge()-o2.getAge()).get();
});
System.out.println(max);
// 最小值
Stu min = getM(()->{
return stus.stream().min((o1, o2) -> o1.getAge()-o2.getAge()).get();
});
System.out.println(min);

public static Stu getM(Supplier<Stu> supplier){
return supplier.get();
}

输出:

1
2
Stu{name='aaaaa', age=43, weight=34, money=13.14}
Stu{name='ddddd', age=11, weight=124, money=133.124}

个人感觉:就是一个方法的实现体,只不过实现体在使用时才确认。而常规的方法体是写死的,专门用来完成某一个功能。而使用了此方法后,虽然也是用来获取某一个值,但是这个值的获取方式却不同,是完成某一种功能

所以为什么叫生产,最后会给出一个产品(返回值),但产品怎么产的(实现体),由你自己定。

Consumer(消费)

消费型接口,指定接口的泛型是什么类型,那么就可以使用接口中的accept方法消费什么类型的数据,具体怎么消费就需要自定义。默认方法则是可以把两个Consumer接口组合在一起对数据进行消费,谁写在前边就先消费谁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@FunctionalInterface
public interface Consumer<T> {

/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);

/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
  • accept:对给定的参数执行此操作。
  • andThen:返回一个组合的 Consumer ,按顺序执行该操作,然后执行 after操作。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String[] str = {"Andy,13", "Mini,7.6", "Ak,23"};
for (String it : str) {
fix(it, c1 -> {
String name = it.split(",")[0];
System.out.println("姓名:" + name);
}, c2 -> {
String age = it.split(",")[1];
System.out.println("年龄:" + age);
});
}


public static void fix(String str, Consumer<String> co1, Consumer<String> con2) {
co1.andThen(con2).accept(str);
}

输出:

1
2
3
4
5
6
姓名:Andy
年龄:13
姓名:Mini
年龄:7.6
姓名:Ak
年龄:23

对传入的值str进行处理,自定义控制执行的先后顺序。

重点示例一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String[] str = {"Andy,13", "Mini,7.6", "Ak,23"};
for (String it : str) {
fix(it, c1 -> {
System.out.print("c1 == ");
System.out.print(it+" <=========> ");
it.toUpperCase();
System.out.println(it+" <=========> ");
}, c2 -> {
System.out.print("c2 == ");
System.out.print(it+" <=========> ");
it.toUpperCase();
System.out.println(it+" <=========> ");
});
}

输出:

1
2
3
4
5
6
c1 == Andy,13 <=========> Andy,13 <=========> 
c2 == Andy,13 <=========> Andy,13 <=========>
c1 == Mini,7.6 <=========> Mini,7.6 <=========>
c2 == Mini,7.6 <=========> Mini,7.6 <=========>
c1 == Ak,23 <=========> Ak,23 <=========>
c2 == Ak,23 <=========> Ak,23 <=========>

修改对象值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
List<Stu> stus = new ArrayList<>();
Stu s1 = new Stu("bbbbb", 23, 24, 23.24);
Stu s2 = new Stu("ddddd", 11, 124, 133.124);
Stu s3 = new Stu("aaaaa", 43, 34, 13.14);
Stu s4 = new Stu("eeeee", 13, 34, 13.14);
Stu s5 = new Stu("ccccc", 33, 34, 13.14);
stus.add(s1);
stus.add(s2);
stus.add(s3);
stus.add(s4);
stus.add(s5);

for (Stu it : stus) {
System.out.println(it);
fix(it, c1 -> {
it.setName("qqqqqqq");
}, c2 -> {
it.setAge(232323);
});
System.out.println(it);
}

public static void fix(Stu str, Consumer<Stu> co1, Consumer<Stu> con2) {
co1.andThen(con2).accept(str);
}

输出:

1
2
3
4
5
6
7
8
9
10
Stu{name='bbbbb', age=23, weight=24, money=23.24}
Stu{name='qqqqqqq', age=232323, weight=24, money=23.24}
Stu{name='ddddd', age=11, weight=124, money=133.124}
Stu{name='qqqqqqq', age=232323, weight=124, money=133.124}
Stu{name='aaaaa', age=43, weight=34, money=13.14}
Stu{name='qqqqqqq', age=232323, weight=34, money=13.14}
Stu{name='eeeee', age=13, weight=34, money=13.14}
Stu{name='qqqqqqq', age=232323, weight=34, money=13.14}
Stu{name='ccccc', age=33, weight=34, money=13.14}
Stu{name='qqqqqqq', age=232323, weight=34, money=13.14}

重点示例二

控制执行Consumer的先后顺序。

1
2
3
4
5
6
7
8
// 先执行co1再执行con2
public static void fix(String str, Consumer<String> co1, Consumer<String> con2) {
co1.andThen(con2).accept(str);
}
// 先执行con2再执行co1
public static void fix(String str, Consumer<String> co1, Consumer<String> con2) {
con2.andThen(co1).accept(str);
}

BiConsumerConsumer类似。

Predicate(判断)

用来对某种数据类型的数据进行判断,接口中的test方法就是用来制定判断规则并把结果返回。三个默认方法其实就是相对三个逻辑——与、或、非。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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);
}
  • test:即自定义判断真假的条件。
  • and:返回一个条件与另一个条件同时满足时的结果。(与)
  • or:返回一个条件或另一个条件满足时的结果。(或)
  • negate:返回相反结果。(取反)

示例:

1
2
3
4
5
6
7
8
9
Integer res = 23;
Predicate<Integer> p1 = (i) -> i > 10;
Predicate<Integer> p2 = (i) -> i > 20;
Predicate<Integer> p3 = (i) -> i > 30;
System.out.println(p1.test(res));
System.out.println(p2.test(res));
System.out.println(p3.test(res));
System.out.println(p1.and(p2).test(res));
System.out.println(p1.or(p2).test(res));

输出:

1
2
3
4
5
true
true
false
true
true

综合示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
List<Stu> stus = new ArrayList<>();
Stu s1 = new Stu("bbbbb", 23, 24, 23.24);
Stu s2 = new Stu("ddddd", 11, 124, 133.124);
Stu s3 = new Stu("aaaaa", 43, 34, 13.14);
Stu s4 = new Stu("eeeee", 13, 34, 13.14);
Stu s5 = new Stu("ccccc", 33, 34, 13.14);
stus.add(s1);
stus.add(s2);
stus.add(s3);
stus.add(s4);
stus.add(s5);

List<Stu> list = new ArrayList<>();
for (Stu it : stus) {
list.add(check(it, p -> p.getAge() < 20));
}
list.forEach(System.out::println);


public static Stu check(Stu stu, Predicate<Stu> p1) {
List<Stu> list = new ArrayList<>();
if (p1.test(stu)) {
return stu;
}
return null;
}

输出:

1
2
3
4
5
null
Stu{name='ddddd', age=11, weight=124, money=133.124}
null
Stu{name='eeeee', age=13, weight=34, money=13.14}
null

BiPredicate与Predicate类似。

Function(转换)

用作转换数据类型。调用apply方法把T数据类型的数据转换为R数据类型的数据,但转换规则需要自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FunctionalInterface
public interface Function<T, R> {

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;
}
}
  • apply:转换数据类型。
  • andThen:把两个Function接口连接起来,第二个Function接口会把第一个Function接口的输出结果作为输入。

示例:

1
2
3
4
5
6
Integer integer = 2;
Function<Integer, Integer> function1 = (i) -> i+1;
Function<Integer, Integer> function2 = (i) -> i*10;
System.out.println(function1.apply(integer));
System.out.println(function2.apply(integer));
System.out.println(function1.andThen(function2).apply(integer));

输出:

1
2
3
3
20
30

BiFunction与Function类似。

总结

  • Supplier(生产):自定义生产方式,返回产品。
  • Consumer(消费):传入要消费的产品(值),自定义进行消费(使用)。
  • Predicate(判断):根据自定义规则判断传入的值。
  • Function(转换):根据自定义规则转换传入的值。

本文作者: 墨水记忆
本文链接: https://tothefor.com/DragonOne/ceb3d903.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!