演变过程 & 初体验
当前有一个List 里面包含公司的员工信息,
需求一:求 当前公司中年龄大于35岁的员工
需求二:求 当前公司中工资大于5000的员工
List<Employee> list = Arrays.asList(
// 姓名 工资 年龄
new Employee("张三", 3888.95, 25),
new Employee("李四", 7000.00, 45),
new Employee("王五", 6988.95, 40),
new Employee("赵六", 2888.85, 25),
new Employee("Wang", 5888.95, 35));
普通做法 :
需求一: 求 当前公司中年龄大于35岁的员工
public static void test3() {
List<Employee> filterEmployee = filterEmployee(list);
for (Employee item : filterEmployee) {
System.out.println(item);
}
}
public static List<Employee> filterEmployee(List<Employee> list) {
ArrayList<Employee> arrayList = new ArrayList<Employee>();
for (Employee item : list) {
if (item.getAge() > 35) {
arrayList.add(item);
}
}
return arrayList;
}
需求二:求 当前公司中工资大于5000的员工
public static void test3() {
List<Employee> filterEmployee = filterEmployee2(list);
for (Employee item : filterEmployee) {
System.out.println(item);
}
}
public static List<Employee> filterEmployee2(List<Employee> list) {
ArrayList<Employee> arrayList = new ArrayList<Employee>();
for (Employee item : list) {
if (item.getSalary() > 5000) {
arrayList.add(item);
}
}
return arrayList;
}
这种做法写的代码。太过于冗余。并且复用性低。为此我们使用 策略设计模式 优化一下
优化(一) 需求一:
新建一个接口 :
public interface FilterEmployee<T> {
boolean filterTest(Employee emp);
}
根据需求一对其实现类:
public class FilterEmployeeByAge implements FilterEmployee<Employee> {
@Override
public boolean filterTest(Employee emp) {
return emp.getAge() > 35;
}
}
根据需求二对其实现类:
public class FilterEmployeeBySalary implements FilterEmployee<Employee> {
@Override
public boolean filterTest(Employee emp) {
return emp.getSalary() > 5000;
}
}
定义一个 这两个需求的公共方法
// 优化方式一 策略设计模式:
public static List<Employee> filterEmployee(List<Employee> list, FilterEmployee<Employee> emp) {
List<Employee> arrayList = new ArrayList<>();
for (Employee item : list) {
if (emp.filterTest(item)) {
arrayList.add(item);
}
}
return arrayList;
}
public static void test3() {
// 优化一需求一:
List<Employee> filterEmployeeByage = filterEmployee(list, new FilterEmployeeByAge());
System.out.println(filterEmployeeByage);
// 优化一需求二:
List<Employee> filterEmployeeByage = filterEmployee(list, new FilterEmployeeBySalary());
System.out.println(filterEmployeeByage);
}
通过这种方式 ,我们对其的进行了策略化,我们发现,这种方法,对我们来说依然是非常冗余的,因为每一种需求 ,就要增加一个实现类,于是我们进行了第二次优化。
接口依然要使用,但是这次我们不需要使用实现类了,我们把接口的实现方式直接放在参数中。这也是匿名内部类的演化过程。
public static void test3() {
// 优化二需求一:
List<Employee> filterEmployeeByage = filterEmployee(list, new FilterEmployee<Employee>() {
@Override
public boolean filterTest(Employee emp) {
return emp.getAge() > 35;
}
});
// 优化二需求二:
List<Employee> filterEmployeeBySalary = filterEmployee(list, new FilterEmployee<Employee>() {
@Override
public boolean filterTest(Employee emp) {
return emp.getSalary() > 5000;
}
});
System.out.println(filterEmployeeByage);
System.out.println(filterEmployeeBySalary);
}
这次优化之后,你以为就完了吗,不,我们使用Lambda表达式对其进行优化
// 优化三:
// Lambda表达式
List<Employee> filterEmployeeByAge2 = filterEmployee(list, (e) -> e.getAge() > 35);
System.out.println(filterEmployeeByAge2);
List<Employee> filterEmployeeByWage2 = filterEmployee(list, (e) -> e.getSalary() > 5000);
System.out.println(filterEmployeeByWage2);
感觉很秒吧,上述两个需求,四行代码就实现了。 其实还有更妙的。那就是Stream Api + Lambda表达式
// 优化方式四:Stream API
list.stream().filter((e) -> e.getAge() > 35).forEach(System.out::println);
list.stream().filter((e) -> e.getSalary() > 5000).forEach(System.out::println);
到这里 ,有没有提起你想学习的兴趣?
什么是Lambda 表达式?
Lambda 表达式的基础语法: Java8中引入了一个新的操作符
->
,该操作符称为箭头操作符或Lambda操作符 ,箭头操作符将Lambda拆分为两部分,
左侧: Lambda 表达式的参数列表 也就是抽象方法所需要的参数
右侧: Lambda 表达式所需执行的功能 即Lambda体 是对抽象方法的实现
注意
- Lambda 表达式的参数列表数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型,即'类型推断'
- 只有在函数式接口中才可以使用Lambda表达式。
函数式接口: 接口中只有一个方法的接口,称为函数式接口。可以使用注解@FunctionalInterface 修饰 可以检查是否是函数式接口
语法格式一:
// 语法格式一 : 无参 无返回值 () -> System.out.printLn(“hello”);
public static void test1() {
// 匿名内部类
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
r.run();
System.out.println("------------------------------------");
// Lambda
Runnable r1 = () -> System.out.println("Hello Lambda!");
r1.run();
}
语法格式二
// 语法格式二: 有一个参数并且无返回值 (num) -> System.out.printLn(num); 当参数只有一个时,小括号可省略不写
public void test2() {
// Consumer<String> con = (x) -> System.out.println(x);
Consumer<String> con = x -> System.out.println(x);
con.accept("Hello Lambda!");
}
语法格式三
语法格式三: 有两个或以上参数 ,有返回值, 并且Lambda体中有多条语句 (a,b) -> {};
public void test3() {
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
}
语法格式四
// 语法格式四: 有两个或以上参数 ,有返回值, 并且Lambda体中只有一条语句 return 以及 {} 可以省略;
public void test4() {
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
}
下面是对其简单的使用。
新建一个接口 并加上 @FunctionalInterface
注解
@FunctionalInterface
public interface MyFun {
public Integer getValue(Integer value);
}
曾加一个对其提供支持的方法。
public static Integer operation(Integer num, MyFun mf) {
return mf.getValue(num);
}
使用:
public static void test5() {
Integer operation = operation(100, (x) -> x * 2);
System.out.println(operation); // 200
}
我们发现 ,需要使用这个表达式,就必须要有接口对它的支持。当然 ,Java8 也内置了。
四大核心函数式接口
* Consumer<T> : 消费型接口 - void accept(T t); 一个参数 无返回值
*
* Supplier<T> : 供给型接口 -T get(); 无参数 有返回值
*
* Function<T,R> : 函数型接口- R apply(T t);
*
* Predicate<T> : 断言型接口- boolean test(T t);
Consumer :
// Consumer 消费型接口
public static void happy(double money, Consumer<Double> co) {
co.accept(money);
}
public static void test1() {
happy(100, (m) -> System.out.println("消费了" + m + "元"));
}
Supplier:
// Supplier<T> : 供给型接口
// 当前需求:产生指定个数的整数。并放入集合中
public static List<Integer> getNumList(int num, Supplier<Integer> sup) {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < num; i++) {
int n = sup.get();
list.add(n);
}
return list;
}
public static void test2() {
System.out.println(getNumList(1000, () -> (int) (Math.random() * 100)));
}
Function:
// 当前需求 传入str 返回 str 长度
public static Integer strHandler(String str, Function<String, Integer> fun) {
return fun.apply(str);
}
public static void test3() {
System.out.println(strHandler("Hello Lambda", (str) -> str.length()));
}
Predicate :
// Predicate<T> : 断言型接口
public static List<String> filterStr(List<String> list, Predicate<String> pre) {
ArrayList<String> arrayList = new ArrayList<String>();
for (String item : list) {
if (pre.test(item)) {
arrayList.add(item);
}
}
return arrayList;
}
public static void test4() {
List<String> strList = Arrays.asList("S575751111tr1", "str57575555fds", "fjsdlaf", "fdsla575##");
List<String> filterStr = filterStr(strList, (str) -> str.length() > 10);
System.out.println(filterStr);
}
方法引用
一:方法引用: 若Lambda 体中的内容有方法已经实现了,我们可以使用“方法引用” (可以理解为方法引用是Lambda 表达式的另一种表现形式)
主要有三种语法格式:
- 对象
::
实例方法名 - 类
::
实例方法名 - 类
::
静态方法名
*1. Lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中的抽象方法的参数列表以及返回值一致
*
* 2.若 Lambda 参数列表的第一个参数是 实例方法的调用者 ,而第二个参数是实例方法的参数时,可以使用 Class Name :: method
// 对象 :: 实例方法名
public static void test1() {
// Consumer<String> con = (str) -> System.out.println(str);
// 抽象接口方法的参数与返回值要与 方法引用中的一致
Consumer<String> con = System.out::println;
con.accept("Hello");
}
public static void test2() {
Employee emp = new Employee();
// Supplier<Integer> sup = () -> emp.getAge();
Supplier<Integer> sup = emp::getAge;
sup.get();
}
// 类 :: 静态方法名
public static void test3() {
// Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
Comparator<Integer> com = Integer::compare;
}
// 类 :: 实例方法名
public static void test4() {
// BiPredicate<String, String> bp = (x, y) -> x.equals(y);
BiPredicate<String, String> bp = String::equals;
}
构造器引用:
需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
* 格式:
* ClassName :: new
public static void test5() {
Supplier<Employee> sup = () -> new Employee();
// 构造器引用方式
Supplier<Employee> sup2 = Employee::new;
Employee emp = sup2.get();
}
public static void test6() {
// Function<Integer, Employee> fun = (x) -> new Employee(x);
// Employee employee = fun.apply(10);
// 调用哪个构造器 取决于函数式接口的参数列表
Function<Integer, Employee> fun = Employee::new;
Employee employee = fun.apply(100);
System.out.println(employee);
}
数组引用:
Type :: new ;
// 数组引用
public static void test7() {
// Function<Integer, String[]> fun = (x) -> new String[x];
// String[] apply = fun.apply(10);
Function<Integer, String[]> fun = String[]::new;
String[] apply = fun.apply(20);
System.out.println(apply.length);
}
流(Stream) 是什么?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
“集合讲的是数据,流讲的是计算”
一. 创建流的方式
- 可以通过Collection 系列集合提供的Stream() 或 parallelStream();;;Stream 是串行的 ,parallelStream是并行
List<String> list = new ArrayList<String>();
Stream<String> stream1 = list.stream();
- 可以通过Arrays中的静态方法 stream() 获取数组流
Employee[] emp = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(emp);
- 通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
- 创建无限流
// 迭代 ,从零开始 一直返回加2的数 2 4 6 ... 直到终止操作
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
stream4.forEach(System.out::println);
// 生成
Stream<Double> stream5 = Stream.generate(() -> Math.random());
stream5.forEach(System.out::println);
2.1-中间操作-筛选与切片
static List<Employee> employee = Arrays.asList(new Employee("张三", 2888.8, 40), new Employee("张三", 2888.6, 40),
new Employee("李四", 7815.9, 25), new Employee("王五", 6545.0, 45), new Employee("赵六", 5671.8, 46),
new Employee("田七", 5784.8, 29));
filter - 接收Lambda ,从流中排除某写元素
// filter
public static void test1() {
employee.stream().filter((em) -> em.getAge() > 30).forEach(System.out::println);
}
limit - 截断流 ,使元素不超过给定数量
// limit
public static void test2() {
employee.stream().limit(3).forEach(System.out::println);
}
- skip(n) 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空 流。
// skip
public static void test3() {
employee.stream().skip(3).forEach(System.out::println);
}
- distinct - 去重,通过流所生成元素的hashcode() 和 equals() 去除重复元素
public static void test4() {
employee.stream().distinct().forEach(System.out::println);
}
2.2-中间操作-映射
map - 接受Lambda ,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap - 接收一个函数作为函数,将流中的每个值都换成新的一个流,然后把所有流连接成一个流
// map-flatMap
public static void test5() {
List<String> asList = Arrays.asList("aaa", "bbb", "ccc", "ddd");
asList.stream().map((str) -> str.toUpperCase()).forEach(System.out::println);
System.out.println("----------------------");
employee.stream().map(Employee::getName).forEach(System.out::println);
System.out.println("-------------");
// 需求:把asList中的字符串一个一个提取出来 用map实现
// {{a,a,a},{b,b,b},{c,c,c}...}
Stream<Stream<Character>> stream = asList.stream().map(TestStream2::filterCharacter);
stream.forEach((sm) -> {
sm.forEach(System.out::println);
});
System.out.println("=============================");
// flatMap实现 接收一个函数作为函数,将流中的每个值都换成新的一个流,然后把所有流连接成一个流
// {a,a,a,b,b,b,c,c,c...}
Stream<Character> sm = asList.stream().flatMap(TestStream2::filterCharacter);
sm.forEach(System.out::println);
}
public static Stream<Character> filterCharacter(String str) {
List<Character> charList = new ArrayList<>();
for (Character item : str.toCharArray()) {
charList.add(item);
}
return charList.stream();
}
map 与 flatMap 的区别
public static void test6() {
// map 与 flatMap 的区别 就类似于 集合中的add 与 addAll的关系,
// add 把整个对象添加到当前对象中,
// addAll把对象作为元素,合并到当前对象中
List<String> list = Arrays.asList("aaa", "bbb");
List list2 = new ArrayList();
list2.add(1);
list2.add(2);
list2.add(list); // [1, 2, [aaa, bbb]]
// list2.addAll(list); // [1, 2, aaa, bbb]
System.out.println(list2);
}
2.3-中间操作-排序
sorted() - 自然排序(Comparable)
*
* sorted(Comparator com) - 定制排序
// sorted()
public static void test7() {
List<String> list = Arrays.asList("zzz", "ddd", "aaa", "bbb", "ccc");
list.stream().sorted().forEach(System.out::println);
System.out.println("=================");
// 先按年龄排,年龄一样 按姓名排
employee.stream().sorted((e1, e2) -> {
if (e1.getAge().equals(e2.getAge())) {
return e1.getName().compareTo(e2.getName());
}
return e2.getSalary().compareTo(e1.getSalary());
}).forEach(System.out::println);
}
3.终止操作 - 查找与匹配
- allMatch - 检查是否匹配所有元素
*
* anyMatch - 检查是否至少匹配一个元素
*
* noneMatch - 检查元素是否没有匹配元素
*
* findFirst - 返回第一个元素
*
* findAny - 返回当前流中的任意元素
*
* count - 返回流中元素的总个数
*
* max - 返回流中的最大值
*
* min - 返回流中最小值
static List<Employee> employee = Arrays.asList(new Employee("张三", 2888.8, 40, Status.UNWANTED),
new Employee("张三", 2888.6, 40, Status.BUSYED), new Employee("李四", 7815.9, 25, Status.UNWANTED),
new Employee("王五", 6545.0, 45, Status.RESTED), new Employee("赵六", 5671.8, 46, Status.BUSYED),
new Employee("田七", 5784.8, 29, Status.RESTED));
public static void test1() {
// allMatch - 检查是否匹配所有元素
// 返回布尔值 检查是否所有元素都是xx
boolean allMatch = employee.stream().allMatch((em) -> Status.BUSYED.equals(em.getStatus()));
System.out.println(allMatch);
// anyMatch - 检查是否至少匹配一个元素
// 返回布尔值 检查是否存在至少一个xx元素
boolean anyMatch = employee.stream().anyMatch((em) -> Status.RESTED.equals(em.getStatus()));
System.out.println(anyMatch);
// noneMatch - 检查元素是否没有匹配元素
boolean noneMatch = employee.stream().noneMatch((em) -> Status.BUSYED.equals(em.getStatus()));
System.out.println(noneMatch);
// findFirst - 返回第一个元素
// 按工资排序 返回第一个
Optional<Employee> findFirst = employee.stream().sorted((e1, e2) -> -e1.getSalary().compareTo(e2.getSalary()))
.findFirst();
System.out.println(findFirst.get());
// findAny - 返回当前流中的任意元素
Optional<Employee> findAny = employee.stream().filter((em) -> Status.BUSYED.equals(em.getStatus())).findAny();
System.out.println(findAny.get());
// count - 返回流中元素的总个数
long count = employee.stream().filter((em) -> em.getAge() > 30).count();
System.out.println(count);
// max 返回流中的最大值
Employee employee2 = employee.stream().max((e1, e2) -> e1.getSalary().compareTo(e2.getSalary())).get();
System.out.println(employee2.getSalary());
// min 返回流中的最小值
Integer min = employee.stream().map(Employee::getAge).min(Integer::compare).get();
System.out.println(min);
}
3.1.终止操作 - 归约
- *归约
* reduce - 可以将流中元素反复结合起来 ,得到一个值
// reduce
public static void test2() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 起始值为 0
// 第一次 传入1 x = 0 , y = 1; 返回 1
// 第二次 传入2 x = 1 , y = 2; 返回3
// 第三次 传入3 x = 3 , y = 3; 返回6
// x 等于上一次返回的值
Integer total = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(total);
System.out.println("--------------");
// 例如: 计算所有员工工资的和
Optional<Double> reduce = employee.stream().map(Employee::getSalary).reduce(Double::sum);
System.out.println(reduce.get());
}
3.2.终止操作 - 收集
收集
collect - 将流转换为其他形式。接收一个Collector 接口的实现。用于给Stream中元素中汇总的方法
容器
// collect
// 需求 ,提取所有员工姓名 放到容器中
public static void test3() {
// 放到list中
List<String> listName = employee.stream().map(Employee::getName).collect(Collectors.toList());
listName.forEach(System.out::println);
System.out.println("----------------------------");
// 放到Set中
Set<String> setName = employee.stream().map(Employee::getName).collect(Collectors.toSet());
setName.forEach(System.out::println);
System.out.println("----------------------------");
// 放到HashSet中
HashSet<String> hashSetName = employee.stream().map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));
hashSetName.forEach(System.out::println);
}
求值。Stream 求值的方式及其多,下面是常用的
public static void tes4() {
// 总数
Long count = employee.stream().collect(Collectors.counting());
// 工资平均值
Double avg = employee.stream().collect(Collectors.averagingDouble(Employee::getSalary));
// 总和
Double sum = employee.stream().collect(Collectors.summingDouble(Employee::getSalary));
// 最大值 工资最高的员工
Optional<Employee> max = employee.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
Employee employee2 = max.get();
// 最小值 公司工资最小值
Optional<Double> min = employee.stream().map(Employee::getSalary).collect(Collectors.minBy(Double::compare));
System.out.println(min.get());
// ....
System.out.println("===================");
DoubleSummaryStatistics dss = employee.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(dss.getAverage());
System.out.println(dss.getCount());
System.out.println(dss.getMax());
System.out.println(dss.getSum());
// .....
}
分组
// 分组
public static void test5() {
Map<Status, List<Employee>> groupBy = employee.stream().collect(Collectors.groupingBy(Employee::getStatus));
}
多级分组
// 多级分组
public static void test6() {
// 先按状态分 ,再按年龄段分
Map<Status, Map<String, List<Employee>>> groupBys = employee.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
if (((Employee) e).getAge() <= 25) {
return "青年";
} else if (e.getAge() <= 45) {
return "中年";
} else {
return "老年";
}
})));
}
分区
// 分区 ,true为一个区,false 为一个区
public static void test7() {
Map<Boolean, List<Employee>> collect = employee.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 5000));
}
连接
// 连接
public static void test8() {
String collect = employee.stream().map(Employee::getName).collect(Collectors.joining("-"));
}