Lambda表达式总结

Lambda表达式总结
使用范例以及例子
Lambda表达式基础语法
函数式接口
Lambda练习
Java8四大内置函数式接口
方法引用和构造器引用
使用范例以及例子
使用匿名内部类:

Comparator<Integer>com = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {  //降序排列
        return Integer.compare(o2,o1);
    }
};

使用Lambda表达式:

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

1
下面给出一个例子来引入Lambda表达式。

给出一个Employee类,有name、age、salary三个属性:

public class Employee {
    private String name;
    private int age;
    private double salary;

    public Employee() {
    }
    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary;
    }
}

然后我们需要通过限制查询数据:

比如查询年龄>25岁的所有员工的信息;
再如查询工资>4000的员工信息;
首先给出一个List集合类模拟数据库表:

//将数组转换成集合的

List<Employee> employees = Arrays.asList(
    new Employee("张三",23,3333.33),
    new Employee("李四",24,4444.44),
    new Employee("王五",25,5555.55),
    new Employee("赵六",26,6666.66),
    new Employee("田七",27,7777.77)
);

原始方法
然后我们写分别查询出年龄大于25岁的员工信息和工资大于4000的员工信息,发现findEmployeesByAge和findEmployeesBySalary两个方法代码非常的相似,只有查询条件不同,所以这个方法是不太可取的。

public void test3(){
    //年龄
    List<Employee> list = findEmployeesByAge(employees);
    for(Employee emp : list){
        System.out.println(emp);
    }
    //工资
    System.out.println("---------------------");
    List<Employee> list2 = findEmployeesBySalary(employees);
    for(Employee emp : list2){
        System.out.println(emp);
    }
}

//原始方法 : 查询出年龄大于25岁的(这个是最原始的方法)
public List<Employee> findEmployeesByAge(List<Employee>list){
    List<Employee>emps = new ArrayList<>();
    for(Employee emp : list){
        if(emp.getAge() > 25){
            emps.add(emp);
        }
    }
    return emps;
}

//原始方法 : 查询出工资大于4000的(这个是最原始的方法)
//和上面的方法唯一的差别只有年龄和工资的改动,代码冗余

public List<Employee> findEmployeesBySalary(List<Employee>list){
    List<Employee>emps = new ArrayList<>();
    for(Employee emp : list){
        if(emp.getSalary() > 4000){
            emps.add(emp);
        }
    }
    return emps;
}

优化方式一-使用策略模式来优化
策略模式需要行为算法族,于是我们创建查询行为的接口MyPredicate<T>:

public interface MyPredicate <T>{
    public boolean test(T t);
}

并创建相关的实现类代表不同的算法行为: (分别是年龄> 25和工资> 4000的 ):

public class FilterEmployeeByAge implements MyPredicate<Employee> {
    @Override
    public boolean test(Employee employee) {
        return  employee.getAge() > 25;
    }
}
public class FilterEmployeeBySalary implements MyPredicate<Employee>{
    @Override
    public boolean test(Employee employee) {
        return employee.getSalary()  >= 4000;
    }
}

这时我们可以只需要创建通用的方法: 具体的调用只需要传入具体的实现类(接口作为参数)

public List<Employee> filterEmployees(List<Employee>list,MyPredicate<Employee>mp){
    List<Employee>emps = new ArrayList<>();
    for(Employee emp : list){
        if(mp.test(emp)){  //调用相应的过滤器
            emps.add(emp);
        }
    }
    return emps;
}

测试的时候就传入两个不同的类,来指定查询的行为

优化方式一 : 使用策略设计模式进行优化 下面的方法只要写一个

public void test4(){
    List<Employee> list = filterEmployees(this.employees, new FilterEmployeeByAge());
    for(Employee emp : list){
        System.out.println(emp);
    }
    System.out.println("------------------------");
    List<Employee> list2 = filterEmployees(this.employees, new FilterEmployeeBySalary());
    for(Employee emp : list2){
        System.out.println(emp);
    }
}

优化方式二-使用匿名内部类优化
这样的好处在于不需要创建接口的具体的实现类,(但是还是需要MyPredicate接口和filterEmployees()方法):

