一、Lambda表达式
1、简介
由编译器推断并帮你转换包装为一段可以传递的代码(将代码像数据一样进行传递)。
可以写出更简洁 、更灵活的代码。
作为 一种更紧凑的代码风格,使java的语言表达能力得到了提升。
2、lambda六大基础语法
- Java8中引入了一个新的操作符”->”,该操作符称为箭头操作符或lambda操作符
- 操作符左侧:lambda表达式的参数列表
- 操作符右侧:lambda表达式中所需执行的功能,即lambda体
使用lambda的前提:依赖于函数式接口,lambda表达式即对函数式接口的实现
/*** 语法1:无参数,无返回值*/@Testpublic void test() {Runnable runnable1 = ()->{System.out.println("lambda");};runnable1.run(); // 输出:lambda}/*** 语法2:有一个 参数,无返回值*/@Testpublic void test1() {Consumer consumer = (x) -> {System.out.println(x);};consumer.accept("哈哈哈哈"); // 输出:哈哈哈哈}/*** 语法3:当只有一个参数时,可以省略括号*/@Testpublic void test2() {Consumer consumer = x -> {System.out.println(x);};consumer.accept("哈哈哈哈"); // 输出:哈哈哈哈}/*** 语法4:有两个及两个以上参数,有返回值,并且lambda体有多语句时*/@Testpublic void test3() {Comparator<Integer> comparator = (x, y) -> {System.out.println(x+"------"+y);return Integer.compare(x,y);};int compare = comparator.compare(1, 2);System.out.println(compare); // 输出:-1}/*** 语法5:当lambda体只有一条语句时,可以省略大括号 和 return*/@Testpublic void test4() {Comparator<Integer> comparator = (x, y) -> Integer.compare(x,y);int compare = comparator.compare(2, 1);System.out.println(compare); // 输出:1}/*** 语法6:类型推断(编译器上下文推断)* lambda表示的参数列表的数据类型可以省略不写,JVM编译器通过上下文推断数据类型*/@Testpublic void test39() {Comparator<Integer> comparator = (x, y) -> Integer.compare(x,y);int compare = comparator.compare(2, 1);System.out.println(compare); // 输出:1}
3、四大内置核心函数式接口
3.1 Supplier接口
java.util.function.Supplier()接口仅包含一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的lambda表达式需要“对外提供”一个符合泛型类型的对象数据
public class SupplierDemo {// 定义一个方法,方法的参数传递Supplier,泛型指定String类型,所以get()方法会返回一个String类型的数据public static void getString(Supplier<String> supplier) {String s = supplier.get();System.out.println(s);}public static void main(String[] args) {getString(() -> "韩广凯"); // 韩广凯}}
3.2 Consumer接口
public class ConsumerDemo {// Consumer是一个消费型接口,泛型指定什么类型,accept()就可以消费什么类型的数据// 至于怎么消费(使用),则需要自定义public static void consumerTest(String str,Consumer<String> consumer) {consumer.accept(str);}public static void main(String[] args) {consumerTest("周杰伦",k -> System.out.println(k)); // 周杰伦}}
3.2.1 默认方法:andThen()
如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合,而这个方法就是Consumer接口中的default方法andThen(),下面是JDK源码:
default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}
说明:andThen()需要要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费。
public class ConsumerTestAndThen {// 定义一个方法,传递一个String类型的参数,再传递两个Consumer类型的参数,泛型使用Stringpublic static void test(String str, Consumer<String> consumer,Consumer<String> consumer1) {consumer.accept(str);consumer1.accept(str);}public static void main(String[] args) {test("周杰伦",k -> System.out.println(k+"方文山"),v -> System.out.println(v+"昆凌") );// 输出:// 周杰伦方文山// 周杰伦昆凌}}
public class ConsumerTestAndThen {// 定义一个方法,传递一个String类型的参数,再传递两个Consumer类型的参数,泛型使用Stringpublic static void test(String str, Consumer<String> consumer,Consumer<String> consumer1) {// 使用andThen()方法,把两个Consumer接口连在一起,再消费数据consumer.andThen(consumer1).accept(str);}public static void main(String[] args) {test("周杰伦",k -> System.out.println(k+"方文山"),v -> System.out.println(v+"昆凌") );// 输出:// 周杰伦方文山// 周杰伦昆凌}}
3.3 Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个 boolean值结果,这时可以使用java.util.function.Predicate
接口。
Predicate接口中包含一个抽象方法:boolean test(T t)。用于条件判断的场景。 ```java public class PredicateDemo {public static boolean pre(String str,Predicate
predicate) { return predicate.test(str);
}
public static void main(String[] args) {
// 定义一个字符串String str = "abcd";boolean pre = pre(str, k ->// 判断传入字符串长度是否大于5k.length() > 5);System.out.println(pre); //false
}
}
<a name="jw61Z"></a>#### 3.3.1 Predicate接口中的三个默认方法——and()既然是条件判断,就会存在与、或、非三种常见的逻辑关系,其中将两个Predicate条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法and。```javapublic class PredicateDemo_and {/*** 定义一个方法* 传递两个参数* 传递一个String类型的参数* 传递两个Predicate* 第一个Predicate判断字符串长度大于2* 第二个Predicate判断字符串包含“H”* 两个条件必须同时满足*/public static boolean checkString(String str, Predicate<String> predicate,Predicate<String> predicate2) {return predicate.test(str) && predicate2.test(str);}// 测试public static void main(String[] args) {boolean b = checkString("HGK", k -> k.length() > 2, k -> k.contains("H"));System.out.println(b);}}
public class PredicateDemo_and {/*** 定义一个方法* 传递两个参数* 传递一个String类型的参数* 传递两个Predicate* 第一个Predicate判断字符串长度大于2* 第二个Predicate判断字符串包含“H”* 两个条件必须同时满足*/public static boolean checkString(String str, Predicate<String> predicate,Predicate<String> predicate2) {return predicate.and(predicate2).test(str);}// 测试public static void main(String[] args) {boolean b = checkString("HGK", k -> k.length() > 2, k -> k.contains("H"));System.out.println(b);}}
3.3.2 Predicate接口中的三个默认方法——or()
public class PredicateDemo_or {/*** 定义一个方法* 传递两个参数* 传递一个String类型的参数* 传递两个Predicate* 第一个Predicate判断字符串长度大于5* 第二个Predicate判断字符串包含“H”* 两个条件满足一个即可*/public static boolean checkString(String str, Predicate<String> predicate,Predicate<String> predicate2) {return predicate.or(predicate2).test(str);}// 测试public static void main(String[] args) {boolean b = checkString("HGK", k -> k.length() > 5, k -> k.contains("H"));System.out.println(b);}}
3.3.3 Predicate接口中的三个默认方法——negate():取反
public class Predicate_negate {/*** 字符串大于5,返回false否则返回true* @param str* @param predicate* @param* @return*/public static boolean checkString(String str, Predicate<String> predicate) {return predicate.negate().test(str);}// 测试public static void main(String[] args) {boolean b = checkString("HGK", k -> k.length() > 5);System.out.println(b);}}
3.4、Function接口
java.util.function.Function
3.4.1 抽象方法:apply
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
使用场景例如:将String类型转换为Integer类型
public class FunctionDemo {/*** 定义一个方法* 方法的参数传递一个字符串类型的整数* 方法的参数传递一个Function接口,泛型使用<String,Integer>* 使用Function接口中的方法apply(),把字符串类型的整数,转换为Integer类型的整数*/public static void change(String s, Function<String,Integer> function) {Integer apply = function.apply(s);System.out.println(apply);}public static void main(String[] args) {change("123",k -> Integer.parseInt(k)); // 123}}
3.4.2 Function接口中的默认方法——andThen()
用来进行组合操作,该方法同样用于“先做什么,再做什么”的场景,和Consumer中的andThen差不多。
public class FunctionDemo_andThen {/*** 把String类型“123”,转换成Integer类型,把转换后的结果加10* 把增加后的Integer类型的数据,转换为String类型*/public static void change(String str, Function<String, Integer> function,Function<Integer,String> function2) {String apply = function.andThen(function2).apply(str);System.out.println(apply);}public static void main(String[] args) {change("10",k -> Integer.parseInt(k)+10,k -> k.toString());}}
4、方法引用、构造器引用的使用
若lambda体中的内容有方法已经实现了,我们可以使用方法引用
可以理解为方法引用是lambda表达式的另外一种表现形式
对象名::实例方法名
类::静态方法名
类::实例方法名
5、lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费,而lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
lambda使用前提:必须存在函数式接口
6、使用lambda作为参数和返回值
如果抛开实现原理不说,java中的lambda表达式可以被当做是匿名内部类的替代品,如果方法的参数是一个函数式接口类型,那么就可以使用lambda表达式进行 替代,使用lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数
二、stream
Stream是java8中处理集合的关键抽象概念,他可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询,也可以使用Stream API来并行执行操作,
简而言之,StreamAPI提供了一种高效且易于使用的处理数据的方式。
1、引入
/*** 传统方式*/@Testpublic void test() {List<String> list = Arrays.asList("张强", "周芷若", "张三丰", "张无忌", "周杰伦");// 1.过滤,只要以张开头的集合ArrayList<String> list1 = new ArrayList<>();for (String s : list) {if (s.startsWith("张")) {list1.add(s);}}// 2.过滤,要名字长度为3的集合ArrayList<String> list2 = new ArrayList<>();for (String s : list1) {if (s.length() == 3) {list2.add(s);}}// 3.遍历集合Iterator<String> iterator = list2.iterator();while (iterator.hasNext()) {String next = iterator.next();System.out.println(next);}}/*** 使用stream方式*/@Testpublic void test1() {List<String> list = Arrays.asList("张强", "周芷若", "张三丰", "张无忌", "周杰伦");list.stream().filter(name -> name.startsWith("张")).filter(name -> name.length() == 3).forEach(System.out::println);}
2、流式思想概述
Stream(流)是一个来自数据源的元素队列
- 元素是特定类型的对象,形成一个对类,Java中的Stream并不会存储元素,而是按需计算
- 数据源的来源,可以是集合、数组等
和以前的Collection操作不同,Stream操作还有两个基础的特征:
- Pipelining:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格(fluentstyle)。这样做可以对操作进行优化,比如延迟执行和短路。
- 内部迭代:以前对集合遍历都是通过iterator或者增强for的方式 。显示的在集合外部进行迭代,这叫做外部迭代,Stream提供了内部迭代的方式,流可以直接调用遍历的方法。
当使用一个流的时候,通常包括三个基本步骤:获取一个数据源——>数据转换——>执行操作获取想要的结果,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以项链条一样排列,形成一个管道。
2.1 获取流(获取Stream流的两种方式)
java.util.stream.Stream
获取一个流非常简单,有以下几种常用的方式:
- 所有的Collection集合都可以通过stream默认方式获取流
stream接口的静态方法of()可以获取数组对应流
public class StreamDemo1 {public static void main(String[] args) {/*** 把集合转换成stream流*/ArrayList<String> list = new ArrayList<>();Stream<String> stream = list.stream();Set<String> set = new HashSet<>();Stream<String> stream1 = set.stream();Map<Integer,String> map = new HashMap<>();// 获取键,存到一个set中Set<Integer> keySet = map.keySet();Stream<Integer> stream2 = keySet.stream();// 获取值,存储到Collection集合中Collection<String> valuesKey = map.values();Stream<String> stream3 = valuesKey.stream();// 获取键值对,即键与值的映射关系Set<Map.Entry<Integer, String>> entries = map.entrySet();Stream<Map.Entry<Integer, String>> stream4 = entries.stream();/*** 把数组转换成stream流*/Stream<Integer> stream5 = Stream.of(1,2,3,4,5);// 可变参数可以传递数组Integer[] arr = {6,3,4,5,6,6};Stream<Integer> stream6 = Stream.of(arr);}}
3、Stream()流中的常用方法
流模型的操作很丰富,这里介绍一些常用API,这些方法可以被分为两种:
延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用(除了终结方法外 ,其余方法均为延迟方法)
终结方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。Stream的操作三个步骤
- 创建Stream
- 一个数据源(如:集合,数组),获取一个流
- 中间操作
- 一个中间操作链,对数据源的数据进行处理
终止操作(终端操作)
-
简介:
可以通过Collection系列集合提供的stream()或parallelStream()获取流(第一个获取的是串行流,第二个获取的是并行流)
1.逐一处理:forEach
虽然方法名字叫forEach,但是与for循环中的“for-each”昵称不同
void forEach(Consumer<? super T> action);
该方法接收一个Consumer接口函数,会将每一个 流元素交给该函数进行处理
public class StreamForEach {/*** forEach()* void forEach(Consumer<? super T> action);* 该方法接收一个Consumer接口函数,会将每一个 流元素交给该函数进行处理* 简单记:* forEach方法,用来遍历流中的数据* 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法*/public static void main(String[] args) {Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);stream.forEach(k -> System.out.println(k));}}
2、过滤方法:filter
可以通过filter方法将一个流转换成另一个子集流。方法签名:
Stream<T> filter(Predicate<? super T> predicate)
该接口接收一个Predicate函数式接口参数(可以是一个Lambda或方法引用)作为 筛选条件。
该方法将会产生一个boolean值结果,代表指定的条件是否满足,如果结果为true,那么Stream流的filter方法将会留用元素,如果为false,那么filter方法将会舍弃元素。public class StreamFilter {public static void main(String[] args) {Stream<String> stream = Stream.of("周杰伦", "周芷若", "周杰", "方文山");Stream<String> stream1 = stream.filter(name -> name.startsWith("周"));stream1.forEach(name -> System.out.println(name));}}
3、映射:map
如果需要将流中的云宿舍映射到另一个流中,可以使用map方法,方法签名:
<R> Stream<R> map(Function<? super T,? extends R> mapper);
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
public class StreamMap {public static void main(String[] args) {Stream<String> stream = Stream.of("1", "2", "3", "4");stream.map(str -> Integer.parseInt(str) + 1).forEach(System.out::println);}}
4、统计个数:count
正如旧集合Collection当中的size方法一样,流提供count方法来数一数其中的元素个数
long count();
该方法返回一个long值,代表元素个数(不再像旧集合那样是Int值)
public class StreamCount {public static void main(String[] args) {Stream<String> stream = Stream.of("1", "2", "3", "4");long count = stream.count();System.out.println(count); // 4}}
5、取用前几个:limit
limit()方法可以对流进行截取,只取用前n个,方法签名:
Stream<T> limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取,否则不进行操作。
public class StreamLimit {public static void main(String[] args) {Stream<String> stream = Stream.of("1", "2", "3", "4");stream.limit(2).forEach(System.out::println);}}
6、跳过前几个:skip
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:
Stream<T> skip(long n);
参数是一个long型,如果流的长度大于n,则跳过前n个,否则将会得到一个长度为0的空流。
public class StreamSkip {public static void main(String[] args) {Stream<String> stream = Stream.of("1", "2", "3", "4");stream.skip(2).forEach(System.out::println);}}
7、组合 :concat
如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat()
static <T> Stream<T> concat(Stream<? extends T> a,Stream<? extends T> b);
备注:这是一个 静态方法,与java.lang.String当中的concat方法是不同的
public class StreamConcat {public static void main(String[] args) {Stream<String> stream = Stream.of("1", "2", "3", "4");Stream<String> stream1 = Stream.of("周杰伦", "方文山", "昆凌");Stream.concat(stream, stream1).forEach(System.out::println);// 1// 2// 3// 4// 周杰伦// 方文山// 昆凌}}
三、方法引用
在使用Lambda表达式的时候,我们实际上传递进行的代码就是一种解决方案:拿什么参数做什么操作,那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那么是否还有必要再写重复逻辑。
-
