Lambda和函数式接口(复盘)

发布于 2022-06-03  185.7k 次阅读


一,Lambda表达式

一,Lambda表达式引子

用集合模拟数据:

List<User> users = Arrays.asList(
                           new User(1,"吴一",12,5403.83),
                           new User(2, "程二", 18,4504.342),
                           new User(3,"张三",20,12010.21),
                           new User(4,"李四",16,8763.21),
                           new User(5,"王五",19,6091.2));

需求:

  • 获取集合中的年龄大于18的员工信息
  • 获取集合中工资大于6000的员工信息

传统解决方式:对两个需要用两个方法解决

//条件查询类
public class CommonMethod {
       //1,查询年龄大于18的方法
       public List<User> filterAgeUser(List<User> users){
             List<User> agelist = new ArrayList();
             for(User u:users) {
                    if(u.age>18) {
                           agelist.add(u);
                    }}
             return agelist;
       }
       
       //2,查询工资大于6000的方法
       public List<User> filterMoneyUser(List<User> users){
             List<User> agelist = new ArrayList();
             for(User u:users) {
                    if(u.money>6000.0) {
                           agelist.add(u);
                    }}
             return agelist;
       }
}

这个方式是最常规的解决办法,但每一种查询条件都需要兴建一个方法,有点麻烦

① 优化一:使用策略设计模式

//1,接口
public interface MyPredicate<T> {
       public boolean test(T t);
       }
 
//2,判断age的类
public class FilterUserByAge implements MyPredicate<User> {
       @Override
       public boolean test(User user) {
             // TODO Auto-generated method stub
             return user.age>18;
       }
}
 
//3,判断money的类
public class FilterUserByMoney implements MyPredicate<User>{
       @Override
       public boolean test(User user) {
       
             return user.money>5000.0;
       }
}
 
//4,具体调用的类
public class filter{
       
       public List<User> filterUser(List<User> users,MyPredicate<User> filter){
             
             List<User> lists = new ArrayList<>();
             
             for(User u: users ) {
                    if(filter.test(u)){
                           lists.add(u);
                    }
             }
             return lists;
       }
}

用面向对象的方式将行为方式抽象成类,在具体调用的类中传入相应的操作类就会完成相应的操作

  • 优点:增加了拓展性,之后如果有新的需求,只需要实现MyPredicate接口,实现对应的逻辑即可
  • 缺点:每一种条件判断需求都需要新建实现MyPredicate接口的类,代码很冗余

②优化二:匿名内部类

这是对策略优化的进一步优化,这样每一种条件判断需求就不需要新建实体类,而是在内部类进行逻辑判断

//1,接口
public interface MyPredicate<T> {
       public boolean test(T t);
       }
 
//2,具体调用的类
public class filter{
       
       public List<User> filterUser(List<User> users,MyPredicate<User> filter){
             
             List<User> lists = new ArrayList<>();
             
             for(User u: users ) {
                    if(filter.test(u)){
                           lists.add(u);
                    }
             }
             return lists;
       }
}
 
//3,测试
       public static void main(String[] args) {
             
             List<User> users = Arrays.asList(
                           new User(1,"吴一",12,5403.83),
                           new User(2, "程二", 18,4504.342),
                           new User(3,"张三",20,12010.21),
                           new User(4,"李四",16,8763.21),
                           new User(5,"王五",19,6091.2));
             
             List<User> l = new filter().filterUser(users, new MyPredicate<User>() {
                    @Override
                    public boolean test(User t) { //匿名内部类
                           return t.age>18;
                    }
             });
             for(User u:l) {
                    System.out.print(u);
             }}

③ 优化三:Lambda表达式

Lambda表达式简化匿名内部类的写法

1和2没有变
3,测试
       public static void main(String[] args) {
             
             List<User> users = Arrays.asList(
                           new User(1,"吴一",12,5403.83),
                           new User(2, "程二", 18,4504.342),
                           new User(3,"张三",20,12010.21),
                           new User(4,"李四",16,8763.21),
                           new User(5,"王五",19,6091.2));
             
             List<User> l = new filter().filterUser(users, (user) -> user.age>18);
             for(User u:l) {
                    System.out.print(u);
             }}

④ 优化四:Stream流

Stream java8中颠覆性的功能,它将很多繁琐的操作直接一行式完成

