Java 8 Lamda表达式

1. 问题引入

为了引入Lambda表达式这个主题,首先以一个常用的查询业务来开始主题。假设,我们设计了一个业务类Employee,来表征员工管理的简单业务。我们需要从姓名、年龄、收入来筛选不同的员工。

1.1 基础处理方式

可以发现filterEmployeeBySalary()方法是一个遍历Employee数组中的每一个对象并筛选Employee的业务逻辑。通过代码简单发现,这个方法筛选工资大于20000的Employee。但是这种方法的问题是,每增加一种筛选方式,需要创建一个新的方法,非常麻烦。

1.2 设计模式的改进方式

为了防止每次新增一种查询方式,就需要增加一个方法定义的开发,可以采用策略设计模式方式,新建一个接口,在接口实现中完成筛选Employee的业务逻辑。如方法filterEmployeeByImp()的定义,该方法具有一定的通用性,在方法参数中加入了接口FilterBusi这个参数,那么在调用这个方式时,只要传给这个方法具体的实现类(具体定义了业务筛选逻辑)即可。

 List<Employee> emps1 = 
test.filterEmployeeByImp(employees,new MyFilterEmployeeByAge());

但是,仔细分析,这种方式的缺点也是显而易见,那就是每实现一个业务筛选逻辑,需要增加一个方法定义。

1.3 使用匿名内部类进一步改进

因为,每次新增加一种业务筛选逻辑,需要自定义一个新的业务逻辑实现类,这种方式并不友好。为了减少新建类的数量,可以使用匿名内部类的方式实现。例如下例中使用匿名内部类实现接口FilterBusi<Employee>:

List<Employee> emps2 = test.filterEmployeeByImp(employees, new FilterBusi<Employee>() {
            @Override
            public boolean filterBusi(Employee employee) {
                return employee.getSalary()<6000;
            }
        });

1.4 使用Lambda表达式

Lambda表达式可以极大减少匿名内部类的编码量,在上边的业务逻辑,使用Lambda表达式可以简单表示如下:
List<Employee> emps3 = test.filterEmployeeByImp(employees,(e) -> e.getSalary()<2000);

业务接口FilterBusi.java

package java8;

public interface FilterBusi<T> {
    boolean filterBusi(T t);
}

接口FilterBusi的一个实现类MyFilterEmployeeByAge.java

package java8;

public class MyFilterEmployeeByAge implements FilterBusi<Employee> {
    @Override
    public boolean filterBusi(Employee employee) {
        return employee.getAge()<35;
    }
}

主要业务类: LambdaTest.java

package java8;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class LambdaTest {
    /**
     * 使用传统方法形式,该方法不能复用,一个业务逻辑需要创建一个方法。
     * @param employee
     * @return
     */
    public List<Employee> filterEmployeeBySalary(List<Employee> employee){
        List<Employee> emps = new ArrayList<Employee>();
        for (Employee emp : employee){
            if(emp.getSalary()>20000){
                emps.add(emp);
            }
        }
        return emps;
    }

    /**
     * 使用实现接口定义的方式来实现,该方法可以进行复用。用接口  FilterBusi mg 来筛选。
     * @param employee
     * @param mg
     * @return
     */
    public List<Employee> filterEmployeeByImp(List<Employee> employee
            , FilterBusi<Employee> mg){
        List<Employee> emps = new ArrayList<Employee>();
        for (Employee emp : employee){
            if(mg.filterBusi(emp)){    //抽象出一个通用形式
                emps.add(emp);
            }
        }
        return emps;
    }

    public static void main(String[] args) {
        LambdaTest test = new LambdaTest();
        List<Employee> employees = Arrays.asList(
                new Employee("Joe",28000,31),
                new Employee("Lucy",5000,18),
                new Employee("Robin",400,7),
                new Employee("Cristina",40000,35),
                new Employee("Fancy",18000,55),
                new Employee("Lion",4000,26)
        );
        //列表全集
        for(Employee emp:employees){
            System.out.println(emp);
        }
        System.out.println("-------------------------------------------");

        //传统方法,采取增加方法实现的方法来实现业务扩展
        List<Employee> emps = test.filterEmployeeBySalary(employees);
        for(Employee emp:emps){
            System.out.println(emp);
        }
        System.out.println("-------------------------------------------");

        //设计模式 策略模式  缺点是:每次我都需要事先创建好一个类似于MyFilterEmployeeByAge这样的类
        List<Employee> emps1 = test.filterEmployeeByImp(employees,new MyFilterEmployeeByAge());
        for(Employee emp:emps1){
            System.out.println(emp);
        }
        System.out.println("-------------------------------------------");

        //这次复用之前创建的方法filterEmployeeByImp(根据接口来筛选)
        List<Employee> emps2 = test.filterEmployeeByImp(employees, new FilterBusi<Employee>() {
            @Override
            public boolean filterBusi(Employee employee) {
                return employee.getSalary()<6000;
            }
        });
        for(Employee emp:emps2){
            System.out.println(emp);
        }
        System.out.println("-------------------------------------------");

        //使用lambda表达式来实现
        List<Employee> emps3 = test.filterEmployeeByImp(employees,(e) -> e.getSalary()<2000);
        for(Employee emp:emps3){
            System.out.println(emp);
        }
        System.out.println("-------------------------------------------");
    }
}

