一、Lambda表达式
1、简介
由编译器推断并帮你转换包装为一段可以传递的代码(将代码像数据一样进行传递)。
可以写出更简洁 、更灵活的代码。
作为 一种更紧凑的代码风格,使java的语言表达能力得到了提升。
2、lambda六大基础语法
- Java8中引入了一个新的操作符”->”,该操作符称为箭头操作符或lambda操作符
- 操作符左侧:lambda表达式的参数列表
- 操作符右侧:lambda表达式中所需执行的功能,即lambda体
使用lambda的前提:依赖于函数式接口,lambda表达式即对函数式接口的实现
/**
* 语法1:无参数,无返回值
*/
@Test
public void test() {
Runnable runnable1 = ()->{
System.out.println("lambda");
};
runnable1.run(); // 输出:lambda
}
/**
* 语法2:有一个 参数,无返回值
*/
@Test
public void test1() {
Consumer consumer = (x) -> {
System.out.println(x);
};
consumer.accept("哈哈哈哈"); // 输出:哈哈哈哈
}
/**
* 语法3:当只有一个参数时,可以省略括号
*/
@Test
public void test2() {
Consumer consumer = x -> {
System.out.println(x);
};
consumer.accept("哈哈哈哈"); // 输出:哈哈哈哈
}
/**
* 语法4:有两个及两个以上参数,有返回值,并且lambda体有多语句时
*/
@Test
public 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
*/
@Test
public 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编译器通过上下文推断数据类型
*/
@Test
public 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类型的参数,泛型使用String
public 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类型的参数,泛型使用String
public 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 ->
// 判断传入字符串长度是否大于5
k.length() > 5
);
System.out.println(pre); //false
}
}
<a name="jw61Z"></a>
#### 3.3.1 Predicate接口中的三个默认方法——and()
既然是条件判断,就会存在与、或、非三种常见的逻辑关系,其中将两个Predicate条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法and。
```java
public 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、引入
/**
* 传统方式
*/
@Test
public 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方式
*/
@Test
public 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中所指定的操作方案,已经有地方存在相同方案,那么是否还有必要再写重复逻辑。
-