小序
第一次学java 就是用的jdk8,但在当时并不知道8的新特性。前半年学习过一次,但也就是过一遍,这次再来学习记录一下。
lambda 表达式
lambda 表达式可以省去不部分代码,并且让我们自己的代码看起来简洁。方便了开发。
具体例子:
//未使用 lambda
Comparator<Integer> c1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
//使用 lambda 表达式
//lambda 表达式可以省去部分代码,让我们的代码看起来更简洁
Comparator<Integer> c2 = (o1, o2) -> Integer.compare(o1,o2);
//方法引用
Comparator<Integer> c3 = Integer::compare;
但需要注意的是,只有当接口有一个抽象类方法的时候,才可以使用lambda表达式
lambda 表达式会进行类型推断。如例子中 o1 ,o2因为 前边Comparator 已经定义了范型 Integer 所以这里的 o1,o2就是Integer 类型
当只有一个参数时,()可以省略
当只有一条语句时(可能是return语句)的时候{}和 return可以省略
函数式接口
在 java8里新加了函数式接口的概念。函数式接口是什么呢?
如果一个接口只有一个抽象方法,那么这个接口就可以称之为函数式接口
通常会用 @FunctionalInterface 来标识
我们同样可以自定义函数式接口
@FunctionalInterface
public interface MyFunctionInterface {
void method();
}
@FunctionalInterface 会帮我们检测我们的接口是否符合 函数式接口的定义
lambda 表达式依赖于函数式接口,其本质就是作为 函数式接口的实例
java 内置的函数式接口
Consumer 消费型接口
参数类型为 T :接收一个T类型的值,没有返回值
包含方法 void accept (T t)
🌰:
public void testConsumer(){
//java8 之前
happyTime(500.0, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("看电影和吃饭消费了" + aDouble);
}
});
//lambda 表达式
happyTime(500.0,money -> System.out.println("看电影和吃饭消费了" + money));
}
public void happyTime(Double money, Consumer<Double> con){
con.accept(money);
}
Supplier 供给型接口
没有参数,返回一个T类型的值
包含方法 T get()
//lambda 表达式
Supplier<String> sup = () -> employee.getName();
System.out.println(sup.get());
Function 函数型接口
参数类型为T,返回类型为R:接收一个 T类型的值进行操作,但返回结果为R类型
包含方法 R apply(T t)
Predicate 断定型接口
进行判断的,接受参数为T的类型,返回一个boolean类型的值
包含方法 boolean test(T t)
举个🌰:
public void testPredicate() {
List<String> list = Arrays.asList("北京","南京","天津");
//过滤出带有 京 的字符串
List<String> filterString = filterString(list, string -> string.contains("京"));
System.out.println(filterString);
}
public List<String> filterString(List<String> strings, Predicate<String> pre) {
ArrayList<String> list = new ArrayList<>();
for (String s : strings) {
if(pre.test(s)) {
list.add(s);
}
}
return list;
}
方法引用
当要传递给lambda 体的操作已经有实现的方法时,就可以使用方法引用
//lambda 表达式
Consumer<String> con = (s) -> System.out.println(s);
con.accept("halo");
//方法引用的方式
Consumer<String> con1 = System.out::println;
con1.accept("halo");
可以看到 Consumer的 accpet方法的接收参数是 字符串类型 s,而且System.out.println也可以接收 字符串 s,这个时候就可以使用方法引用 类名(对象名)::方法名
方法引用 是对 lambda 表达式的进一步简化操作。
我觉得可以简单这样理解: 你的参数和返回值 和你 要引用的参数和返回值一致,就可以使用方法引用。
- 对象名::非静态方法
//lambda 表达式
Supplier<String> sup = () -> employee.getName();
System.out.println(sup.get());
//方法引用
Supplier<String> sup1 = employee::getName;
System.out.println(sup1.get());
- 类名::静态方法
Comparator<Integer> comparator1 = (o1, o2) -> Integer.compare(o1, o2);
System.out.println(comparator1.compare(1,2));
Comparator<Integer> comparator2 = Integer::compare;
System.out.println(comparator2.compare(1,2));
- 类名::非静态方法
这种使用就比较特殊了,它并不符合 参数和返回值相同就可以使用 方法引用 的原则。
Comparator<String> com = (s1, s2) -> s1.compareTo(s2);
com.compare("abc","abd");
Comparator<String> com2 = String::compareTo;
com2.compare("abc","abd");
第一个参数作为了方法调用者出现, s1.compareTo(s2),这个时候也是可以 方法引用的。
Stream API
java8 另一个重要的改变就是 Stream API,它给我们提供了对集合数据的操作,有点类似于 sql 的查询操作。当 数据库使用 nosql 的时候,岂不是能出现神奇的效果。
相比于Collection在内存中的存储,stream 更多的是是对数据的操作。
Stream 有三个步骤
创建 -> 中间操作 -> 终止操作
- 创建:通过 集合,数组,获取一个 stream对象
- 中间操作:一个中间操作链,对数据执行完操作后返回当前这个 stream对象,是链式的。中间操作在没有执行 终止操作 前是不会 执行的。
- 终止操作:一旦执行,就会产生结果,之后这个stream 不能再被使用
Stream 的创建
这个就比较简单了,java8在 Collection 接口中添加了 stream的默认方法
List<String> strings = new ArrayList<>();
strings.add("hello");
strings.add("java8");
strings.add("nihao");
strings.add("a");
//返回一个顺序流
Stream<String> stream = strings.stream();
//返回一个并行流
Stream<String> stringStream = strings.parallelStream();
当然你也可以通过数组来获取,而且 Stream中也有一个of()的静态方法来获取 stream 对象
Stream<String> stringStream1 = Stream.of("123", "456", "789");
Stream 的中间操作
筛选与切片
- filter(Predicate p) 从流中排出某些元素
- limit(n) 截断流,使流中元素个数不超过n
- skip(n) 跳过元素,返回一个扔掉前n个的流,若流中元素个数不足n个,则返回一个空流
- distinct() 筛选去除重复元素
List<String> strings = new ArrayList<>();
strings.add("hello");
strings.add("java8");
strings.add("nihao");
strings.add("a");
strings.add("nihao");
//去除a这个元素
strings.stream()
.filter((s -> !s.equals("a")))
.forEach(System.out::println);
System.out.println();
//只要前两个元素
strings.stream()
.limit(2)
.forEach(System.out::println);
System.out.println();
//去除前两个元素
strings.stream()
.skip(2)
.forEach(System.out::println);
System.out.println();
//去除重复元素 nihao
strings.stream()
.distinct()
.forEach(System.out::println);
映射
map
map(Function f) 接收一个一个函数作为参数(lambda表达式),该函数会被作用到每一个元素上,并将其映射成一个新的元素.(也就是 函数型接口)
将集合中所有的字符串变为大写,并过滤出长度大于2的字符串
List<String> stringStream = Arrays.asList("aa", "bb", "cc", "ddd", "eeee");
stringStream.stream()
.map((s) -> s.toUpperCase())
.filter(s -> s.length() > 2)
.forEach(System.out::println);
flatMap
flatMap(Function f)接收一个函数作为参数,将流中的每一个元素都变成另一种类型单独的流,再将所有的流合成一个流
这个方法有点难度。
可以举个例子看一下,还是上边的 stringStream
public static void main(String[] args) {
//可以看一下返回类型是 Stream<Stream<Character>> 这种套娃型的
Stream<Stream<Character>> streamStream = stringStream.map(Test::fromStringToStream);
//如果这时要进行元素便利的话,需要进行双层循环
streamStream.forEach(characterStream -> {
characterStream.forEach(System.out::println);
});
}
public static Stream<Character> fromStringToStream(String str) {
List<Character> list = new ArrayList<>();
for (Character c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
Stream< Stream< Character >>这种套娃型的stream 最好使用 flatMap,它会自动帮你把这些里边的全部的Stream< Character >打开变成一个 Stream< Character >
使用flatMap后就变成了:
stringStream.stream()
.flatMap(Test::fromStringToStream)
.forEach(System.out::println);
看起来简洁的许多。
stream 的终止操作
匹配和分类
- allMatch(Predicate p) 接收一个断定型接口,判断是否所有元素符合条件,符合则返回true
- anyMatch(Predicate p) 判断是否有一个元素符合条件,如果符合,返回true
- noneMatch(Predicate p) 检查是否没有匹配的元素,如果有匹配上的,返回false
- findFirst() 获取第一个元素
- findAny() 返回流中任意一个元素
- count() 获取流中元素的个数
- max(Comparator com)
- min(Comparator com)
- forEach(Consumer con)
归约
- reduce(T identity, BinaryOperator) 将流中的元素反复结合起来,得到一个值, identity 是初始值。
//reduce(T identity, BinaryOperator) 将流中的元素反复结合起来,得到一个值, identity 是初始值。
//1 + 2 的结果再作为第一个值,再将第三个元素3作为第二值,循环往复
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = integers.stream().reduce(0, Integer::sum);
System.out.println(sum);
1 + 2 的结果再作为第一个值,再将第三个元素3作为第二值,两次再次相加的结果作为第一值,循环往复。
当然如果你不是设置identity的值的话,reduce(BinaryOperator)还是有这个只有一个形参的方法,只不过返回的是一个optoinal<>的值
收集
collet(Collector c) 将流中的结果转换成一个Collector(集合)出来
//获取元素长度大于2的集合
List<String> collect = stringStream.stream()
.filter(s -> s.length() > 2)
.collect(Collectors.toList());