2. Lambda表达式语法

Java8引入新的语法符号 -> ,该操作符称为箭头操作符或 Lambda 操作符。
箭头操作符将 Lambda 表达式拆分成两部分:
符号->左边: Lambda 表达式的参数列表
符号->右边: Lambda 表达式中所需执行的功能, 即 Lambda 体
Lambda表达式通常包含4种语法格式:
语法格式一: 没有参数,没有返回值。(Runnable接口为例)
() -> System.out.println("Lambda Expression without para!");
语法格式二: 一个参数,没有返回值。(java.util.function.Consumer接口为例)
(x) -> System.out.println(x);
如果只有一个参数,可以将括号去掉,x -> System.out.println(x);
语法格式三: 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句 (java.util.Consumer接口为例)
Comparator<Integer> comp = (x,y) -> {
System.out.println("Comparing "+x+" "+y+":");
return Integer.compare(x, y);
};
语法格式四: 如果只有一条语句(有返回值),可以省略大括号 和 return

下面的例子 LambdaBasicPro.java 演示了4种表达方式:

package java8;

import java.util.Comparator;
import java.util.function.Consumer;
import org.junit.Test;

/***
 * Lambda表达式的语法
 *   函数式接口:只有一个方法定义的接口。Lambda表达式针对实现函数式接口。
 *   
 *   Java8引入新的语法符号 -> ,该操作符称为箭头操作符或 Lambda 操作符
 *   箭头操作符将 Lambda 表达式拆分成两部分:
 *      符号->左边: Lambda 表达式的参数列表
 *      符号->右边: Lambda 表达式中所需执行的功能, 即 Lambda 体
 *   
 *   语法格式一: 没有参数,没有返回值。(Runnable接口为例)
 *      () -> System.out.println("Lambda Expression without para!");
 *   
 *   语法格式二: 一个参数,没有返回值。(java.util.function.Consumer接口为例)
 *      (x) -> System.out.println(x);
 *      如果只有一个参数,可以将括号去掉,x -> System.out.println(x);
 *      
 *   语法格式三: 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句 (java.util.Consumer接口为例)
 *      Comparator<Integer> comp = (x,y) -> {
 *          System.out.println("Comparing "+x+" "+y+":");
 *          return Integer.compare(x, y);
 *      };
 *    
 *    语法格式四: 如果只有一条语句(有返回值),可以省略大括号 和 return
 *   
 * 
 * @author natty
 */

public class LambdaBasicPro {
    
    @Test
    public void test01(){
        Runnable ra = () -> System.out.println("Lambda Expression without para!");
        ra.run();
    }
    
    @Test
    public void test02() {
        Consumer<Integer> con = x -> System.out.println(x);
        con.accept(20);
    }
    
    @Test 
    public void test03() {
        Comparator<Integer> comp = (x,y) -> {
            System.out.println("Comparing "+x+" "+y+":");
            return Integer.compare(x, y);
        };
        System.out.println(comp.compare(1000, 100));
    }
    
    @Test
    public void test04() {
        Comparator<Integer> comp = (z,t) -> Integer.compare(z, t);
        System.out.println(comp.compare(1000, 100));
    }
    
    @Test
    public void test8() {
        String str  = "{\"category\":\"住宿服务;宾馆酒店;五星级宾馆\",\"idx\":0,\"queryId\":\"B00155MTKJ\",\"requestId\":\"8cf273c9c65f44f98c9d73f59ca1d0c7\",\"searchType\":2,\"source\":\"MAP_GD_SEARCH\",\"subIdx\":-1,\"timestamp\":1544025600000,\"type\":4003}";
        System.out.println(str);
    }
}

