1.java8重要的新特性
lambda表达式
函数式接口
接口中可以定义非抽象方法
Stream
1.Lambda表达式
Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。
很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。
之前的代码是这样的:
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("do something.");
}
}
现在的代码:
Runnable multiLine = () -> {// 3
System.out.println("Hello ");
System.out.println("World");
};
系统的学习Lambda表达式的语法:
expression = (variable) -> action
1.variable: 这是一个变量,一个占位符。像x,y,z,可以是多个变量;
2.action: 这里我称它为action, 这是我们实现的代码逻辑部分,它可以是一行代码也可以是一个代码片段。
lambda表达式可以包含多个参数,例如:
int sum = (x, y) -> x + y;
lambda表达式可以转换为函数式接口,比如比较器Comparator就是一个函数式接口,既可以继承他实现接口,也可以使用拉姆达表达式转换,BiFunction<T,U,R>也是函数式接口,
ArrayList中的removeIf也接收一个lambda表达式,
lambda表达式也可以转换为以一个方法引用,System.out::println;等价于 (x)-> System.out.println(x),::分割对象名类名和方法名。this,super也可以。
构造器引用,只是将方法名字换为new,比如Person::new.
lambda表达式可以捕获外部的变量,但是这个变量必须是最终的。
现在如果我们要自己编写一个方法处理Lambda表达式,那么这个方法参数必定有一个是函数式的接口,比如可能式Runnable,Suppiler<T>,Predicate<T>,BiFuniction<T>等等,他们的功能各不相同,也可以自己写函数式接口。
2.Predicate函数
首先说一下他的注解,可以看出Predicate上加上了注解@FunctionalInterface表明他是一个函数式接口,比如常见的还有一个 函数式接口Runnable
那么加上这个@FunctionalInterface注解有什么特点:
- 该注解只能标记在"有且仅有一个抽象方法"的接口上。
- JDK8接口中的静态方法和默认方法(static,default ),都不算是抽象方法。
- 接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法(equals).
-
该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
注解介绍完了以后就是Predicate函数的一个重要方法test(),他的作用如下
- 评估参数里面的表达式(说白了就是验证传进来的参数符不符合规则,后面有例子)
- 它的返回值是一个boolean类型(这点需要注意一下)。
实现一个函数式接口:判断一个人是否成年,在此之前我们的做法一般是编写一个 判断是否是成人的方法,是无法将 判断 共用的。而在本例只,你要做的是将 行为 (判断是否是成人,或者是判断是否大于30岁) 传递进去,函数式接口告诉你结果是什么。
创建一个函数式接口:
package com.wx.test1;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
写一个判断方法,将条件作为参数。
package com.wx.test1;
public class Test1 {
public static void main(String[] args) {
boolean isAdult = doPredicate(5, x -> x >= 18);
System.out.println(isAdult);
}
public static boolean doPredicate(int age, Predicate<Integer> predicate) {
return predicate.test(age);
}
}
其实就是把条件条件抽取出来作为参数传了进去,所以这个条件是可变的参数,随时可以改,好处就在这里。
3.java Stream
什么是Stream? 参考博客:https://mp.weixin.qq.com/s/8x16XgHZvqksJfMIi0um1g
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。Stream(流)是一个来自数据源的元素队列并支持聚合操作。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现
在 Java 8 中, 集合接口有两个方法来生成流:
-
stream() − 为集合创建串行流。
-
parallelStream() − 为集合创建并行流。
Stream的用法:
流的创建->流的处理->流的收集->流的分区分组->流的下游收集->约简操作
首先需要了解的是流的创建方式:
list.stream();
Arrays.stream()
Stream.of()
Stream.iterate()
Stream.generate()
Stream.empty()
看个案例:计算1+2+3.+..100的值
Integer integer1 = Stream
.iterate(1, integer -> integer + 1)
.limit(100)
.reduce((integer, integer2) -> integer + integer2)
.get();
其次需要掌握他的一些核心API,
1.Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
2.map 方法用于映射每个元素到对应的结果,
flatMap:ok如果对流中的每一个元素操作返回的又是一个流,那么可以使用flatMap来摊平这个流。
现在有这样的一个需求,就是要把集合中的元素都变成他的平方然后遍历输出。
List<Integer> list = Arrays.asList(2, 5, 4, 9, 6, 4, 5, 8, 4);
list.stream()
.map(i -> i*i)
.forEach(System.out::println);
如果要返回这个集合:
List<Integer> list = Arrays.asList(2, 5, 4, 9, 6, 4, 5, 8, 4);
List<Integer> collect = list.stream()
.map(i -> i * i)
.collect(Collectors.toList());
3.filter方法用于条件过滤,判断集合中空字符串的数量
List<String> stringList = Arrays.asList("wef", "fwef", "", "vdsv45", "434", " ", "435ghn");
long count = stringList.stream()
.filter(string -> (string.isEmpty())).count();
System.out.println(count);
4.limit 方法返回一个新的流,对于裁剪无限流的尺寸非常有用,用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:
skip(n)方法正好相反,他会丢掉前面的n个元素。这个在将文本分割为单词的时候会显得有用,因为第一个空格可以被跳过。
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
上面两个方法是抽取子流的方法,当然流可以拼接。使用Stream类的静态concat()方法,这里需要注意,第一个流不能是无限的流。
distinct方法是对流的去重。
5.sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:
总的来说排序可以接收两个参数,一个是Comparable类型的java bean ,一个是Compartor。
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
Stream.of("fsd", "sda", "s", "rewrwe").sorted(Comparator.comparing(String::length).reversed());
6.并行(parallel)程序
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
int count = strings.parallelStream().filter(string -> string.isEmpty()).count();
7.peek 流的操作都是中间的过程,如果结果不正确,那么如何调试呢?就是用peek,它产生一个新流,元素和原来的元素相同。
上面都是流的中间操作,接下来是流的终结操作。
有count,求和的reduce, 还有匹配的anyMatch,allMatch,还有firstFind,Min,Max等等,他们有的返回的是Optional对象。
有关Optional的博客:https://blog.csdn.net/weixin_37650458/article/details/98522607
收集结果
通常会有这几种操作,转换为数组,转化为集合,转化为字符串,求对象属性的和,求对象属性的最大值等等:
Object[] objects = Stream.iterate(0, e -> e + 1).limit(10).toArray();
Integer[] array = Stream.iterate(0, e -> e + 1).limit(10).toArray(Integer[]::new);
List<Integer> list2 = Stream.iterate(0, e -> e + 1).limit(10).collect(Collectors.toList());
TreeSet<Integer> collect2 = Stream.iterate(0, e -> e + 1).limit(10).collect(Collectors.toCollection(TreeSet::new));
String s1 = list.stream().collect(Collectors.joining(","));
IntSummaryStatistics summaryStatistics = list.stream().collect(Collectors.summarizingInt(String::length));
summaryStatistics.getAverage();
summaryStatistics.getCount();
summaryStatistics.getMax();
收集到映射表中
群组和分区
groupingBy()将具有相同特性的值群聚成组。
Map<String, List<Locale>> collect4 = Stream.of(Locale.getAvailableLocales()).collect(Collectors.groupingBy(Locale::getCountry));
下游收集器
上面groupingBy()将流分组得到一个Value为List类型,下游收集器可以对这个List进行操作提供的方法有counting(),summing(),maxBy(),mapping(),
Map<String, Long> collect5 = Stream.of(Locale.getAvailableLocales())
.collect(Collectors.groupingBy(Locale::getCountry, Collectors.counting()));
mapping()用法:
Map<String, Set<String>> collect6 = Stream.of(Locale.getAvailableLocales())
.collect(Collectors.groupingBy(Locale::getCountry, Collectors.mapping(Locale::getLanguage, Collectors.toSet())));
约简操作
reduce
4.练习
首先需要理解Lambda跟Stream配合可以做什么,不可以做什么,首先是操作一个数据集合的,数组也叫数据集合,那么可以做的操作就是对数据进行过滤,筛选,映射等等。
package com.wx1.test3;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* User: Mr.Wang
* Date: 2019/10/23
*/
public class Test1 {
public static void main(String[] args) {
//1.初识
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("传统方式");
}
}).start();
new Thread(() -> {
System.out.println("java8");
}).start();
//2.练习 Java开发者经常使用匿名类的另一个地方是为 Collections.sort() 定制 Comparator。
//3.使用Lambda表达式对列表进行迭代
List<String> list = Arrays.asList("apple", "orange", "banana");
list.stream().forEach(e -> {
System.out.println(e.toString());
});
//4.使用 java.util.function.Predicate 函数式接口以及lambda表达式,可以向API方法添加逻辑,
// 用更少的代码支持更多的动态行为。这个函数接口可以把条件传过去,也就是说将条件抽象出来为一个
// Lambda表达式,作为参数往函数中传,牛批了。
List<String> filter = filter(list, (e) -> true);
List<String> list1 = filter(list, (str) -> String.valueOf(str).startsWith("a"));
filter.stream().forEach((e) -> {
System.out.println(e);
});
//5.如何在lambda表达式中加入Predicate 上面的例子只是帮助我们理解Predicate,
//真实的用法可能会是下面这个样子,在Stream中做多个条件的筛选。
Predicate<String> condition1 = (e) -> String.valueOf(e).contains("a");
Predicate<String> condition2 = (e) -> String.valueOf(e).startsWith("b");
Predicate<String> and = condition1.and(condition2);
List<String> collect = list.stream().filter(condition1.and(condition2)).collect(Collectors.toList());
//6.Java 8中使用lambda表达式的Map和Reduce示例
//map,它允许你将对象进行转换,允许拿到对象的属性
list.stream().map((e) -> e + "map").collect(Collectors.toList());
list.stream().map(String::length).collect(Collectors.toList());
//7. reduce() 函数可以将所有值合并成一个。Map和Reduce操作是函数式编程的核心操作,
// 因为其功能,reduce 又被称为折叠操作。另外,reduce 并不是一个新的操作,
// 你有可能已经在使用它。SQL中类似 sum()、avg() 或者 count() 的聚集函数,实际上就是 reduce 操作,
// 因为它们接收多个值并返回一个值
List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
Optional<Double> reduce = costBeforeTax.stream().map(e -> e + e * 0.2).reduce((sum, cost) -> sum + cost);
Double reduce1 = costBeforeTax.stream().map(e -> e + e * 0.2).reduce(8.0, (integer, aDouble) -> (integer + aDouble));
Double aDouble = reduce.get();
//8.对列表的每个元素应用函数
//逐一乘以某个数、除以某个数或者做其它操作。这些操作都很适合用 map() 方法,
//可以将转换逻辑以lambda表达式的形式放在 map() 方法里,就可以对集合的各个元素进行转换了
//key要是原来的数,value是映射的数
Map<Boolean, List<Integer>> collect1 = costBeforeTax.stream().map((e) -> e + 20)
.collect(Collectors.groupingBy((e) -> Integer.valueOf(e.toString()) >= 200 && Integer.valueOf(e.toString()) <= 400));
}
private static List<String> filter(List<String> list, Predicate predicate) {
List<String> re = new ArrayList<>();
list.stream().forEach(e -> {
if (predicate.test(e)) {
re.add(e);
}
});
return re;
}
}
假设需要从一个字符串列表中选出以数字开头的字符串并输出,Java 7之前需要这样写:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
for(String str : list){
if(Character.isDigit(str.charAt(0))){
System.out.println(str);
}
}
而Java 8就可以这样写:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
list.stream()// 1.得到容器的Steam
.filter(str -> Character.isDigit(str.charAt(0)))// 2.选出以数字开头的字符串
.forEach(str -> System.out.println(str));// 3.输出字符串
上述代码首先1. 调用List.stream()
方法得到容器的Stream,2. 然后调用filter()
方法过滤出以数字开头的字符串,3. 最后调用forEach()
方法输出结果。
使用Stream有两个明显的好处:
- 减少了模板代码,只用Lambda表达式指明所需操作,代码语义更加明确、便于阅读。
- 将外部迭代改成了Stream的内部迭代,方便了JVM本身对迭代过程做优化(比如可以并行迭代)。
假设需要从一个字符串列表中,选出所有不以数字开头的字符串,将其转换成大写形式,并把结果放到新的集合当中。Java 8书写的代码如下:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
Set<String> newList =
list.stream()// 1.得到容器的Stream
.filter(str -> !Character.isDigit(str.charAt(0)))// 2.选出不以数字开头的字符串
.map(String::toUpperCase)// 3.转换成大写形式
.collect(Collectors.toSet());// 4.生成结果集
上述代码首先1. 调用List.stream()
方法得到容器的Stream,2. 然后调用filter()
方法选出不以数字开头的字符串,3. 之后调用map()
方法将字符串转换成大写形式,4. 最后调用collect()
方法将结果转换成Set
。这个例子还向我们展示了方法引用(method references
,代码中标号3处)以及收集器(Collector
,代码中标号4处)的用法,这里不再展开说明。
通过这个例子我们看到了Stream链式操作,即多个操作可以连成一串。不用担心这会导致对容器的多次迭代,因为不是每个Stream的操作都会立即执行。Stream的操作分成两类,一类是中间操作(intermediate operations
),另一类是结束操作(terminal operation
),只有结束操作才会导致真正的代码执行,中间操作只会做一些标记,表示需要对Stream进行某种操作。这意味着可以在Stream上通过关联多种操作,但最终只需要一次迭代。如果你熟悉Spark RDD,对此应该并不陌生。
5.Stream中自定义排序
比如在一个集合中,集合装的是学生的对象,需要按照学生的对象的属性年龄来进行升序或者降序排序。
使用stream().sorted()进行排序,需要该类实现 Comparable 接口,写一个比较的方法
package com.wx.test1;
import java.time.LocalDate;
import java.util.List;
public class StudentInfo implements Comparable<StudentInfo> {
//名称
private String name;
//性别 true男 false女
private Boolean gender;
//年龄
private Integer age;
//身高
private Double height;
//出生日期
private LocalDate birthday;
public StudentInfo(String name, Boolean gender, Integer age, Double height, LocalDate birthday) {
this.name = name;
this.gender = gender;
this.age = age;
this.height = height;
this.birthday = birthday;
}
@Override
public int compareTo(StudentInfo ob) {
return this.age.compareTo(ob.getAge());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getGender() {
return gender;
}
public void setGender(Boolean gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
public LocalDate getBirthday() {
return birthday;
}
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
}
添加数据,按照年龄升序排列:
package com.wx.test1;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class Test3 {
public static void main(String[] args) {
//测试数据,请不要纠结数据的严谨性
List<StudentInfo> studentList = new ArrayList<>();
studentList.add(new StudentInfo("李小明",true,18,1.76,LocalDate.of(2001,3,23)));
studentList.add(new StudentInfo("张小丽",false,18,1.61,LocalDate.of(2001,6,3)));
studentList.add(new StudentInfo("王大朋",true,19,1.82,LocalDate.of(2000,3,11)));
studentList.add(new StudentInfo("陈小跑",false,17,1.67,LocalDate.of(2002,10,18)));
/**按年龄升序排序*/
studentList.stream()
.sorted(Comparator.comparing(StudentInfo::getAge)).collect(Collectors.toList()).forEach(studentinfon->System.out.println(studentinfon.getAge()));
}
}
测试结果:
如果我想输出整个对象的信息呢?,重写toString方法:
public String toString() {
String info = String.format("%s\t\t%s\t\t%s\t\t\t%s\t\t%s", this.name, this.gender.toString(), this.age.toString(), this.height.toString(), birthday.toString());
return info;
}
添加reversed(),按年龄降序排序:
studentList.stream()
.sorted(Comparator.comparing(StudentInfo::getAge).reversed())
.collect(Collectors.toList())
.forEach(studentinfon->System.out.println(studentinfon.toString()));
使用年龄进行降序排序,年龄相同再使用身高升序排序
studentList.stream()
.sorted(Comparator.comparing(StudentInfo::getAge).reversed().thenComparing(StudentInfo::getHeight))
.collect(Collectors.toList())
.forEach(studentinfon->System.out.println(studentinfon.toString()));
6.Stream中的分组使用
例子:
随机生成50个小于100的整数,放入List中,将List中的数据除以10,以结果的整数值作为key放入Map中,得到 如{1=>[11,10,12],2=>[21,24,23]}的Map,再将Map中key对应的数组进行排序,得到如{1=> [10,11,12],2=>[21,23,24]}。排序不能使用List.sort() 方法。必须自己写排序方法。
要求:
使用工厂模式,创建一个接口类和两个实现类(2分)
创建一个工厂,生成基于给定信息的实体类的对象。(1分)
在main 函数中,通过上面的工厂获取到唯一的类。(1分)
两个实现类分别使用java 8 的Stream 和其他方式(4分)
实现功能 (4)
分析:按照要求需要创建一个接口,这个接口是专门处理数据类的接口,两个实现类分别完成如下工作,第一个实现类完成数据的映射和分组,第二个实现类完成数据的自定义排序。
ok,首先我们完成一个基本的数据处理接口
package com.wx.test;
import java.util.Map;
public interface BaseInterface<T> {
Map doData(T collection);
}
实现这个接口,第一个类完成数据的映射,这里这是数据,如果是对象可以根据对象的属性来分组和排序,使用::就可以取到对象的属性了。
package com.wx.test;
import java.util.*;
import java.util.stream.Collectors;
public class MappingDate implements BaseInterface<List> {
@Override
public Map doData(List collection) {
List<Integer> list = (List) collection;
Map<Integer, List<Integer>> collect1 = list.stream()
.collect(Collectors.groupingBy(o -> (Integer) (o / 10)));
//但是要求需要将List<Integer>转化为数组
Map<Integer, Integer[]> collect2 = new HashMap<Integer, Integer[]>();
for (Integer integer : collect1.keySet()) {
List<Integer> list1 = collect1.get(integer);
Integer[] array = list1.stream().toArray(Integer[]::new);
collect2.put(integer, array);
}
return collect2;
}
}
实现第二个类完成对数据的排序
package com.wx.test;
import java.util.Map;
public class SortDate implements BaseInterface<Map>{
@Override
public Map doData(Map collection) {
//要求使用Stream,那又得转换为List,所以这里就不用了,直接对数组进行排序
Map<Integer, Integer[]> collection1 = (Map<Integer, Integer[]>) collection;
for (Integer integer : collection1.keySet()) {
collection1.put(integer, sort(collection1.get(integer)));
}
return collection1;
}
//排序方法,希尔排序,是插入排序的一种
private static Integer[] sort(Integer[] array) {
int len = array.length;
int temp;
//设置增量,增量是数据长度的一半依次减一减到一
for (int k = len / 2; k > 0; k--) {
//将依据增量分割的序列进行对比
for (int i = k; i < len; i++) {
if (array[i - k] > array[i]) {
//交换数据
temp = array[i - k];
array[i - k] = array[i];
array[i] = temp;
}
}
}
return array;
}
}
实现一个工厂类:
package com.wx.test;
public class FactoryMethod {
public <T extends BaseInterface> T getProuect(Class<T> c) {
BaseInterface product = null;
try {
product = (T) Class.forName(c.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) product;
}
}
好了,我们再来写main函数:
package com.wx.test;
import java.util.*;
public class TestOne {
//接口类是对数据的处理接口
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
Random random = new Random();
for (int i = 0; i < 50; i++) {
//int num = random.nextInt(50) + 50;//生成范围为50到100的数
int num = random.nextInt(100);
list.add(num);
}
FactoryMethod factoryMethod=new FactoryMethod();
MappingDate mapdata = factoryMethod.getProuect(MappingDate.class);
//数据的映射,转化为Map<Integer, Integer[]>
Map<Integer, Integer[]> map = mapdata.doData(list);
//自定义排序
SortDate sortDate = factoryMethod.getProuect(SortDate.class);
Map<Integer, Integer[]> map1 = sortDate.doData(map);
for (Integer integer : map1.keySet()) {
System.out.println(integer);
/* Arrays.asList(map1.get(integer)).stream()
.forEach(System.out::print);*/
Integer[] integers = map1.get(integer);
for (Integer integer1 : integers) {
System.out.print(integer1+",");
}
System.out.println("");
}
}
}
结果:
参考博客:
https://www.runoob.com/java/java8-streams.html
https://blog.csdn.net/zjy15203167987/article/details/88246482
https://www.cnblogs.com/kexianting/p/8588987.html
https://www.cnblogs.com/xisuo/p/9705944.html
https://www.cnblogs.com/CarpenterLee/p/5936664.html