1.Lambda表达式
1.1为什么使用lambda表达式?
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
1.2从匿名类到lambda表达式
- 原来的内部类
@Test
public void test1() {
Comparator<Integer> com = new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
//TreeSet需要拟传递一个匿名内部类进去,然后按照这个规则进行排序,最后输出的是一个有序的结果
TreeSet<Integer> ts = new TreeSet<Integer>(com);
ts.add(15);
ts.add(20);
ts.add(18);
ts.add(30);
ts.add(49);
ts.add(16);
ts.forEach(System.out::println);//输出结果:15 16 18 20 30 49
}
- 使用lambda表达式的匿名内部类
@Test
public void test2() {
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
TreeSet<Integer> ts = new TreeSet<Integer>(com);
ts.add(15);
ts.add(20);
ts.add(18);
ts.add(30);
ts.add(49);
ts.add(16);
ts.forEach(System.out::println);//输出结果:15 16 18 20 30 49
}
1.3Lmabda表达的优点体验
- 前置条件,创建一个实体类,以便进行下面实验
雇员实体类
package entitys;
import lombok.Data;
import java.util.Objects;
@Data
public class Employee {
private int id;
private String name;
private int age;
private double salary;
private Status status;
//构造方法
public Employee() {
}
public Employee(int age) {
this.age = age;
}
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee(int id, String name, int age, double salary, Status status) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return getAge() == employee.getAge() && Double.compare(employee.getSalary(), getSalary()) == 0 && Objects.equals(getName(), employee.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge(), getSalary());
}
//建立一个枚举
public enum Status{
FREE,
BUSY,
VOCATION;
}
}
- 使用java自带的方法Arrays.asList把数组转集合,先创建一个eployee实体集合,以便进行实验
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 99999.9),
new Employee("李四", 29, 99499.9),
new Employee("王五", 22, 1999.9),
new Employee("赵六", 26, 12999.9),
new Employee("钱七", 38, 3999.9)
);
需求:获取年龄大于25岁的员工
- 原来的写法
public List<Employee> filterEmployees(List<Employee> employees) {
List<Employee> emps = new ArrayList<>();
for (Employee emp : employees) {
if (emp.getAge() >= 35) {
emps.add(emp);
}
}
return emps;
}
- 改进写法1:使用策略者模式
先创建一个 Mypredicate的接口,后面可以实现这个接口类的方法
public interface Mypredicate<T> {
public boolean test(T t);
}
实现Mypredicate接口,通过这个实现接口进行过滤
import entitys.Employee;
//按照年龄进行过滤
public class FilterEmployeeByAge implements Mypredicate<Employee>{
@Override
public boolean test(Employee t) {
return t.getAge()>=20;
}
}
//按照薪水进行过滤
public class FilterEmployeeBysalary implements Mypredicate<Employee>{
@Override
public boolean test(Employee t) {
return t.getSalary()>=2000;
}
}
创建一个filterEmployee方法,里面调用Mypredicate的实现方法,进行过滤数据
public List<Employee>filterEmployee(List<Employee>list,Mypredicate<Employee>mp){
List<Employee>emps=new ArrayList<>();
for (Employee employee :list) {
if(mp.test(employee)){
emps.add(employee);
}
}
return emps;
}
然后实例化你需要过滤的方式的类,作为参数传递进filterEmployee方法进行过滤.
//按照年龄进行过滤
@Test
public void test3(){
//需要传递一个Mypredicate的实现类
List<Employee>list=filterEmployee(employees,new FilterEmployeeByAge());
for (Employee employee:list){
System.out.println(employee);
}
}
//按照薪水进行过滤
@Test
public void test4(){
//需要传递一个Mypredicate的实现类
List<Employee>list=filterEmployee(employees,new FilterEmployeeBysalary());
//输出金额超过2000的对象
for (Employee employee:list){
System.out.println(employee);
}
}
- 改进方式2:使用匿名内部类
@Test
public void test5(){
//这里调用了改进方式1的filterEmployee方法,只是传递进去的参数使用匿名内部类进行实现
List<Employee>list=filterEmployee(employees, new Mypredicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getSalary()<=50000;
}
});
//输出金额超过2000的对象
for (Employee employee:list){
System.out.println(employee);
}
}
- 改进方式3:使用lambda表达式
@Test
public void test6(){
//这里调用了改进方式1的filterEmployee方法,只是传递进去的参数使用lambda表达式进行实现
List<Employee>list=filterEmployee(employees,(employee -> employee.getSalary()<=5000));
list.forEach(System.out::println);
}
- 改进方式4:使用Stream API
@Test
public void test7(){
employees.stream()
.filter((employee -> employee.getSalary()>=5000))//过滤
.limit(2)//取出前几个
.forEach(System.out::println);//输出
System.out.println("-----------------------------------");
//遍历所有的名字
employees.stream()
.map(Employee::getName)
.forEach(System.out::println);
}
- 总结
上面的所有方法都是逐层递进的关系,可以发现越往后面走,方法越渐变,所写的代码也越来越少,通用性也越来越高,但是存在的问题就是代码的可读性逐渐降低,鱼和熊掌不可兼得,所以选择了效率必定也会有所损失,代码的维护性不高.
1.4 Lambda表达式语法
Lambda表达式在Java语言中引入了一个新的语法元素和操作符。这个操作符为“->”,该操作符被称为Lambda操作符或剪头操作符。它将Lambda分为两个部分:
左侧:指定了Lambda表达式需要的所有参数
右侧:指定了Lambda体,即Lambda表达式要执行的功能。
语法格式一:无参,无返回值,Lambda体只需一条语句
/*语法格式一:无参数,无返回值
()->System.out.println("Hello Lambda!");*/
int num = 0;//在1.7中,匿名内部类使用局部变量,需要加一个final,但是1.8不用加(其实是默认加了,所以不能对这个值进行修改),称之为糖衣于法
@Test
public void test1() {
//以前最常见的就是Runnable()方法
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello lambda" + num);
}
};
r.run();
//使用lambda表达式的写法
System.out.println("============================");
Runnable runnable = () -> System.out.println("Hello lambda" + num);
runnable.run();
}
语法格式二:Lambda需要一个参数,此时参数的小括号可以省略
/*语法格式2:有一个参数,无返回值
* 若只有一个参数,参数的小括号可以省略不写,如果只有一个lambda体,大括号可以省
* 左右遇一括号省
* */
@Test
public void test2() {
//Consumer是java内置的函数式接口
Consumer<String> con = (x) -> System.out.println(x);
con.accept("加油学习Java");
}
语法格式三:Lambda需要两个参数,并且有返回值
/*语法格式3:有两个以上的参数,并且lambda体中有多条语句,并且有放回值
* 如果有多条语句必须使用大括号
* */
@Test
public void test3() {
//Comparator是java内置的函数式接口
Comparator<Integer> comparator = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
System.out.println(comparator.compare(15, 20));
}
语法格式四:当 Lambda 体只有一条语句时,return 与大括号可以省略
/*语法格式4:有两个以上的参数,并且lambda体中只有一条语句,并且有放回值
* 则大括号和return都可以省略
* */
@Test
public void test4() {
//Comparator是java内置的函数式接口
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
System.out.println(comparator.compare(15, 20));
}
1.5 推断类型
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”.
/*语法格式5:lambda表达式的数据源的参数类型可以不写,如果写必须都写
* 因为JVM编译器通过上下文推断出数据类型,称之为"类型推断"
* 左侧推断类型省
* */
@Test
public void test5() {
Comparator<Integer> comparator = (Integer x, Integer y) -> Integer.compare(x, y);
System.out.println(comparator.compare(45, 20));
}
2.函数式接口
函数式接口,就是指这个接口只有一个抽象方法的接口,并且在接口类上加上注解:@FunctionalInterface
2.1带有一个参数的函数式接口
- 例1:使用函数式接口进行数字计算
创建一个函数式接口
//带有一个抽象方法的接口称之为函数式接口
@FunctionalInterface
public interface MyFun {
public Integer getValue(Integer num);
}
创建一个方法operation,对函数式接口进行调用
public Integer operation(Integer num, MyFun myFun) {
return myFun.getValue(num);
}
使用lambda表达式实现函数式接口
@Test
public void test6() {
Integer num = operation(100, (x) -> x * x);
System.out.println(num);
}
- 例2:使用函数式接口对字符串进行操作
创建一个函数式接口
@FunctionalInterface
public interface MyFunction {
public String getValue(String str);
}
创建一个方法strHandler,对函数式接口进行调用
public String strHandler(String str,MyFunction mf){
return mf.getValue(str);
}
声明函数式接口,接口中声明抽象方法,并对接口进行实现,然后调用strHandler,将接口实现方法传递进去;
@Test
public void test2(){
//替换空格
String str=strHandler("\t\t\t哈哈,我是大宝贝",(x)->x.trim());
System.out.println(str);
System.out.println("-------------------------------");
//转换成大写
str=strHandler("happy everyday",x->x.toUpperCase(Locale.ROOT));
System.out.println(str);
System.out.println("-------------------------------");
//截取字符串
str=strHandler("happy everyday",x->x.substring(2,5));
System.out.println(str);
}
2.2带有泛型的函数式接口
创建函数式接口
//泛型类型为<T, R>T为参数,R为返回值
public interface MyFunction2<T, R> {
public R getValue(T t1, T t2);
}
创建一个方法longHander,对函数式接口进行调用
public void longHander(Long l1,Long l2,MyFunction2<Long,Long> myFunction){
System.out.println(myFunction.getValue(l1,l2));
}
声明函数式接口,接口中声明抽象方法,并对接口进行实现,然后调用strHandler,将接口实现方法传递进去,对两个long型数据进行处理;
@Test
public void test3(){
longHander(12L,18L,(x,y)->x+y);
longHander(12L,18L,(x,y)->x*y);
}
3.Java 四大核心内置函数式接口
函数式接口 | 参数类型 | 返回类型 | 抽象方法 | 用途 |
---|---|---|---|---|
Consumer:消费型接口 | T | void | void accept(T t); | 操作数据 |
Supplier:供给型接口 | 无 | T | T get(); | 创造数据 |
Function<T,R>:函数型接口 | T | R | R apply(T t); | 操作并返回数据 |
Predicate:断言型函数接口 | T | boolean | Boolean test(T t); | 判断数据 |
3.1 Consumer:消费型接口
//Consumer<T>消费型接口
public void shopping(Double money, Consumer<Double>consumer){
consumer.accept(money);
}
@Test
public void test(){
shopping(1000.0,x-> System.out.println(String.format("今天购物花费了%.2f元",x)));
}
3.2 Supplier:供给型接口
//Supplier<T>供给型接口
@Test
public void test2(){
//生成10个100以内的随机整数
List<Integer>list=getNumber(10,()->(int)(Math.random()*100));
list.forEach(System.out::println);
}
//需求:产生指定个数,并且放到集合中
public List<Integer>getNumber(int num, Supplier<Integer>sup){
List<Integer>list=new ArrayList<>();
for(int i=0;i<num;i++){
Integer n=sup.get();
list.add(n);
}
return list;
}
3.3 Function<T,R>:函数型接口
//Function<T,R>函数型接口
@Test
public void test3(){
String str=strHander("\t\t\t\t\t加油减肥 ",(x)->x.trim());
System.out.println(str);
System.out.println("=====================================");
//截取字符串
str=strHander("你是笨蛋我是傻瓜",(x)->x.substring(2,5));
System.out.println(str);
}
//需求:用于处理字符串
public String strHander(String str, Function<String,String>function){
return function.apply(str);
}
3.4 Predicate:断言型函数接口
//Predicate<T>断言型接口:
@Test
public void test4(){
//将长度四个字的名字返回
String[]names={"辣目洋子","东野圭吾","马尔科夫","史蒂芬","霍金","易烊千玺","雨果","曹禺","钱钟书","张爱玲"};
List<String>list=new ArrayList<>(Arrays.asList(names));
List<String>list1=filterStr(list,(x)->x.length()>=4);
list1.forEach(System.out::println);
}
//需求:将满足条件的字符串,放入到集合中
public List<String> filterStr(List<String>list, Predicate<String>pre){
List<String>filter_data=new ArrayList<>();
for(String str:list){
if(pre.test(str)){
filter_data.add(str);
}
}
return filter_data;
}
4.其他接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | 对类型为 T, U 参数应用操作, 返回 R 类型的结 果。包含方法为R apply(T t, U u); |
UnaryOperator (Function子接口) | T | T | 对类型为T的对象进行一元运算, 并返回T类型的结果。包含方法为T apply(T t); |
BinaryOperator(BiFunction 子接口) | T, T | T | 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为T apply(T t1, T t2); |
BiConsumer<T, U> | T,U | void | 对类型为T, U 参数应用操作。包含方法为void accept(T t, U u); |
ToIntFunction ,ToLongFunction ,ToDoubleFunction | T | int ,long ,double | 分 别 计 算 int 、 long 、double、值的函数; |
IntFunction, LongFunction, DoubleFunction | int ,long ,double | R | 参数分别为int、long、double 类型的函数; |
5.方法引用与构造器引用
5.1 方法引用
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)
方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。
如下三种主要使用情况:
- 对象::实例方法
@Test
public void test2(){
Employee employee=new Employee();
Supplier<String>sup=()->employee.getName();
String str=sup.get();
System.out.println(str);
System.out.println("====================使用方法引用====================");
Supplier<Integer>sup2=employee::getAge;
System.out.println(sup2.get());
}
- 类::静态方法
//类::静态方法名
@Test
public void test3(){
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println("====================使用方法引用====================");
BiPredicate<String, String> bp2 = String::equals;
if(bp2.test("加油","加油")){
System.out.println("相同");
}else{
System.out.println("不同");
}
}
- 类::实例方法
//对象::实例方法名
@Test
public void test1(){
PrintStream printStream=System.out;
Consumer<String> con=(x)->printStream.println(x);
System.out.println("====================使用方法引用====================");
con=printStream::println;
//等价于
con=System.out::println;
con.accept("abcdefg");
}
5.2 构造器引用
在实体类中直接创建一个无参构造方法,可以直接通过构造器引用
//构造器引用
@Test
public void test5(){
Supplier<Employee>supplier=()->new Employee();
//构造引用方法
Supplier<Employee>sup2=Employee::new;
Employee emp=sup2.get();
System.out.println(emp);
}
@Test
public void test6(){
Function<Integer,Employee>function=(x)->new Employee(x);
System.out.println("================使用方法引用=================");
Function<Integer,Employee>fun2=Employee::new;
Employee employee=fun2.apply(50);
System.out.println(employee.toString());
//BiFunction是Function的子类
BiFunction<String,Integer,Employee>biFunction=Employee::new;
Employee employee1=biFunction.apply("张三",100);
System.out.println(employee1.toString());
}
5.3 数组引用
//数组引用
@Test
public void test7(){
Function<Integer,String[]>fun=(x)->new String[x];
String[]strs=fun.apply(10);
System.out.println(strs.length);//20
//使用数组引用
Function<Integer,String[]>fun2=String[]::new;
String[] str2=fun2.apply(20);
System.out.println(str2.length);//20
}