文章目录
✨Java 8 的新特性
-
速度更快 (对于底层的数据结构有了改变、对底层的内存结构也有了改变【将堆中的方法区删除,取而代之的是MetaSpace元空间,使用了物理内存,当垃圾回收机制回收的概率变低了,因为是物理内存变大了】)
-
代码更少(增加了新的语法Lambda表达式)
-
强大的Stream API
-
便于并行
-
最大化的减少空指针异常
1、Lambda表达式
需求1:获取当前公司中员工年龄大于35的员工信息
数据准备:
Employee类:
/**
* @version 1.0
* @author: YxinMiracle
* @date: 2021-10-26 10:04
*/
public class Employee {
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
employees-list:
List<Employee> employees = Arrays.asList(
new Employee("张三",18,9999.99),
new Employee("李四",38,5555.55),
new Employee("王五",50,6666.66),
new Employee("赵六",16,3333.33),
new Employee("田七",8,7777.77)
);
方法一进行实现:
public List<Employee> filterEmployees(List<Employee> list) {
List<Employee> emps = new ArrayList<>();
for (Employee emp : employees) {
if (emp.getAge() >= 35) {
emps.add(emp);
}
}
return emps;
}
代码截图:
现在需求改变了,变为:获取当前公司中员工工资大于5000的员工信息,那么现在意味着需要又要多写一个一模一样的方法:
public List<Employee> filterEmployees2(List<Employee> list) {
List<Employee> emps = new ArrayList<>();
for (Employee emp : employees) {
if (emp.getSalary() >= 5000) {
emps.add(emp);
}
}
return emps;
}
唯独只变了一句话,这样就增加了代码的冗余程度了,所以设计模式就是对代码最好的优化方式;
优化方式一
创建一个MyPredicate接口:
/**
* @version 1.0
* @author: YxinMiracle
* @date: 2021-10-26 10:25
*/
public interface MyPredicate<T> {
public boolean test(T t);
}
创建个该接口的实现类:
/**
* @version 1.0
* @author: YxinMiracle
* @date: 2021-10-26 10:25
*/
public class FilterEmployeeByAge implements MyPredicate<Employee> {
@Override
public boolean test(Employee t) {
return t.getAge() >= 35;
}
}
编写方法:
public List<Employee> filterEmployee(List<Employee> list,MyPredicate<Employee> emp){
List<Employee> emps = new ArrayList<>();
for (Employee employee : employees) {
if (emp.test(employee)){
emps.add(employee);
}
}
return emps;
}
进行使用:
// 优化方式1:
@Test
public void test2(){
List<Employee> emps = filterEmployee(this.employees, new FilterEmployeeByAge());
for (Employee emp : emps) {
System.out.println(emp);
}
}
此时此刻,要是过滤需求改变了,需要按照工资过滤,那么现在设计好的东西就不需要动了,只需要再创建一个实现类,不需要改变filterEmployee方法:
/**
* @version 1.0
* @author: YxinMiracle
* @date: 2021-10-26 10:33
*/
public class FilterEmployeeBySalary implements MyPredicate<Employee> {
@Override
public boolean test(Employee employee) {
return employee.getSalary() >= 5000;
}
}
运行使用:
@Test
public void test2(){
List<Employee> emps = filterEmployee(this.employees, new FilterEmployeeByAge());
for (Employee emp : emps) {
System.out.println(emp);
}
System.out.println("-------------------------");
List<Employee> emps2 = filterEmployee(this.employees, new FilterEmployeeBySalary());
for (Employee emp : emps) {
System.out.println(emp);
}
}
之前多一个需求就需要多一个方法,现在只需要写一个filterEmployee,在需求增加的时候就只需要多增加一个实现类即可,这种设计模式就称为策略设计模式
现在也有一个不好的方法,每次多一个策略就需要多一个实现类,这样是非常不方便的;
优化方式二
那么现在就有优化方式二,使用匿名内部类的方式:
@Test
public void test3(){
List<Employee> employees = filterEmployee(this.employees, new MyPredicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getSalary() <= 5000;
}
});
for (Employee employee : employees) {
System.out.println(employee);
}
}
使用匿名内部类实现接口:
但是有用的只有一句,可读性降低了,还更加混乱了。
优化方式三
于是就出现了优化方式三,使用lambda表达式:
@Test
public void test4(){
List<Employee> employees = filterEmployee(this.employees, (e) -> e.getSalary() <= 5000);
employees.forEach(System.out::println);
}
就把最关键的代码提取出来了;
优化方式四
当然了要是不满意的话还有优化方式四,就一行代码解决问题,这就是streamApi:
@Test
public void test5(){
employees.stream().filter(e->e.getSalary()<=5000).forEach(System.out::println);
}
结果截图:
2、Lambda基础语法
Lambda表达式的基础语法:Java8中进入了一个新的操作符“->” 该操作符称为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分
左侧:Lambda表达式的参数列表
右侧:Lambda表达式中所需要执行的功能,即Lambda体
但是lambda表达式,需要一个函数式接口的支持,就是指这个接口只有一个抽象方法
语法格式一
语法格式一:无参数,无返回值
() -> System.out.println(“xxxxx”)
exp1Runnable接口
@Test
public void test6(){
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("hello world");
}
};
System.out.println("------------------------------");
Runnable r1 = () -> System.out.println("hello world");
r.run();
r1.run();
}
语法格式二
语法格式二:有一个参数,无返回值
exp:Consumer接口
代码:
@Test
public void test7(){
Consumer<String> con = (x) -> System.out.println(x); // 对accept这个抽象方法的实现
con.accept("cyxnbnb");
}
结果截图:
语法格式三
语法格式三:只有一个参数,那么小括号可以不写
@Test
public void test8(){
Consumer<String> con = x -> System.out.println(x);
con.accept("cyxnbnb");
}
语法格式四
语法格式四:有两个以上的参数,并且lambda体中有多条语句,lambda方法体要用{}
包起来
代码:
@Test
public void test9(){
Comparator<Integer> com = (x,y) -> {
System.out.println("函数式接口");
return Integer.compare(x,y);
};
int compare = com.compare(1, 2);
System.out.println(compare);
}
语法格式五
语法格式五:若 Lambda体中只有一条语句,return 和 大括号都可以省略不写
代码:
@Test
public void test10(){
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
}
语法格式六
语法格式六:lambda表达式的参数列表的数据类型可以省略不写,因为jvm编译器可以通过上下文进行类型的推断,大部分的情况下都是不写
@Test
public void test10(){
Comparator<Integer> com = (Integer x,Integer y) -> Integer.compare(x,y);
}
3、函数式接口
函数式接口:接口中只有一个抽象方法的接口,称为函数式接口,可以使用注解@FunctionalInterface,可以检查这个接口是否是函数式接口
exp:
要是不是函数式接口,就会报错:
4、Java8中司法核心函数式接口
-
Consumer :消费型接口
void accept(T t)
-
Supplier : 供给型接口
T get()
-
Function<T,R> : 函数型接口
R apply(T t)
-
Predicate: 断言型接口
boolean test(T t)
消费性接口
Consumer
@Test
public void test11(){
happy(100.0,(x)-> {
x = x - 10;
System.out.println("剩余"+x+"元");
});
}
public void happy(Double money,Consumer<Double> con){
con.accept(money);
}
运行结果:
供给型接口
需求,产生指定个数的整数
代码:
@Test
public void test12() {
List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));
for (Integer integer : numList) {
System.out.println(integer);
}
}
public List<Integer> getNumList(int num, Supplier<Integer> sup) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
list.add(sup.get());
}
return list;
}
}
函数型接口
需求:处理字符串
@Test
public void test13(){
String s = strHandler("\t\t\t\t\t\t 这是一个有很多空格的字符串", x -> x.trim());
System.out.println(s);
}
public String strHandler(String str, Function<String, String> fun){
String str1 = fun.apply(str);
return str1;
}
断言型接口
需求:将满足条件的字符串放在集合中
@Test
public void test14(){
List<String> list = Arrays.asList("hello","ahhaha","lambda","www","yxinmiracle","okkk","o");
List<String> list1 = filterStr(list, x -> x.length() > 3);
System.out.println(list1);
}
public List<String> filterStr(List<String> list, Predicate<String> pre) {
List<String> strList = new ArrayList<>();
for (String str : list) {
if (pre.test(str)){
strList.add(str);
}
}
return strList;
}
输出:
[hello, ahhaha, lambda, yxinmiracle, okkk]
子接口
还有一些子接口:
5、方法引用
方法引用:若Lambda体中的内容有方法已经实现类,那么我们可以使用“方法引用”(可以理解为方法引用就是Lambda表达式的另外一种表现形式)
三种表现形式
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
对象::实例方法名
例1
举例:
使用消费型接口进行举例
lambda体中的功能已经有方法可以实现了,那么就可以使用方法引用的当时
但是有一个要求:
需要实现这个接口的抽象方法的参数列表,以及lambda中这个方法参数列表以及返回值类型要保持一致。
Consumer<String> con = (x) -> System.out.println(x);
也就是说,上面这行代码,还可以写成下面这个样子:
Consumer<String> con2 = System.out::println;
原因是,抽象方法的参数列表和返回值为:
lambda方法体中的println参数列表和返回值类型:
相同,所以可以这么使用
结果:
例2
再用供给型接口进行举例
查看Supplier接口中的抽象方法:
与我们Employee中的getName方法
参数列表和返回值类型都相同
所以可以写成:
@Test
public void test16(){
Employee emp = new Employee();
emp.setName("hello java");
// 形式1
Supplier<String> sup = () -> emp.getName();
System.out.println(sup.get());
// 形式2
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}
输出结果:
hello java
hello java
类::静态方法名
@Test
public void test17(){
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
Comparator<Integer> com1 = Integer::compare;
}
类::实例方法名
如果第一个参数是这个方法的调用者,第二个参数是这个调用方法的参数时,就可以使用类::实例方法名的方式
@Test
public void test18(){
BiPredicate<String,String> bp = (x,y)->x.equals(y);
BiPredicate<String,String> bp2 = String::equals;
}
6、构造器引用
格式:
ClassName::new
例子:
@Test
public void test19(){
Supplier<Employee> sup = ()->new Employee();
// 构造器引用
Supplier<Employee> sup2 = Employee::new;
}
注:选择哪个构造器需要看抽象方法中函数的参数列表是怎么样的
7、Stream API
流是数据渠道,用于操作数据源(集合、数组等)所产生的元素序列。“集合讲的是数据,流讲的是计算”
注意:
- Stream 自己不会存储元素
- Stream 不会改变源对象。相反,热门会返回一个持有新结果的Stram
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
Stream操作的三个步骤
-
创建Stream流
一个数据源(如:集合、数组),获取一个流
-
中间操作
一个中间操作链,对数据源的数据进行处理
-
终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果
创建流
可以通过Collection系列集合提供的stream()或parallelStream()
@Test
public void test20(){
List<String> lsit = new ArrayList<>();
// 1.得到流
Stream<String> stream = lsit.stream();
}
中间操作
- filter-接收lambda,从流中排除某些元素
- limit-截断流,使其元素不超过给定数量
- skip(n)-跳过元素,返回一个人扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit互补
- distinct-筛选,通过流所产生的元素的hashCode() 和 equals() 去除重复元素
只有遇到了终止操作才会进行执行中间操作
filter
@Test
public void test1() {
employees.stream()
.filter((e) -> {
System.out.println("中间操作");
return e.getAge() > 35;
})
.forEach(System.out::println);
}
结果:
中间操作
中间操作
Employee{name='李四', age=38, salary=5555.55}
中间操作
Employee{name='王五', age=50, salary=6666.66}
中间操作
中间操作
要是把终止操作forEach去掉
就没有任何输出
其中,为什么foreach中可以写System.out::println,原因是:
foreach中的参数为consumer函数式接口:
而该consumer函数式接口的方法的参数列表和返回值类型为:
与println对应:
limit
只要前几个
@Test
public void test2(){
employees.stream()
.filter(e->e.getSalary()>5000)
.limit(2)
.forEach(System.out::println);
}
输出:
Employee{name='张三', age=18, salary=9999.99}
Employee{name='李四', age=38, salary=5555.55}
skip
扔掉前几个
@Test
public void test3(){
employees.stream()
.filter(e->e.getSalary()>5000)
.skip(2)
.forEach(System.out::println);
}
输出:
Employee{name='王五', age=50, salary=6666.66}
Employee{name='田七', age=8, salary=7777.77}
distinct
去重
数据改变:
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 38, 5555.55),
new Employee("王五", 50, 6666.66),
new Employee("赵六", 16, 3333.33),
new Employee("田七", 8, 7777.77),
new Employee("田七", 8, 7777.77),
new Employee("田七", 8, 7777.77)
);
要重写hashcode和equals方法
代码:
@Test
public void test4(){
employees.stream()
.filter(e->e.getSalary()>5000)
.distinct()
.forEach(System.out::println);
System.out.println("---------------");
employees.stream()
.filter(e->e.getSalary()>5000)
.forEach(System.out::println);
}
输出:
Employee{name='张三', age=18, salary=9999.99}
Employee{name='李四', age=38, salary=5555.55}
Employee{name='王五', age=50, salary=6666.66}
Employee{name='田七', age=8, salary=7777.77}
---------------
Employee{name='张三', age=18, salary=9999.99}
Employee{name='李四', age=38, salary=5555.55}
Employee{name='王五', age=50, salary=6666.66}
Employee{name='田七', age=8, salary=7777.77}
Employee{name='田七', age=8, salary=7777.77}
Employee{name='田七', age=8, salary=7777.77}
映射
map-接收lambda,将元素换成其他形式或者提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射到成一个新的元素。
flatmap-接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连城一个流
map
@Test
public void test5(){
List<String> list = Arrays.asList("aaa","bbb","ddd","eee");
list.stream()
.map(s->s.toUpperCase()) // 到这一个步会变成一个新的流
.forEach(System.out::println);
}
结果:
AAA
BBB
DDD
EEE
需求:
提取员工姓名:
@Test
public void test5(){
employees.stream()
.map(Employee::getName)
.forEach(System.out::println);
}
flatmap
接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连城一个流
@Test
public void test6(){
List<String> list = Arrays.asList("aaa","bbb","ddd","eee");
list.stream()
.flatMap(TestStreamApi::filterCharater) // 到这一个步会变成一个新的流
.forEach(System.out::println);
}
输出:
a
a
a
b
b
b
d
d
d
e
e
e
他与map的区别在于,flatmap会把返回的流全部合成为一个新流,但是map不会,所以这里会看见需要两次foreach
@Test
public void test6(){
List<String> list = Arrays.asList("aaa","bbb","ddd","eee");
list.stream()
.map(TestStreamApi::filterCharater) // 到这一个步会变成一个新的流
.forEach(sm->{
sm.forEach(System.out::println);
});
}
结果:
a
a
a
b
b
b
d
d
d
e
e
e
排序
- sorted() – 自然排序(Comparable)
- sorted(Comparator com)–定制排序(Compatator)
8、Stream API 终止操作
- allMatch - 检查是否匹配所有元素
- anyMatch - 检查是否至少匹配一个元素
- noneMatch - 检查是否没有匹配所有元素
- findFirst - 返回第一个元素
- findAny - 返回当前流中的任意元素
- count - 返回流中元素的总个数
- max - 返回流中最大值
- min - 返回流中最小值
allMatch
@Test
public void test8(){
boolean flag = employees.stream()
.allMatch(employee -> employee.getName().contains("张三"));
System.out.println(flag);
}
输出:
false
anyMatch
@Test
public void test9(){
boolean flag = employees.stream()
.anyMatch(employee -> employee.getName().contains("张三"));
System.out.println(flag);
}
输出:
true
noneMatch
@Test
public void test10(){
boolean flag = employees.stream()
.noneMatch(employee -> employee.getName().contains("张三1"));
System.out.println(flag);
}
输出:
true
findFirst
@Test
public void test11(){
Optional<Employee> opt = employees.stream()
.filter(employee -> employee.getAge() > 10)
.findFirst();
System.out.println(opt.get());
}
输出:
Employee{name='张三', age=18, salary=9999.99}
findAny
@Test
public void test12(){
Optional<Employee> any = employees.parallelStream()
.findAny();
System.out.println(any.get());
}
使用并行流parallelStream进行测试
输出:
Employee{name='田七', age=8, salary=7777.77}
count
@Test
public void test13(){
long count = employees.stream().count();
System.out.println(count);
}
输出:
7
max
@Test
public void test14(){
Optional<Employee> max = employees.stream()
.max((e1, e2) -> e1.getAge() - e2.getAge());
System.out.println(max.get());
}
输出:
Employee{name='王五', age=50, salary=6666.66}
min
@Test
public void test15(){
Optional<Employee> min = employees.stream()
.min((e1, e2) -> e1.getAge() - e2.getAge());
System.out.println(min.get());
}
输出:
Employee{name='田七', age=8, salary=7777.77}
reduce
可以将流中的元素反复结合起来,得到一个值
@Test
public void test16(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum);
}
输出
55
需求:
计算员工的公司和
Optional<Double> reduce = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(reduce.get());
collect
将流转换为其他形式。接收一个Collector接口的实现,用于Stream中元素做汇总的方法
@Test
public void test17() {
List<Employee> collect = employees.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(collect);
}
输出:
[Employee{name='张三', age=18, salary=9999.99}, Employee{name='李四', age=38, salary=5555.55}, Employee{name='王五', age=50, salary=6666.66}]
其中Collectors还可以计算平均值,总和,最大值…
例如:
Double avgAge = employees.stream()
.collect(Collectors.averagingInt(Employee::getAge));
System.out.println(avgAge);
collect分组
需求1:安装年龄分组
Map<Integer, List<Employee>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getAge));
System.out.println(map);
输出:
{16=[Employee{name='赵六', age=16, salary=3333.33}], 50=[Employee{name='王五', age=50, salary=6666.66}], 18=[Employee{name='张三', age=18, salary=9999.99}], 38=[Employee{name='李四', age=38, salary=5555.55}], 8=[Employee{name='田七', age=8, salary=7777.77}, Employee{name='田七', age=8, salary=7777.77}, Employee{name='田七', age=8, salary=7777.77}]}
collect多级分组
需求:按照薪水分组,然后再按年龄进行自定义分组
Map<Integer, Map<String, List<Employee>>> collect = employees.stream()
.collect(Collectors.groupingBy(Employee::getAge, Collectors.groupingBy(e -> {
if (e.getAge() <= 35) {
return "青年";
} else if (e.getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})));
System.out.println(collect);
输出:
{16={青年=[Employee{name='赵六', age=16, salary=3333.33}]}, 49={中年=[Employee{name='田七', age=49, salary=7777.77}]}, 50={中年=[Employee{name='王五', age=50, salary=6666.66}]}, 18={青年=[Employee{name='张三', age=18, salary=9999.99}]}, 69={老年=[Employee{name='田七', age=69, salary=7777.77}]}, 38={中年=[Employee{name='李四', age=38, salary=5555.55}]}, 29={青年=[Employee{name='田七', age=29, salary=7777.77}]}}
collect分区
满足条件的一个区,不满足条件的一个区
Map<Boolean, List<Employee>> collect = employees.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 5000));
System.out.println(collect);
输出:
{false=[Employee{name='赵六', age=16, salary=3333.33}], true=[Employee{name='张三', age=18, salary=9999.99}, Employee{name='李四', age=38, salary=5555.55}, Employee{name='王五', age=50, salary=6666.66}, Employee{name='田七', age=29, salary=7777.77}, Employee{name='田七', age=49, salary=7777.77}, Employee{name='田七', age=69, salary=7777.77}]}
collect-summarizingDouble返回平均值最大值最小值…
代码:
DoubleSummaryStatistics dss = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
可用方法:
collect-join
把字符串拼接起来:
String collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining());
System.out.println(collect);
输出:
张三李四王五赵六田七田七田七
把字符串用‘,’拼接起来:
String collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(","));
System.out.println(collect);
输出:
张三,李四,王五,赵六,田七,田七,田七
把字符串用‘,’拼接起来后,首尾用“==”拼接:
String collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(",","==","=="));
System.out.println(collect);
输出:
==张三,李四,王五,赵六,田七,田七,田七==