Lambda表达式是基于函数式接口(Functional Interface - FI)而产生的,函数式接口就是具有唯一的一个抽象方法的接口。所以满足这个条件的接口都可以写成Lambda表达式。 比如Runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
我们平时在运行一个任务时会这样写
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("hi");
}
}).start();
而Runnable是能够被改编为Lambda表达式的,第一版改编如下:
new Thread(()->{
System.out.println("hi");
}
).start();
可以看出,没有入参时,就直接用括号代替。又因为花括号内只有一个语句,因此花括号也可以省略,第二版改编如下:
new Thread(()->System.out.println("hi")).start();
Java 8 为Lambda表达式新建了一个包,该路径下的接口均为函数式接口,且被打上了@FunctionalInterface
java.util.function包下面,有5种类型的接口:
- Consumer:消费型T->void,流的消费者
- Function:函数型T->R,完成参数类型T向结果类型R的转换。配合map()使用
- Operator:函数的输入类型和输出类型相同,即可用xxxOperator
- Predicate:判断型T->boolean,判断条件是否满足,配合filter()使用
- Supplier:供给型void->T,流的提供者,配合generate()使用
Stream.generate(Supplier).map(Function).filter(Predicate).forEach(Consumer)
Stream
Stream 是Java 8的另一个新特性,它就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
现实生活中的水流,中间经过层层处理(分流、过滤、限流...),最后流到每一个水龙头出口,水流完了就没有了。
这里面的操作分为:中间操作(分流、过滤、消毒...),最终操作(用水),限流操作(限流)。
Java8的流的操作类比于水流,也将流的操作分为三种:
- Intermediate:一个流后面可以跟零个或多个 intermediate 操作。其主要目的是做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。这类操作有:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
- Terminal:一个流只能有一个 terminal 操作,Terminal操作属于一种消费型的操作,当这个操作执行后,流就被使用“光”了,无法再被操作,所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。这类操作有:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
- Short-circuiting:一个流后面可以跟零个或多个 Short-circuiting 操作。其主要目的是限流。这类操作有:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。 当然我们也可以用 Stream、Stream、Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。
一般情况下,我们在想要打印结果时会使用forEach(System.out::println),但是这是一个 terminal 操作,流在这里会被消耗掉。 在 Intermediate 操作中有一个类似的方法 peek(x -> System.out.printf("value=", x)),但是它不会消耗流。
List<List<String>> nestedList = Lists.newArrayList(
Lists.newArrayList("b", "a", "c"),
Lists.newArrayList("Cc", "Bb", "Aa"),
Lists.newArrayList("aaa", "mmm", "sss", "zzz"));
List<String> result = nestedList.stream()
.flatMap(Collection::stream)//等同于x->x.stream(),即将每一个list也转为stream,这样就相当于将所有的list拼接为一个stream
.filter(x -> x.length() >= 2)//将长度小于2的元素过滤掉,这样就剩下了:"Cc", "Bb", "Aa", "aaa", "mmm","sss","zzz"
.map(x -> x.substring(0, 1))//保留每个元素的第一个字符,这时的元素为:C, B, A, a, m ,s, z
.map(String::toUpperCase)//将所有的字母转大写
.sorted()//按照字符顺序排序,得到了:A, A, B, C, M, S, Z
.skip(3)//跳过前3个元素
.collect(Collectors.toList());//转换为List,得到C, M, S, Z
//注意:在调用sorted()后使用limit/skip,会使得sorted()的排序次数并没有减少,因为在做limit/skip时,并不知道排序后的顺序是什么样的。
Arrays.asList("ae", "f", "gqet", "abcd", "ertyu", "zxc")
.stream()
.sorted(Comparator.comparing(String::length).thenComparing(String::compareTo))//多个比较器
.forEach(System.out::println);
//#################################################
IntStream.of(1, 2, 3, 4, 5, 4, 3, 2, 1).distinct().filter(x -> x % 2 == 0).forEach(System.out::println);//打印出2, 4
Map<Integer, List<Integer>> groupedMap = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1).distinct().collect(Collectors.groupingBy(x -> x % 3));//将数字1~5按照模3的余数进行分组
IntStream.range(1, 3).forEach(System.out::println);//打印出1, 2
IntStream.rangeClosed(1, 3).parallel().map(x -> x * x).forEach(System.out::println);//打印出1, 4, 9
OptionalInt min = IntStream.rangeClosed(1, 3).min();
//#################################################
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
Stream.generate(() -> "hello").limit(10).forEach(System.out::println);
Stream.iterate(1, x -> x * 2).limit(10).forEach(x -> System.out.printf("%d, ", x));
//#################################################
Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
//#################################################
Stream.of("A", "B", "C", "D").reduce("", String::concat);//字符串拼接,得到"ABCD"
Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);//求最小值,得到-3.0
Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);//求和,得到10
Stream.of(1, 2, 3, 4).reduce(Integer::sum);//求和,得到Optional<Integer>对象,因为没有起始值,所以可能没有足够的元素
常用流式操作:
# 实体集合某个字符转换为数组: String[] arrayLink = tTaskDetails.stream().map(item -> item.getSmsUrl() + "&uid=" + item.getUid()).toArray(String[]::new);
# 实体集合某两个字段组成Map
Map<String, String> phoneCrmIdMap = archivesByPhoneList.stream().collect(Collectors.toMap(TCrmUserArchives::getPhone, TCrmUserArchives::getCrmId, (exiting, replacement) -> exiting));
# 实体集合根据某个属性值去重
List<TPhoneOrig> origPhoneDataList = origPhoneDataList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(t -> t.getOriPhone()))), ArrayList::new));
# 实体集合根据某个属性值分组
Map<Integer, List<TPhoneOrig>> groupMap = phoneOrigData.stream().collect(Collectors.groupingBy(TPhoneOrig::getGroupId));
# 实体集合按照某个属性值从小到大排序: 此处Priorit为实体Task的属性,值为整数型
taskList.stream().sorted(Comparator.comparing(Task::getPriority)).collect(Collectors.toList());
# 从大到小排序
taskList.stream().filter(item -> item.getCircleType().equals(circleType)).sorted(Comparator.comparing(Task::getPriority).reversed()).collect(Collectors.toList());
# 统计数组元素中的个数
String[] arr = {"a", "c", "a", "b", "d", "c"};
Stream.of(arr).collect(Collectors.toMap(k -> k, k -> 1, Integer::sum)).forEach((k, v) -> System.err.println(k + " : " + v));
在上面的代码中
Collectors.toMap(k -> k, k -> 1, Integer::sum)这一部分可能不好理解,对于这里面的三个参数,
第一个参数代表将arr中的每一个元素作为Map中的key,第二个参数代表每一个key所对应的value,
在这里每一个元素都对应个数1,第三个参数代表,如果存在相同的key,该如何进行合并,这里通过使用
Integer::sum,代表将具有相同key的元素进行合并时,其value进行相加,这样便实现了每个元素个数的统计。
源码:
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)
{
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
# 统计数组中前 k 个高频元素
public int[] topKFrequent(int[] nums, int k) {
return Arrays.stream(nums)
.boxed()
.collect(Collectors.toMap(e -> e, e -> 1, Integer::sum))
.entrySet()
.stream()
.sorted((m1, m2) -> m2.getValue() - m1.getValue())
.limit(k)
.mapToInt(Map.Entry::getKey)
.toArray();
}