3. Lambda表达式练习

下面实现3个关于lambda表达式的练习,在上边的注释上写明了需求。

LambdaPractice.java

package com.pmpa.Collections;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.junit.Test;

public class LambdaPractice {
    
    List<Employee> emplist = Arrays.asList(
            new Employee("Joe",4800,23)
            ,new Employee("Lucas",8200,32)
            ,new Employee("Cindy",5000,25)
            ,new Employee("Cristina",5000,34)
            ,new Employee("Lucifer",24000,28)
            ,new Employee("Jim",60000,37)
    );
    
    
    /**
     * 首先根据工资排序,工资一样的根据名字排序。
     */
    @Test
    public void test1() {
        Collections.sort(emplist, (e1,e2) -> {
            if(e1.getSalary() == e2.getSalary())
            {
                return e1.getName().compareTo(e2.getName());
            }
            else
            {
                return Float.compare(e1.getSalary(), e2.getSalary()) ; 
            }
        });
        for(Employee emp:emplist) {
            System.out.println(emp);
        }
    }
    
    /*
     * 声明函数式接口,接口中声明抽象方法,
     * 使用函数式接口实现字符串转化为大写,并且截取字符串。
     */
    @Test
    public void test2() {
        String str = "aboisfqf";
        GetString gs = x -> {
            String lucifer = x.toUpperCase();
            return lucifer.substring(2, 4);
        };
        System.out.println(gs.getValue(str));
    }
    
    /**
     * 创建函数式接口 fuc(T,R)
     * 计算2个整数的和 和两个整数的乘积。
     */
    @Test
    public void test3() {
        FunctionDemo<Integer,Integer> fd = (x,y) -> x + y;
        System.out.println(fd.getVal(20, 340));
        FunctionDemo<Integer,Integer> fd1 = (x,y) -> x*y;
        System.out.println(fd1.getVal(90, 2));
    }
}

声明函数式接口 FunctionDemo.java

package com.pmpa.Collections;

public interface FunctionDemo<T,R> {
    public R getVal(T t1,T t2);
}

4. Java 8自带函数式接口

从上边的练习看出,在每实现一个需求时,都需要创建一个接口,这其实是很麻烦的事情。在java 8中, rt.jar中的java.util.function包中,给我们提供了需要的函数式接口,直接使用就行,不用再新声明函数式接口了。
其中最核心的4个函数式接口分别是:
1.消费型接口 Consumer<T>:有输入参数,但是无返回值,有来无回 void accept(T t)
2.供给型接口 Supplier<T>: 没有输入参数,但是有返回值 T get()
3.函数型接口 Function<T,R> : 有一个参数,有一个返回值 R apply(T t)
4.断言型接口 Predicate<T> :有一个参数,返回布尔值结果 boolean test(T t)
上边核心接口基本都提供了一个输入参数泛型,如果参数不够时,还可以使用类似BiFunction等扩展的接口,这个包内都是接口,可以查看源码来确定使用。下边的例子是说明函数式接口的使用方法:
MainFunctionInterface.java

package java8;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.junit.Test;

/**
 * 
 * @author natty
 * Java中内置了一些函数式接口 :
 * 1. 消费型接口  Consumer<T>:消费型接口
 *  有输入参数,但是无返回值,有来无回    void accept(T t)
 * 
 * 2. 供给型接口  Supplier<T>: 供给型接口
 *  没有输入参数,但是有返回值      T get()              T 
 * 
 * 3. 函数型接口  Function<T,R>  
 *  有一个参数,有一个返回值  R apply(T t)
 *  
 * 4. 断言型接口  Predicate<T>
 * 有一个参数,返回布尔值结果       boolean test(T t)
 * 
 */

public class MainFunctionInterface {
    
    //消费型接口测试。
    public void testConsumer(String test, Consumer<String> con)
    {
        con.accept(test);
    }
    
    //测试供给型接口
    public String testSupplier(Supplier<String> sup) {
        return sup.get();
    }
    
    //函数型接口
    public String testFunction(Integer ls , Function<Integer,String> func) {
        return func.apply(ls);
    }
    