优化方式二 : 使用匿名内部类 这样的好处是不要创建一个额外的 策略类

public void test5(){
    List<Employee> list = filterEmployees(this.employees, new MyPredicate<Employee>() {
        @Override
        public boolean test(Employee employee) {
            return employee.getSalary() > 4000;
        }
    });
    for (Employee emp:list) {
        System.out.println(emp);
    }
}

优化方式三-使用Lambda表达式
省去匿名内部类的没用的代码,增强可读性:(注意还是需要那个filterEmployees方法)

public void test6(){
    List<Employee> list = filterEmployees(this.employees, (e) -> e.getSalary() > 4000);
    list.forEach(System.out::println);
}

优化方式四-使用Stream-API
使用StreamAPI完全不需要其他的代码,包括不需要filterEmployees()方法,代码很简洁:

public void test7(){
    employees.stream().filter( (e) -> e.getSalary() < 4000 ).limit(2).forEach(System.out::println);
    System.out.println("------------------");
    employees.stream().map(Employee::getName).forEach(System.out::println); //打印所有的名字
}

Lambda表达式基础语法

关于箭头操作符:

Java8中引入了一个新的操作符,"->",该操作符称为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分;
左侧: Lambda表达式的参数列表,对应的是接口中抽象方法的参数列表;
右侧: Lambda表达式中所需要执行的功能(Lambda体),对应的是对抽象方法的实现;(函数式接口(只能有一个抽象方法))
Lambda表达式的实质是 对接口的实现;

语法格式:

(一) 接口中的抽象方法 : 无参数,无返回值;

例如: Runnable接口中的run方法:

   public void test1(){
        /*final */int num = 2; //jdk1.7之前必须定义为final的下面的匿名内部类中才能访问

        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello world!" + num); //本质还是不能对num操作(只是jdk自己为我们设置成了final的)
            }
        };
        r.run();

        System.out.println("----------使用Lambda输出-----------");

        Runnable r1 = () -> System.out.println("Hello world!" + num);
        r1.run();
    }

(二) 接口中的抽象方法 : 一个参数且无返回值; (若只有一个参数,那么小括号可以省略不写)

public void test2(){
//  Consumer<String>con = (x) -> System.out.println(x);
    Consumer<String>con = x -> System.out.println(x);
    con.accept("Lambda牛逼!");
}

(三) 两个参数,有返回值,并且有多条语句 : 要用大括号括起来,而且要写上return

public void test3(){
     Comparator<Integer>com = (x,y) -> {
         System.out.println("函数式接口");
         return Integer.compare(y,x); //降序
     };
     
     Integer[] nums = {4,2,8,1,5};
     Arrays.sort(nums,com);
     System.out.println(Arrays.toString(nums));
}

输出:

函数式接口
函数式接口
函数式接口
函数式接口
函数式接口
函数式接口
函数式接口
函数式接口
函数式接口
[8, 5, 4, 2, 1]

(四) 两个参数,有返回值,但是只有一条语句: 大括号省略,return省略

public void test4(){
     Comparator<Integer>com = (x,y) -> Integer.compare(x,y);//升序
     Integer[] nums = {4,2,8,1,5};
     Arrays.sort(nums,com);
     System.out.println(Arrays.toString(nums));
 }

输出:

[1, 2, 4, 5, 8]

(五) Lambda表达式的参数列表的数据类型 可以省略不写,因为JVM编译器通过上下文推断出数据类型,即"类型推断"(Integer x,Integer y ) -> Integer.compare(x,y)可以简写成(x,y) -> Integer.compare(x,y);

上联: 左右遇一括号省
下联: 左侧推断类型省
横批: 能省则省

函数式接口

若接口中只有一个抽象方法的接口称为函数式接口;
可以使用注解@FunctionlInterface来标识,可以检查是否是函数式接口;
例子: 对一个进行+-*/的运算:

函数式接口:

@FunctionalInterface //函数式接口
public interface MyFunction {
    public Integer getValue(Integer num);
}

通用函数:

public Integer operation(Integer num,MyFunction mf){
    return mf.getValue(num);
}