public static void main(String[] args) {
             
             List<User> users = Arrays.asList(
                           new User(1,"吴一",12,5403.83),
                           new User(2, "程二", 18,4504.342),
                           new User(3,"张三",20,12010.21),
                           new User(4,"李四",16,8763.21),
                           new User(5,"王五",19,6091.2));
 
             users.stream() //三行代码直接搞定,不需要什么策略模式和类方法设计,
                     .filter((e) -> e.getAge()>18)
                     .forEach(System.out::println);}

Stream是一把利器但有其的使用场景局限性,大多数集合数据操作可以使用它来完成,但宏观的架构和优化还是需要设计模式和类方法设计

二,Lambda基础语法

lambda表达式使用 -> 操作符分成了两个部分

  • 左:lambda表达式的参数列表
  • 右:lambda表达式中所需执行的逻辑,即lambda体

lambda格式:按照参数和返回值区分

① 语法格式一:无参数,无返回值

() -> System.out.print("无返回值")

② 语法格式二:一个参数,无返回值

(x) -> System.out.print(x)

③ 语法格式三:若只有一个参数,小括号可以省略不写

x -> System.out.print(x)

④ 语法格式四:有两个以上的的参数,有返回值,并且lambda体中有多条语句

Comparator<Integer> c = (x,y) -> {
                    System.out.print("函数式接口!");
                    return Integer.compare(x, y);
             };

多条语句用{包裹起来},返回值必须要有return

⑤ 语法格式五:若Lambda体中只有一条语句,return和大括号都可以省略不写

Comparator<Integer> c = (x,y) -> Integer.compare(x, y);

⑥ 语法格式六:Lambda表达式的参数列表的数据类型可以省略不写,因为编译器可以通过上下文进行推断,即"类型推断"

//1,类型推断
(x,y) -> system.out.print(x+"<-->"+y)
//2,写数据类型
(Integer x,Integer y) -> system.out.print(x+"<-->"+y)
//3,错误写法
(Integer x,y) -> system.out.print(x+"<-->"+y)

写数据类型,当假如有多个参数,一个写了其他的也必须写

Lambda的主要作用是简化代码,减少操作,它并不能改变逻辑结构,可以理解为一个代码简洁增强工具

在具体的开发场景中,Lambda需要"函数式接口"的支持,两种结合使用

二,函数式接口

一,四大核心函数式接口

java8提供了四大核心的函数式接口:

① Consumer<T>:消费型接口 --> 有一个参数,无返回值 (T为输入的类型 - 参数类型)

 

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);        
        //默认实现方法,accept进行拼接
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

② Supplier<T>:供给型接口 --> 无参数,有返回值(T为输出的类型 - 返回值的类型)

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

③ Function<T,R>:函数型接口 --> 有参和返回值(T输入类型 R输出类型)

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before)  {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
   
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

④ Predicate<T>:断言型接口 --> 返回值固定为Boolean,有参数(T为输入的类型)

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }}

注:其他非核心的内置的函数式接口还有很多

这四个核心的函数式接口风格迥异,适用于不同的开发场景:

  • consumer:需要输入,但无需返回值的处理
  • supplier:无输入,但有返回值的处理
  • funaction:有输入且有返回值的处理
  • predicate:有输入,针对条件判断的处理

演示:

① 方法

public class FunctionInterface {
       //1,Consumer:对传入的值进行拼接打印
       public void my_consumer(Double number,Consumer<Double> consumer) {
             consumer.accept(number);
       }
       
       //2,Supplier:产生指定个数的整数并放入集合中
       public List<Integer> my_supplier(int limit,Supplier<Integer> supplier){
             
             List<Integer> list = new ArrayList<>();
             for(int x=0;x<limit;x++) {
                    list.add(supplier.get());
             }
             return list;
       }
       
       //3,Function:处理字符串
       public String my_function(String str,Function<String,String> function) {
             
             return function.apply(str);
       }
       
       //4,Predicate:将满足条件的字符串放入集合中
       public List<String> my_prection(String[] str,Predicate<String>  predicate){
             List<String> list = new ArrayList<>();
             for(String s:str) {
                    if(predicate.test(s)) {
                           list.add(s);
                    }
             }
             return list;
       }
}

② 测试

