敞开生长之旅!这是我参与「日新计划 2 月更文挑战」的第 12 天,点击检查活动概况


前言

Lambda表达式是JDK8的一个新特性,能够替代大部分的匿名内部类,写出更优雅的Java代码,尤其在调集的遍历和其他调集操作中,能够极大地优化代码结构

JDK也供给了很多的内置函数式接口供咱们运用,使得Lambda表达式的运用愈加便利、高效

一、什么是Lambda表达式

Lambda表达式,也称为闭包java8的新特性,lambda运行将函数作为一个办法的参数,也便是将函数作为参数传递到办法中。运用lambda表达式能够让代码愈加简洁。

Lambda表达式常用于简化接口完结,关于接口完结,能够有很多种办法。例如:

  1. 创立接口的完结类;
  2. 运用匿名内部类;

可是lambda表达式,比这两种办法都简单。代码示例如下:

interface TestInterface{
    public void testFun();
}
public class TestClass {
    public static void main(String[] args) { 
        //运用lambda表达式完结接口
        TestClass test = () -> {
            System.out.println("test");
        };
        test.testFun();
    }
}

二、运用前提

上文中说到,lambda表达式能够在⼀定程度上简化接口的完结。可是,并不是所有的接口都能够运用lambda表达式来简化接口的完结的。

先说结论,lambda表达式,只能完结函数式接口lambda表达式究竟仅仅⼀个匿名办法

三、函数式接口

3.1 概念

函数式接口在 Java 中是指: 有且仅有一个笼统办法的接口

函数式接口,即适用于函数式编程场景的接口。而 Java 中的函数式编程体现便是Lambda,所以函数式接口便是能够适用于Lambda运用的接口。只要确保接口中有且仅有一个笼统办法,Java中的 Lambda才干顺畅地进行推导。

补白:

语法糖 是指运用愈加便利,可是原理不变的代码语法。

例如:

在遍历调集时运用的for-each语法,其实底层的完结原理仍然是迭代器,这便是语法糖。从运用层面来讲, Java 中的 Lambda 能够被作为是匿名内部类的语法糖,可是二者在原理上是不同的。

3.2 格局

只需确保接口中有且仅有一个笼统办法即可,伪代码如下:

修饰符 interface 接口称号 {
    public abstract 回来值类型 办法称号(可选参数信息);
    // 其它非笼统办法内容
}

由于接口当中笼统办法的public abstract是能够省掉的,所以界说一个函数式接口很简单:

public interface TestFunctionalInterface {
    void testMethod();
}

3.3 @FunctionalInterface

@Override 注解的效果类似, Java 8 中专门为函数式接口引入了一个新的注解:@FunctionalInterface。该注解可用于一个接口的界说上:

@FunctionalInterface
public interface TestFunctionalInterface {
    void testMethod();
}

一旦运用该注解来界说接口,编译器将会强制检查该接口是否确实有且仅有一个笼统办法,不然将会报错。需求注意的是,即便不运用该注解,只需满意函数式接口的界说,这仍然是一个函数式接口,运用起来都一样。

3.4 自界说函数式接口

关于刚刚界说好的 MyFunctionalInterface 函数式接口,典型运用场景便是作为办法的参数:

public class TestFunctionalClass {
    // 运用自界说的函数式接口作为办法参数
    private static void doSomeThing(TestFunctionalInterface testInterFace){
        testInterFace.testMethod();
    }
    public static void main(String[] args) {
        // 调用函数式接口的办法
        doSomeThing(() -> System.out.println("Hello world!"));
    }
}

四、语法格局

4.1 基础语法

lambda表达式,其实实质来讲,便是⼀个匿名函数。因此在写lambda表达式的时分,不需求关怀办法名是什么。

实际上,咱们在写lambda表达式的时分,也不需求关怀回来值类型,只需求重视两部分内容即可:参数列表和办法体

(参数1,参数2,…) -> {

​ 办法体

};

各部分详述

参数部分:办法的参数列表,要求和完结的接口中的办法参数部分⼀致,包括参数的数量和类型。

办法体部分 : 办法的完结部分,假如接口中界说的办法有回来值,则在完结的时分,注意回来值的回来。

-> : 分隔参数部分和办法体部分。

总结来说:语法方式为 () -> {},其中 () 用来描绘参数列表,{} 用来描绘办法体,-> 为 lambda运算符 ,读作(goes to)。

4.2 重要特征

  • **可选类型声明:**不需求声明参数类型,编译器能够统一辨认参数值。
  • **可选的参数圆括号:**一个参数无需界说圆括号,但多个参数需求界说圆括号。
  • **可选的大括号:**假如主体包含了一个句子,就不需求运用大括号。
  • **可选的回来关键字:**假如主体只要一个表达式回来值则编译器会自动回来值,大括号需求指定表达式回来了一个数值。

代码示例:

public class Demo01 {
    /**多参数无回来*/
    @FunctionalInterface
    public interface NoReturnMultiParam {
        void method(int a, int b);
    }
    /**无参无回来值*/
    @FunctionalInterface
    public interface NoReturnNoParam {
        void method();
    }
    /**一个参数无回来*/
    @FunctionalInterface
    public interface NoReturnOneParam {
        void method(int a);
    }
    /**多个参数有回来值*/
    @FunctionalInterface
    public interface ReturnMultiParam {
        int method(int a, int b);
    }
    /*** 无参有回来*/
    @FunctionalInterface
    public interface ReturnNoParam {
        int method();
    }
    /**一个参数有回来值*/
    @FunctionalInterface
    public interface ReturnOneParam {
        int method(int a);
    }
    public static void main(String[] args) {
        //无参无回来
        NoReturnNoParam noReturnNoParam = () -> {
            System.out.println("NoReturnNoParam");
        };
        noReturnNoParam.method();
        //一个参数无回来
        NoReturnOneParam noReturnOneParam = (int a) -> {
            System.out.println("NoReturnOneParam param:" + a);
        };
        noReturnOneParam.method(6);
        //多个参数无回来
        NoReturnMultiParam noReturnMultiParam = (int a, int b) -> {
            System.out.println("NoReturnMultiParam param:" + "{" + a +"," + + b +"}");
        };
        noReturnMultiParam.method(6, 8);
        //无参有回来值
        ReturnNoParam returnNoParam = () -> {
            System.out.print("ReturnNoParam");
            return 1;
        };
        int res = returnNoParam.method();
        System.out.println("return:" + res);
        //一个参数有回来值
        ReturnOneParam returnOneParam = (int a) -> {
            System.out.println("ReturnOneParam param:" + a);
            return 1;
        };
        int res2 = returnOneParam.method(6);
        System.out.println("return:" + res2);
        //多个参数有回来值
        ReturnMultiParam returnMultiParam = (int a, int b) -> {
            System.out.println("ReturnMultiParam param:" + "{" + a + "," + b +"}");
            return 1;
        };
        int res3 = returnMultiParam.method(6, 8);
        System.out.println("return:" + res3);
    }
}

控制台输出:

NoReturnNoParam
NoReturnOneParam param:6
NoReturnMultiParam param:{6,8}
ReturnNoParamreturn:1
ReturnOneParam param:6
return:1
ReturnMultiParam param:{6,8}
return:1

五、语法简化

5.1 参数部分的精简

5.1.1 参数的类型

由于在接口的办法中,现已界说了每⼀个参数的类型是什么。并且在运用lambda表达式完结接口的时分,必需求确保参数的数量和类 型需求和接口中的办法坚持⼀致。因此,此刻lambda表达式中的参数的类型能够省掉不写。

注意事项:

假如需求省掉参数的类型,要确保:要省掉, 每⼀个参数的类型都有必要省掉不写。肯定不能呈现,有的参数类型省掉了,有的参数类型没有省掉。

// 有参+回来值
Test test = (name,age)  -> {
    System.out.println(name+age+"了!");
    return age + 1;
};
int age = test.test("小刘学编程",18);
System.out.println(age);

5.1.2 参数的小括号

假如办法的参数列表中的参数数量 有且只要⼀个,此刻,参数列表的小括号是能够省掉不写的。

注意事项:

  • 只要当参数的数量是⼀个的时分, 多了、少了都不能省掉。
  • 省掉掉小括号的一起, 必需求省掉参数的类型
//一个参数
Test test = name -> {
    System.out.println(name+"test");
};
test.test("小刘学编程");

5.2 办法体部分的精简

当⼀个办法体中的逻辑,有且只要⼀句的情况下,⼤括号能够省掉

Test test = name -> System.out.println(name+"test");
test.test("小新");

5.3 return部分的精简

假如⼀个办法中唯⼀的⼀条句子是⼀个回来句子, 此刻在省掉掉大括号的一起, 也有必要省掉掉return。

Test test = (a,b) -> a+b;

六、常用示例

lambda表达式是为了简化接口的完结的,在lambda表达式中,不应该呈现比较复杂的逻辑。假如在lambda表达式中需求处理的逻辑比较复杂,会对程序的可读性形成非常大的影响,⼀般情况会单独的写⼀个办法。在lambda表达式中直接引证这个办法即可。

函数引证:引证⼀个现已存在的办法,使其替代lambda表达式完结接口的完结

6.1 静态办法的引证

语法:类::静态办法

注意事项:

  • 在引证的办法后面,不要增加小括号。
  • 引证的这个办法,参数(数量、类型)和回来值,必需求跟接口中界说的⼀致
class Subtraction{
    public static int subtract(int a,int b ){
        // 稍微复杂的逻辑:核算a和b的差值的肯定值
        if (a > b) {
            return a - b;
        }
        return b - a;
    }
}
interface TestInterface{
    int test(int a,int b);
}
public class TestClass {
    public static void main(String[] args) {
        //完结多个参数,一个回来值的接口
        //对一个静态办法的引证,语法:类::静态办法
        TestInterface test = Subtraction::subtract;
        System.out.println(test.test(1,2));
    }
}