测试:

public void test5(){
     Integer res = operation(200, (x) -> x * x);
     System.out.println(res);
 }

Lambda练习

练习一-Employee类中先按年龄比,年龄相同按照姓名比-都是升序

先给出集合,模拟数据库表:

List<Employee> employees = Arrays.asList(
        new Employee("田七",27,7777.77),
        new Employee("王五",24,5555.55),
        new Employee("张三",23,3333.33),
        new Employee("李四",24,4444.44),
        new Employee("赵六",26,6666.66)
);
public void test1(){
   Collections.sort(employees,(x,y) ->{
       if(x.getAge() == y.getAge()){
           return x.getName().compareTo(y.getName());
       }else{
           return Integer.compare(x.getAge(),y.getAge());
       }
   });

   for (Employee emp: employees) {
       System.out.println(emp);
   }
}

输出:

name='张三', age=23, salary=3333.33
name='李四', age=24, salary=4444.44
name='王五', age=24, salary=5555.55
name='赵六', age=26, salary=6666.66
name='田七', age=27, salary=7777.77

练习二-声明一个带两个泛型的接口,并且对两个Long型数值计算

@FunctionalInterface
public interface MyCalFunction<T,R> {
    public R getValue(T t1,T t2); 
}

对应函数和测试:

 public void test3(){
     op(200L,200L,(x,y) -> x + y);
     op(200L,200L,(x,y) -> x * y);
 }
 public void op(Long l1,Long l2,MyCalFunction<Long,Long>mc){//需求: 对于两个long型运算进行处理
     System.out.println(mc.getValue(l1, l2));
 }

更多的例子: (取自<<Java8实战>>)


1

![image.png](https://upload-images.jianshu.io/upload_images/4028253-98e3c7b85bf4ccf5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
2

(注意类型可以省略(类型推导))。


3

上图的Apple类:

public class Apple {
    public String color;
    public int weight;

    public Apple() {
    }
    public Apple(String color, int weight) {
        this.color = color;
        this.weight = weight;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }
}

Java8四大内置函数式接口

我们发现,如果使用Lambda还要自己写一个接口的话太麻烦,所以Java自己提供了一些接口:

Consumer< T >con 消费性 接口: void accept(T t);
Supplier< T >sup供给型接口 : T get();
Function< T , R >fun 函数式接口 : R apply (T t);
Predicate< T >: 断言形接口 : boolean test(T t);
Consumer< T >con消费性接口-void accept(T t)
@Test
public void test1(){
    apply(1000,(num) -> System.out.println("消费了" + num + "元!"));
}
public void apply(double num,Consumer<Double>con){
    con.accept(num);
}

Supplier< T >sup供给型接口-T get()
例子: 产生指定个数的整数,并放入集合中;

public void test2(){
    ArrayList<Integer> res = getNumList(10, () -> (int) (Math.random() * 100));
    System.out.println(res);
}

//需求,产生指定个数的整数,并放入集合中

public ArrayList<Integer> getNumList(int num, Supplier<Integer>sup){
    ArrayList<Integer>list = new ArrayList<>();
    for(int i = 0; i < num; i++){
        Integer e = sup.get();
        list.add(e);
    }
    return list;
}

Function< T, R >fun函数式接口-R apply (T t)

public void test3(){
    String newStr = strHandler("abc", (str) -> str.toUpperCase());
    System.out.println(newStr);
    newStr = strHandler("   abc  ", (str) -> str.trim());
    System.out.println(newStr);
}
public String strHandler(String str, Function<String,String>fun){
    return fun.apply(str);
}

Predicate< T >断言形接口-boolean test(T t)
判断一些字符串数组判断长度>2的字符串:

public void test4(){
    List<String> list = Arrays.asList("Hello", "atguiu", "lambda", "ok", "www", "z");
    List<String> res = filterStr(list, (str) -> str.length() > 2);
    System.out.println(res);
}
//需求
public List<String> filterStr(List<String>list, Predicate<String>pre){
    ArrayList<String>res = new ArrayList<>();
    for(String str : list){
        if(pre.test(str)){
            res.add(str);
        }
    }
    return res;
}

方法引用和构造器引用
方法引用
使用前提: Lambda体中调用方法的参数列表和返回值类型,要和函数式接口中抽象方法的参数列表和返回值类型保持一致;

语法格式(一) 对象::实例方法名

public void test1(){
    //普通写法
     PrintStream ps = System.out;
     Consumer<String>con = (x) -> ps.println(x);
     con.accept("hello !");

     System.out.println("----------------------");
    //简写
     Consumer<String>con1 = ps::println;
     con1.accept("hello ! ");

     System.out.println("----------------------");
    //更简单的写法
     Consumer<String>con2 = System.out::println;
     con2.accept("hello ! ");
}

注意,这样写的前提: Consumer中的accept()方法和println()方法的参数列表和返回类型要完全一致:

image

image

再看一个例子:
三种写法的效果是一样的:

public class TestLambda {

    public static void main(String[] args) {

        // method 1
        Consumer<String> consumer = s -> System.out.println(s);
        useConsumer(consumer,"123");

        //method 2
        useConsumer(s -> System.out.println(s),"123");

        //method3   method reference (方法引用)
        useConsumer(System.out::println,"123"); //因为println和 accept 是同样的只有一个入参,没有返回值
    }

    public static <T> void useConsumer(Consumer<T> consumer,T t){
        consumer.accept(t);
    }
}

再看一个例子:

public static void main(String[] args) {
    //都是输出 字符 'l'
    BiFunction<String,Integer,Character> bf = String::charAt; //这里第一个必须传入 String
    Character c = bf.apply("hello,", 2);
    System.out.println(c);

    //注意这里使用的是Function 接口
    String str = new String("hello");
    Function<Integer,Character> f = str::charAt; //这里不需要String
    Character c2 = f.apply(2);
    System.out.println(c2);
}

再看一个例子:

public void test2(){
    Employee emp = new Employee("zx",23,5555);

    Supplier<String>sup = () -> emp.getName();
    System.out.println(sup.get());

    //简写
    Supplier<String>sup2 = emp::getName;
    System.out.println(sup2.get());
}
image

image

语法格式(二) 类名::静态方法

public void test3(){
     Comparator<Integer>com = (x,y) -> Integer.compare(x,y);

     Comparator<Integer>com2 = Integer::compare;
}

Integer类中的


image

)