public class FunctionMain {
       public static void main(String[] args) {
       
    FunctionInterface fun = new FunctionInterface();
    
    //1,consumer
    fun.my_consumer(99.9, (x) -> System.out.println("小王花了"+x+"在阿里云上买了个.com域名"));
       
    //2,supplier:返回指定个数的随机数
    List<Integer> list = fun.my_supplier(10, () ->  (int) (Math.random() *  100));
    System.out.println(list.toString());
    
    //3,funaction:对字符串大写返回
    String y = fun.my_function("wql kxj", (x) -> x.toUpperCase());
    System.out.println(y);
    
    //4,predicate:将字符串长度大于5的加入list并返回
    List<String> l=fun.my_prection(new  String[]{"wqlfq","qwertt","tyi","wqr","dasdjkq"}, (x) -> x.length()>5);
    System.out.println(l.toString());
    
}
}

二,其他子函数式接口

java8除了四个核心的函数接口还提供了子函数式接口

序号
接口 & 描述
1
BiConsumer<T,U>
代表了一个接受两个输入参数的操作,并且不返回任何结果
2
BiFunction<T,U,R>
代表了一个接受两个输入参数的方法,并且返回一个结果
3
BinaryOperator<T>
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4
BiPredicate<T,U>
代表了一个两个参数的boolean值方法
5
BooleanSupplier
代表了boolean值结果的提供方
7
DoubleBinaryOperator
代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8
DoubleConsumer
代表一个接受double值参数的操作,并且不返回结果。
9
DoubleFunction<R>
代表接受一个double值参数的方法,并且返回结果
10
DoublePredicate
代表一个拥有double值参数的boolean值方法
11
DoubleSupplier
代表一个double值结构的提供方
12
DoubleToIntFunction
接受一个double类型输入,返回一个int类型结果。
13
DoubleToLongFunction
接受一个double类型输入,返回一个long类型结果
14
DoubleUnaryOperator
接受一个参数同为类型double,返回值类型也为double 。
16
IntBinaryOperator
接受两个参数同为类型int,返回值类型也为int 。
17
IntConsumer
接受一个int类型的输入参数,无返回值 。
18
IntFunction<R>
接受一个int类型输入参数,返回一个结果 。
19
IntPredicate
:接受一个int输入参数,返回一个布尔值的结果。
20
IntSupplier
无参数,返回一个int类型结果。
21
IntToDoubleFunction
接受一个int类型输入,返回一个double类型结果 。
22
IntToLongFunction
接受一个int类型输入,返回一个long类型结果。
23
IntUnaryOperator
接受一个参数同为类型int,返回值类型也为int 。
24
LongBinaryOperator
接受两个参数同为类型long,返回值类型也为long。
25
LongConsumer
接受一个long类型的输入参数,无返回值。
26
LongFunction<R>
接受一个long类型输入参数,返回一个结果。
27
LongPredicate
R接受一个long输入参数,返回一个布尔值类型结果。
28
LongSupplier
无参数,返回一个结果long类型的值。
29
LongToDoubleFunction
接受一个long类型输入,返回一个double类型结果。
30
LongToIntFunction
接受一个long类型输入,返回一个int类型结果。
31
LongUnaryOperator
接受一个参数同为类型long,返回值类型也为long。
32
ObjDoubleConsumer<T>
接受一个object类型和一个double类型的输入参数,无返回值。
33
ObjIntConsumer<T>
接受一个object类型和一个int类型的输入参数,无返回值。
34
ObjLongConsumer<T>
接受一个object类型和一个long类型的输入参数,无返回值。
37
ToDoubleBiFunction<T,U>
接受两个输入参数,返回一个double类型结果
38
ToDoubleFunction<T>
接受一个输入参数,返回一个double类型结果
39
ToIntBiFunction<T,U>
接受两个输入参数,返回一个int类型结果。
40
ToIntFunction<T>
接受一个输入参数,返回一个int类型结果。
41
ToLongBiFunction<T,U>
接受两个输入参数,返回一个long类型结果。
42
ToLongFunction<T>
接受一个输入参数,返回一个long类型结果。
43
UnaryOperator<T>
接受一个参数为类型T,返回值类型也为T。

三,自定义函数式接口

函数式接口的含义:指含有一个抽象方法的接口

场景:函数式接口适用于函数式编程场景。在java中函数式编程的体现就是Lambda,所以函数式接口主要适用于Lambda,一个抽象方法lambda才能顺利推导