6.2 非静态办法的引证

语法:办法归属者::办法名 静态办法的归属者为类名,一般办法归属者为目标

注意事项:

  • 在引证的办法后⾯,不要增加小括号。
  • 引证的这个办法, 参数(数量、类型) 和 回来值, 必需求跟接口中界说的⼀致。
public class Test06 {
    public static void main(String[] args) {
        //对非静态办法的引证,需求运用目标来完结
        Test2 test2 = new Calculator()::calculate;
        System.out.println(test2.calculate(2, 3));
    }
    private static class Calculator{
        public int calculate(int a, int b) {
            return a > b ? a - b : b - a;
         }
    }
}
interface Test2{
    int calculate(int a,int b);
}

6.3 结构办法的引证

运用场景

假如某⼀个函数式接口中界说的办法,仅仅是为了得到⼀个类的目标。此刻咱们就能够运用结构办法的引证,简化这个办法的完结。

语法:类名::new

注意事项:能够通过接口中的办法的参数, 区别引证不同的结构办法。

interface ItemCreatorBlankConstruct {
    Item getItem();
}
interface ItemCreatorParamContruct {
    Item getItem(int id, String name, double price);
}
public class Exe2 {
    public static void main(String[] args) {
        ItemCreatorBlankConstruct creator = () -> new Item();
        Item item = creator.getItem();
        ItemCreatorBlankConstruct creator2 = Item::new;
        Item item2 = creator2.getItem();
        ItemCreatorParamContruct creator3 = Item::new;
        Item item3 = creator3.getItem(112, "小刘学编程", 135.99);
    }
}

6.4 Lambda 表达式创立线程

创立线程一般都是通过创立Thread目标,然后通过匿名内部类重写run()办法,一说到匿名内部类就应该想到能够运用 lambda 表达式来简化线程的创立过程。

Thread t = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        System.out.println("新建线程->" + ":" + i);
    }
});
t.start();

6.5 遍历调集

能够调用调集的 public void forEach(Consumer<? super E> action) 办法,通过 lambda 表达式的办法遍历调集中的元素。以下是Consumer接口的办法以及遍历调集的操作。Consumer接口是jdk供给的一个函数式接口。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    //....
}
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5);
//lambda表达式 办法引证
list.forEach(System.out::println);
list.forEach(item -> {
    if (element % 2 == 0) {
        System.out.println(item);
    }
});

6.6 删去调集中的某个元素

通过public boolean removeIf(Predicate<? super E> filter)办法来删去调集中的某个元素,Predicate也是jdk为供给的一个函数式接口,能够简化程序的编写。

ArrayList<Item> items = new ArrayList<>();
items.add(new Item(11, "小牙刷", 12.05 ));
items.add(new Item(5, "日本马桶盖", 999.05 ));
items.add(new Item(7, "格力空调", 888.88 ));
items.add(new Item(17, "肥皂", 2.00 ));
items.add(new Item(9, "冰箱", 4200.00 ));
items.removeIf(ele -> ele.getId() == 7);
//通过 foreach 遍历,检查是否现已删去
items.forEach(System.out::println);

6.7 调集内元素的排序

若要为调集内的元素排序,就有必要调用sort办法,传入比较器匿名内部类重写compare 办法,现在能够运用lambda 表达式来简化代码。

ArrayList<Item> list = new ArrayList<>();
list.add(new Item(13, "背心", 7.80));
list.add(new Item(11, "半袖", 37.80));
list.add(new Item(14, "风衣", 139.80));
list.add(new Item(12, "秋裤", 55.33));
list.sort((o1, o2) -> o1.getId() - o2.getId());
System.out.println(list);

七、注意

这⾥类似于局部内部类匿名内部类,仍然存在闭包的问题。假如在lambda表达式中,运用到了局部变量,那么这个局部变量会被隐式的声明为 final。是⼀个常量,不能修改值。

如下代码示例:假如咱们把注释放开会报错,提示num值是final不能被改动。这儿尽管没有标识num类型为final,可是在编译期间虚拟时机加上fina修饰关键字。

public static void main(String[] args) {
    int num = 10;
    Test<String> test = () -> {
        System.out.println(num);
    };
    //num = num + 2;
    test.doSomeThing("hello world !");
}

总结

本文从Lambda表达式的基础概念、函数式接口、以及Lambda表达式的常用示例几方面完好的评论了这一Java8新增的特性,实际开发中确实为咱们供给了许多便利,简化了代码。欢迎小伙伴持续提出不同的见地一起评论!

参阅 & 鸣谢

1、Java中Lambda表达式运用及详解

2、Lambda表达式详解

感谢前人的经历、共享和付出,让咱们能够有时机站在巨人的膀子上瞭望星辰大海!


敞开生长之旅!这是我参与「日新计划 2 月更文挑战」的第 12 天,点击检查活动概况