Comparator接口中的方法:


image

语法格式(三) 类::实例方法名
使用注意: 若Lambda参数列表中的第一个参数是实例方法的第一个调用者,而第二个参数是实例方法的参数时,可以使用ClassName :: method。

public void test4(){
    BiPredicate<String,String>bp = (x,y) -> x.equals(y);

    BiPredicate<String,String>bp2 = String::equals;
}

构造器引用
需要调用构造器的参数列表,要与函数式接口中的抽象方法的参数列表保持一致;

public void test5(){
    Supplier<Employee>sup = () -> new Employee();

    Supplier<Employee>sup2 = Employee::new; //调用的是默认的
    System.out.println(sup2.get());
}

输出:

name='null', age=0, salary=0.0
image

image

再看构造器一个参数的:

public void test6(){
    Function<String,Employee>fun = Employee::new;
    System.out.println(fun.apply("zx"));
}

输出:

name='zx', age=0, salary=0.0

如果想要匹配多个的,(两个的可以使用BiFunction),下面看一个三个的:
例如想匹配这个:

public class ComplexApple {

    private String name;
    private int weight;
    private String color;

    public ComplexApple() {
    }

    //匹配这个构造方法
    public ComplexApple(String name, int weight, String color) {
        this.name = name;
        this.weight = weight;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

自己建一个接口:

@FunctionalInterface
public interface ThreeFunction<A,B,C,R> {
    R apply(A a,B b,C c);
}

测试:

public class Test {

    public static void main(String[] args) {

        ThreeFunction<String,Integer,String,ComplexApple> tf = ComplexApple::new;

        ComplexApple apple = tf.apply("蓝色", 12, "好苹果");
        
    }
}

————————————————
版权声明:本文为CSDN博主「zxzxin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zxzxzx0119/java/article/details/82392396

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354