    //断言型接口
    public List<String> testPredicate(List<String> li , Predicate<String> pre){
        List<String> re = new ArrayList<String>();
        for(String str: li ) {
            if(pre.test(str)) re.add(str);
        }
        return re;
    }
    
    @Test
    public void test1() {
        testConsumer("vipkid",(x) -> System.out.print("The choise for " + x));
    }
    
    @Test
    public void test2() {
        System.out.println(testSupplier(() -> "abc"));
    }
    
    @Test
    public void test3() {
        String phnics = testFunction(2000,(x) -> "The dollar using"+x);
        System.out.println(phnics);
    }
    
    @Test
    public void test4() {
        List<String> phs = Arrays.asList("US-Harvard", "UK-Cambrige" , "US-Yale" , "JP-Tokyo" , "US-Duke"); 
        List<String> output = testPredicate(phs,(x) -> x.substring(0, 2).equals("US"));
        output.forEach(System.out::println);
    }
}

5. 方法引用和构造器应用

若Lambda体中的内容已经有方法实现了,就可以使用“方法引用”,也可以理解为: 方法引用是Lambda表达式的另外一种表达形式。

5.1 方法引用

主要有三种语法格式:
1.对象::实例方法名
2.类::静态方法名
3.类::实例方法名
使用方法引用需要有一个前提,前提就是Lambda表达式实现的接口的参数列表和返回值类型需要与Lambda表达式中的方法的参数列表和返回值一致。

需要注意以下2点:
1.Lambda体中调用方法的参数列表和返回值类型 要与函数式接口中抽象方法的函数列表和返回值类型保持一致。
2.若Lambda参数列表中的第一参数是实例方法(equals)的调用者,第二个参数是实例方法的参数时,可以使用ClassName::method

5.2构造器引用

语法: ClassName::new
需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。

5.3 数组引用

语法:type::new ;
下边的类是一个实例代码

package java8;

import java.util.Comparator;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;

import org.junit.Test;

/**
 * Lambda体中调用方法的参数列表和返回值类型 要与函数式接口中抽象方法的函数列表和返回值类型保持一致。
 * 
 * @author natty
 *
 */

public class MethodRef {
    
    // 对象::实例方法  方法引用
    @Test
    public void test1() {
        Employee emp = new Employee("Honda",23,5000);
        Supplier<String> sup = () -> emp.getName();
        // Lambda体中的内容emp的getName方法已经实现了,可以使用方法引用
        Supplier<String> sup2 = emp::getName;
        System.out.println(sup2.get());     
    }
    
    // 类::静态方法   方法引用
    @Test
    public void test2() {
        //compare()方法是Integer类的一个静态方法   lambda体的实现使用Integer.compare方法实现。
        Comparator<Integer> com = Integer::compare;
        Comparator<Integer> com1 = (x,y) -> Integer.compare(x, y);
        System.out.println(com.compare(10, 200));
    }
    
    // 类:: 实例方法   方法引用
    @Test
    public void test3() {
        BiPredicate<String,String> bip = (x,y) -> x.equals(y);
        //使用类::实例方法  方式来简化
        //若Lambda参数列表中的第一参数是实例方法(equals)的调用者,第二个参数是实例方法的参数时,可以使用ClassName::method
        BiPredicate<String,String> bip2 = String::equals;
        System.out.println(bip.test("Juventus", "Juventus"));
    }
    
    //构造器应用
    @Test
    public void test4() {
        //使用函数式接口创建对象
        Supplier<Employee> sup = () -> new Employee();
        //因为Supplier接口的方法是无参有返回值,那么对应的Employee::new 也会使用Employee的无参构造器
        Supplier<Employee> sup1 = Employee::new;
        Employee emp1 = sup1.get();
        System.out.println(emp1);
        
        //func是有一个参数,一个返回值的函数式接口,那会调用Employee包含一个参数的构造方法。
        Function<String,Employee> func = (c) -> new Employee(c);
        Function<String,Employee> func2 = Employee::new;
        Employee sg = func2.apply("Liverpool");
        System.out.println(sg);
    }
    
    //数组应用
    @Test 
    public void test5() {
        Function<Integer,String[]> func = (x) -> new String[x]; 
        Function<Integer,String[]> func2 = String[]::new; 
        
        String[] chelsea = func2.apply(20);
        System.out.println(chelsea.length);
    }

}

======== 返回目录 ========
》》》 下一篇 Stream API

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

推荐阅读更多精彩内容