函数式编程
Stream流
一、函数式编程:
1、1.概念
函数式接⼝在Java中是指:有且仅有⼀个抽象⽅法的接⼝。
函数式接⼝,即适⽤于函数式编程场景的接⼝。⽽Java中的函数式编程体现就是Lambda,所以函数式接⼝ 就是可以适⽤于Lambda使⽤的接⼝。只有确保接⼝中有且仅有⼀个抽象⽅法,Java中的Lambda才能顺利 地进⾏推导。
1、2.格式
只要确保接⼝中有且仅有⼀个抽象⽅法即可:
修饰符 interface 接⼝名称 {
public abstract 返回值类型 ⽅法名称(可选参数信息);
// 其他⾮抽象⽅法内容
}
由于接⼝当中抽象⽅法的 public abstract 是可以省略的,所以定义⼀个函数式接⼝很简:
public interface MyFunctionalInterface {
void myMethod();
}
1、3.自定义函数式接口
对于刚刚定义好的 MyFunctionalInterface 函数式接⼝,典型使⽤场景就是作为⽅法的参数:
public class Demo1{
Private static void do(MyFunctionalInterface inter){
inter.myMethod();
}
public static void main(String[] args){
do (() -> System.out.println("----"))
}
}
2、常用函数接口
2、1.Supplier
java.util.function.Supplier 接⼝仅包含⼀个⽆参的⽅法: T get() 。⽤来获取⼀个泛型参数 指定类型的对象数据。由于这是⼀个函数式接⼝,这也就意味着对应的Lambda表达式需要“对外提供”⼀ 个符合泛型类型的对象数据。
实例:
import java.util.function.Supplier;
public class Demo1{
private static String getString(Supplier<String> function){
return function.get();
}
public static void main(String[] args){
String A = "aa";
String B = "bb";
System.out.println(getString(() -> A + B));
}
}
2.2.Consumer
java.util.function.Consumer 接⼝则正好与Supplier接⼝相反,它不是⽣产⼀个数据,⽽是消费 ⼀个数据,其数据类型由泛型决定。
抽象方法:accept
Consumer 接⼝中包含抽象⽅法 void accept(T t) ,意为消费⼀个指定泛型的数据。基本使⽤如:
import java.util.function.Consumer;
public class Demo1{
private static void consumeString(Consumer<String> function){
function.accept("aa");
}
public static void main(String[] args){
consumeString(s -> System.out.println(s));
}
}
默认方法:andThen
如果⼀个⽅法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,⾸先做⼀ 个操作,然后再做⼀个操作,实现组合。⽽这个⽅法就是 Consumer 接⼝中的default⽅法 andThen 。 要想实现组合,需要两个或多个Lambda表达式即可 。
2、3.Predicate
有时候我们需要对某种类型的数据进⾏判断,从⽽得到⼀个boolean值结果。这时可以 使 java.util.function.Predicate 接⼝。
抽象方法:test
有时候我们需要对某种类型的数据进⾏判断,从⽽得到⼀个boolean值结果。这时可以 使 java.util.function.Predicate 接⼝。
import java.util.function.Predicate;
public class Demo15PredicateTest {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.test("HelloWorld");
System.out.println("字符串很⻓吗:" + veryLong);
}
public static void main(String[] args) {
method(s -> s.length() > 5);
}
}
默认方法:
2.3.2、and:
既然是条件判断,就会存在与、或、⾮三种常⻅的逻辑关系。其中将两个 Predicate 条件使⽤“与”逻辑连 接起来实现“并且”的效果时,可以使⽤default⽅法 and 。其JDK源码为
```java
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
2.3.3、or:
与 and 的“与”类似,默认⽅法 or 实现逻辑关系中的“或”。JDK源码为:
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
2.3.4、negate:
“与”、“或”已经了解了,剩下的“⾮”(取反)也会简单。默认⽅法 negate 的JDK源代码为:
default Predicate<T> negate() {
return (t) -> !test(t);
}
ps:它是执⾏了test⽅法之后,对结果boolean值进⾏“!”取反⽽已。⼀定要在 test ⽅法 调⽤之前调⽤ negate ⽅法,正如 and 和 or ⽅法⼀样:
2、4.Function:
java.util.function.Function 接⼝⽤来根据⼀个类型的数据得到另⼀个类型的数据,前者称为 前置条件,后者称为后置条件。
抽象⽅法:apply
Function 接⼝中最主要的抽象⽅法为: R apply(T t) ,根据类型T的参数获取类型R的结果。 使⽤的场景例如:将 String 类型转换为 Integer 类型。
import java.util.function.Function;
public class Demo11FunctionApply {
private static void method(Function<String, Integer> function) {
int num = function.apply("10");
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s -> Integer.parseInt(s));
}
}
默认方法: andThen
Function 接⼝中有⼀个默认的 andThen ⽅法,⽤来进⾏组合操作。JDK源代码如:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
ps: 该⽅法同样⽤于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多
二、Stream流
1.1获取流的方法
java.util.stream.Stream 是Java 8新加⼊的最常⽤的流接⼝。(这并不是⼀个函数式接⼝。) 获取⼀个流⾮常简单,有以下⼏种常⽤的⽅式:
所有的 Collection 集合都可以通过 stream 默认⽅法获取流;
Stream 接⼝的静态⽅法 of 可以获取数组对应的流。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
public class Demo1 {
public static void main(String[] args) {
//获得Stream对象
//List获得stream对象
List <String> list = List.of("鄙人","张麻子","黄四郎");//快速生成集合,创建之后不可变
Stream<String> stream = list.stream();
//set获得stream对象
Set<String > set = Set.of("鄙人","张麻子","黄四郎");
Stream<String> stream1 = set.stream();
//Map获得stream对象
Map<String ,Integer>map = Map.of("鄙人",1,"张麻子",2,"黄四郎",3);
Stream<String> stream2 = map.keySet().stream();
Stream<Integer> stream3 = map.values().stream();
Stream<Map.Entry<String, Integer>> stream4 = map.entrySet().stream();
//数组获得stream对象
String [] strArr = {"鄙人","张麻子","黄四郎"};
Stream<String> stream6 = Arrays.stream(strArr);
Stream<Integer> stream5 = Stream.of(1,2,3,4,5);
}
}
1、2.常用方法:
1.2.1:逐一处理:forEach
虽然⽅法名字叫 forEach ,但是与for循环中的“for-each”昵称不同。
void forEach(Consumer<? super T> action);
该⽅法接收⼀个 Consumer 接⼝函数,会将每⼀个流元素交给该函数进⾏处理。
实例:
import java.util.stream.Stream;
public class Demo12StreamForEach {
public static void main(String[] args) {
Stream<String> stream = Stream.of("鄙人", "张麻子", "黄老爷");
stream.forEach(name-> System.out.println(name));
}
}
1.2.2:过滤:filter
可以通过 filter ⽅法将⼀个流转换成另⼀个⼦集流。⽅法签名:
Stream<T> filter(Predicate<? super T> predicate);
该接⼝接收⼀个 Predicate 函数式接⼝参数(可以是⼀个Lambda或⽅法引⽤)作为筛选条件。
该⽅法将会产⽣⼀个boolean值结果,代表指定的条件是否满⾜。如果结果为true,那么Stream流的 filter ⽅法将会留⽤元素;如果结果为false,那么 filter ⽅法将会舍弃元素。
实例: 通过Lambda表达式来指定了筛选的条件:必须姓张。
import java.util.stream.Stream;
public class Demo07StreamFilter {
public static void main(String[] args) {
Stream<String> original = Stream.of("张麻子", "黄四郎", "九筒");
Stream<String> result = original.filter(s -> s.startsWith("张"));
}
}
1.2.3:映射:map
如果需要将流中的元素映射到另⼀个流中,可以使⽤ map ⽅法。⽅法签名:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接⼝需要⼀个 Function 函数式接⼝参数,可以将当前流中的T类型数据转换为另⼀种R类型的流。
实例: map ⽅法的参数通过⽅法引⽤,将字符串类型转换成为了int类型(并⾃动装箱为 Integer类对象)。
import java.util.stream.Stream;
public class Demo08StreamMap {
public static void main(String[] args) {
Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(str->Integer.parseInt(str));
}
}
1.2.4:统计个数:count
正如旧集合 Collection 当中的 size ⽅法⼀样,流提供 count ⽅法来数⼀数其中的元素个数:
long count();
该⽅法返回⼀个long值代表元素个数(不再像旧集合那样是int值)。
import java.util.stream.Stream;
public class Demo09StreamCount {
public static void main(String[] args) {
Stream<String> original = Stream.of("鄙人","张麻子","黄四郎");
Stream<String> result = original.filter(s -> s.startsWith("张"));
System.out.println(result.count()); // 2
}
}
1.2.5: 取⽤前⼏个:limit
limit ⽅法可以对流进⾏截取,只取⽤前n个。⽅法签名:
Stream<T> limit(long maxSize);
参数是⼀个long型,如果集合当前⻓度⼤于参数则进⾏截取;否则不进⾏操作。基本使⽤:
import java.util.stream.Stream;
public class Demo10StreamLimit {
public static void main(String[] args) {
Stream<String> original = Stream.of("鄙人","张麻子","黄四郎");
Stream<String> result = original.limit(2);
System.out.println(result.count()); // 2
}
}
1.2.6: 跳过前⼏个:skip
如果希望跳过前⼏个元素,可以使⽤ skip ⽅法获取⼀个截取之后的新流:
Stream<T> skip(long n);
如果流的当前⻓度⼤于n,则跳过前n个;否则将会得到⼀个⻓度为0的空流。基本使⽤:
import java.util.stream.Stream;
public class Demo11StreamSkip {
public static void main(String[] args) {
Stream<String> original = Stream.of("鄙人","张麻子","黄四郎");
Stream<String> result = original.skip(2);
System.out.println(result.count()); // 1
}
}
1.2.7: 组合:concat
如果有两个流,希望合并成为⼀个流,那么可以使⽤ Stream 接⼝的静态⽅法 concat :
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
实例:
import java.util.stream.Stream;
public class Demo12StreamConcat {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("张麻子");
Stream<String> streamB = Stream.of("张起灵");
Stream<String> result = Stream.concat(streamA, streamB);
}
}
1、3 小结练习
题⽬ :现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,要求使⽤传统的for循环(或增强for循环)依 次进⾏以下若⼲操作步骤:
第⼀个队伍只要名字为3个字的成员姓名;存储到⼀个新集合中。
第⼀个队伍筛选之后只要前3个⼈;存储到⼀个新集合中。
第⼆个队伍只要姓张的成员姓名;存储到⼀个新集合中。
第⼆个队伍筛选之后不要前2个⼈;存储到⼀个新集合中。
将两个队伍合并为⼀个队伍;存储到⼀个新集合中。
根据姓名创建 Person 对象;存储到⼀个新集合中。
打印整个队伍的Person对象信息。
package Stream;
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo4 {
public static void main(String[] args) {
//第⼀⽀队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("⽯破天");
one.add("⽯中⽟");
one.add("⽼⼦");
one.add("庄⼦");
one.add("洪七公");
//第⼆⽀队伍
ArrayList<String> two = new ArrayList<>();
two.add("古⼒娜扎");
two.add("张⽆忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张⼆狗");
Stream<String> s1 = one.stream().filter(t -> t.length() == 3).limit(3);
Stream<String> s2 = two.stream().filter(t -> t.startsWith("张")).skip(2);
Stream<String> stream = Stream.concat(s1, s2);
stream.map(name -> new Person(name)).forEach(p -> System.out.println(p ));
}
}