@FunctionalInterface注解标注:

  • 放在接口定义的上方,表示该接口是函数式接口
  • 接口有且仅有一个抽象方法

例:

①  标准的有输入和输出

@FunctionalInterface
interface my_fundicate<T,R> { //T输入泛型,R输出泛型
       public R fun(T s);
}

② 有不同类型的输入

@FunctionalInterface
interface my_fundicate<T1,T2,R> { //T1和T2输入泛型,R输出泛型
       public R fun(T1 s1,T2 s2);
}

四,练习小例子

例1:使用自定义函数式接口和Lambda,输入一个字符串,全部转化为大写截取1-3然后输出

① 函数接口

@FunctionalInterface
interface my_fundicate<T,R> {
       public R fun(T s);
}

② 方法和测试

public class ExerciseMain {
       public static void main(String[] args) {
             
             String o =  test1("wql-kxj",(x)->x.toUpperCase().subSequence(0,3).toString());
             System.out.print(o);
       }
       
       public static String test1(String str,my_fundicate<String, String> fun) {
             
             return fun.fun(str);
       }
}

例2:使用自定义函数式接口和Lambda,输入两个Double,将它们相加再返回

① 函数接口

@FunctionalInterface
interface my_fundicate<T,R> {
       public R fun(T a1,T a2);
}

② 方法和测试

public static void main(String[] args) {

             Double o = test1(10.1,10.2,(x,y) -> x+y);

             System.out.print(o);
       }
       
       public static Double test1(Double dou1,Double dou2,my_fundicate<Double,  Double> fun) {
           
             return fun.fun(dou1,dou2);
    }

三,Lambda引用

Lambda引用是一种语法的简化,无特殊含义

一,方法引用

方法引用:若Lambda体中的内容有方法已经实现了,我们可以使用"方法引用"(可以理解为方法引用是Lambda表达式的另外一种表现形式)

方法引用三种语法格式:

  • 对象 :: 实例方法名
  • 类 :: 静态方法名
  • 类 :: 实例方法名

使用方法引用的条件:

  1.   Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致
  2. 若Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用 类名 :: 实例方法名的方式

1,对象 :: 实例方法名

① 演示1

//普通的调用
Consumer<String> con = (x) -> System.out.print(x);
//方法引用  --> Consumer有输入没有输出(有参数没有返回值),System.out.print方法也只有输入没有输出,是匹配的所以能够使用这种方式
Consumer<String> con = System.out::print;
con.accept("wql");

② 演示2

User users = new User(1,"吴一",12,5403.83);
//普通方式获取姓名获取
 Supplier<String> sup1 = () -> users.getName();
//方法引用方式获取(Supplier和users.getName参数列表和返回值一样)
Supplier<String> sup2 = users::getName;

2,类名 :: 静态方法名

//普通方式比较整数大小
Comparator<Integer> com1 = (x,y) -> Integer.compare(x, y);
//方法引用的方式比较
Comparator<Integer> com2 = Integer::compare;

3,类名 :: 实例方法名

这种方式比较特殊

//普通方式两个参数值判断
BiPredicate<String, String> b1 = (x,y) -> x.equals(y);
//方法引用
BiPredicate<String, String> b2 = String::equals; //实际上也是x.equals(y),但它也只适用于这种场景

适应场景:第一个参数是实例方法的调用者,第二个参数是实例方法的参数

二,构造器引用

构造器引用:简化Lambda创建对象的操作

语法格式:类名::new

注:需要调用的构造器的参数列表要和函数式接口中抽象方法的参数列表保持一致

例1:无参构造器创建对象

//Lambda普通方式创建
Supplier<User> sup1 = () -> new User();
//构造方法引用方式创建
Supplier<User> sup2 = User::new;

例2:有参构造器创建对象

//Lambda普通方式创建
BiFunction<Integer, String, User> bf1 = (x,y) -> new User(x,y);
//构造方法引用方式创建
BiFunction<Integer, String, User> bf2 = User::new;

三,数组引用

格式:Type[]::new

注:数组的长度与函数式接口中抽象方法的参数列表是一致的

例:创建String数组

//普通方式
Function<Integer,String[]> sup1 = (x) -> new String[x];
//数组引用
Function<Integer,String[]> sup2 = String[]::new;

路漫漫其修远兮,吾将上下而求索