Save Load
GitHub 切换暗/亮/自动模式 切换暗/亮/自动模式 切换暗/亮/自动模式 返回首页

Java Function

Java 函数式编程

一、背景

  • 1.Lambda表达式的语法
  • 2.函数式接口

二、Lambda表达式的基本使用

三、语法精简

四、变量捕获

五、Lambda在集合当中的使用

  • 1.Collection接口

六、Lambda 与四大函数接口的使用

七、总结


一、背景

Lambda 表达式是 Java8 中一个重要的新特性。lambda 表达式允许你通过 表达式 来代替功能接口。 lambda 表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码 块)。 Lambda 表达式(Lambda expression)可以看作是一个匿名函数,基于数学中的λ演算得名,也可称为闭包(Closure)

  • Lambda 表达式是对函数接口进行简单的实现

1.Lambda表达式的语法

基本语法:

(parameters) -> expression (parameters) ->{ statements; }

Lambda表达式由三部分组成:

  • 1.paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明 也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。
  • 2.->:可理解为“被用于”的意思
  • 3.方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反 回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。

2.函数式接口

要了解Lambda表达式,首先需要了解什么是函数式接口,

函数式接口定义:

一个接口有且只有一个抽象方法 。

注意:

  • 1.如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
  • 2.如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口 中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的。

定义方式:

@FunctionalInterface
interface NoParameterNoReturn {
    //注意:只能有一个抽象方法
    void test();
}

但是这种方式也是可以的:

@FunctionalInterface
interface NoParameterNoReturn {
    void test();
 
    default void test2() {
        System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");
    }
}

二、Lambda表达式的基本使用

首先,我们实现准备好几个接口:

@FunctionalInterface
interface NoParameterNoReturn {
    //注意:只能有一个抽象方法
    void test();
}
 
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
    void test(int a);
}
 
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
    void test(int a, int b);
}
 
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
    int test();
}
 
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
    int test(int a);
}
 
//有返回值多参数
@FunctionalInterface
interface MoreParameterReturn {
    int test(int a, int b);
}

我们在上面提到过,Lambda 表达式本质是一个匿名函数,函数的方法是:返回值 方法名 参数列表 方法体。在,Lambda表达式中我们只需要关心:参数列表 方法体。

具体使用见以下示例代码:

@FunctionalInterface
interface NoParameterNoReturn {
    //注意:只能有一个抽象方法
    void test();
}
 
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
    void test(int a);
}
 
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
    void test(int a, int b);
}
 
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
    int test();
}
 
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
    int test(int a);
}
 
//有返回值多参数
@FunctionalInterface
interface MoreParameterReturn {
    int test(int a, int b);
}
 
 
public class TestDemo2 {
    public static void main(String[] args) {
 
        NoParameterNoReturn noParameterNoReturn = () -> {
            System.out.println("无参数无返回值");
        };
        //test方法的主体内容在上述括号内
        noParameterNoReturn.test();
 
 
        OneParameterNoReturn oneParameterNoReturn = (int a) -> {
            System.out.println("无参数一个返回值:" + a);
        };
        oneParameterNoReturn.test(10);
 
 
        MoreParameterNoReturn moreParameterNoReturn = (int a, int b) -> {
            System.out.println("无返回值多个参数:" + a + " " + b);
        };
        moreParameterNoReturn.test(20, 30);
 
 
        NoParameterReturn noParameterReturn = () -> {
            System.out.println("有返回值无参数!");
            return 40;
        };
        //接收函数的返回值
        int ret = noParameterReturn.test();
        System.out.println(ret);
 
        OneParameterReturn oneParameterReturn = (int a) -> {
            System.out.println("有返回值有参数!");
            return a;
        };
 
        ret = oneParameterReturn.test(50);
        System.out.println(ret);
 
 
        MoreParameterReturn moreParameterReturn = (int a, int b) -> {
            System.out.println("有返回值多个参数!");
            return a + b;
        };
        ret = moreParameterReturn.test(60, 70);
        System.out.println(ret);
    }
}

三、语法精简

  • 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
  • 参数的小括号里面只有一个参数,那么小括号可以省略
  • 如果方法体当中只有一句代码,那么大括号可以省略
  • 如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字。

四、变量捕获

Lambda 表达式中存在变量捕获 ,了解了变量捕获之后,我们才能更好的理解Lambda 表达式的作用域 。Java当中的匿名类中,会存在变量捕获。

下面我们来讲下在Lambda当中也可以进行变量的捕获,具体我们看一下代码:

@FunctionalInterface
interface NoParameterNoReturn {
    void test();
 
}
 
public class TestDemo2 {
    public static void main(String[] args) {
        int a = 10;
        NoParameterNoReturn noParameterNoReturn = () -> {
            /*
            注意此处不能够修改a的值,与匿名内部类中相同
            a = 99;
            */
            System.out.println("捕获变量:" + a);
        };
        noParameterNoReturn.test();
    }
}

执行结果

捕获变量:10

五、Lambda在集合当中的使用

为了能够让Lambda和Java的集合类集更好的一起使用,集合当中,也新增了部分接口,以便与Lambda表达式对接。

对应接口 新增方法
Collection removeIf() spliterator() stream() parallelStream() forEach()
List replaceAll() sort()
Map getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge()

注意:Collection的forEach()方法是从接口 java.lang.Iterable 拿过来的。

1.Collection 接口

forEach() 方法演示

该方法在接口 Iterable 当中,原型如下:

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

**forEach()**方法表示:对容器中的每个元素执行action指定的动作

可以看到我们的参数 Consumer 其实是一个函数式接口: 这个函数式接口中有一个抽象方法accept:

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
public class TestDemo2 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("bit");
        list.add("hello");
        list.add("lambda");
        list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                //简单遍历集合中的元素
                System.out.println(s);
            }
        });
    }
}

我们可以修改为如下代码:

public class TestDemo2 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("bit");
        list.add("hello");
        list.add("lambda");
      
         list.forEach((String s) -> {
            System.out.println(s);
        });
    }
}

可以简化代码:

public class TestDemo2 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("bit");
        list.add("hello");
        list.add("lambda");
 
 
        list.forEach(s -> System.out.println(s));
        // 再简化        
        list.forEach(System.out::println);
    }
}

六、Lambda 与四大函数接口的使用

1、Lambda 的延迟执行

public class PrintLog {
    public static void showLog(int level, String msg){
        if(level==1){
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        String msg1 = "Hello";
        String msg2 = "Java";
        String msg3 = "heroC";
        showLog(1,msg1 + msg2 + msg3);
    }
}

此打印日志程序,存在性能浪费,如果在main方法中,调用showLog方法,传入的等级不是1,那么字符串依然会被拼接,字符串拼接之后判断等级不是1,就白拼接了,也浪费了资源和性能。

通过 Lambda 优化日志案例

函数式接口:

// 创建一个函数式接口
@FunctionalInterface
public interface PrintLogInterface {
    public abstract String printLog();
}

测试类:

public class PrintLog {

    public static void showLog(int level, PrintLogInterface printLogInterface){
        if(level == 1){
            System.out.println(printLogInterface.printLog());
        }
    }

    public static void main(String[] args) {
        String msg1 = "Hello";
        String msg2 = "Java";
        String msg3 = "heroC";
        int level = 1;
        showLog(level,() -> msg1 + msg2 + msg3);
    }
}

通过使用Lambda表达式,Lambda表达式有一个很大的有点就是延迟执行,调用showLog方法,只将参数传递进去了,先判断是否等于1,判断了如果等于1才会去调用Lambda表达式实现的接口类,进行拼接字符串,然后输出,否则不会拼接字符串。

2、使用Lambda作为参数和返回值

  • 使用Lambda作为参数
// 写了一个函数式接口
@FunctionalInterface
public interface MyFunInterface {
    public abstract void method();
}
// 测试类
public class Test{
    // 函数式接口作为方法参数
    public static void show(MyFunInterface myFunInterface){
        myFunInterface.method();
    }
    
    public static void main(String[] args){
        show(() -> System.out.println("通过Lambda,实现函数式接口"));
    }
}
  • 使用Lambda作为返回值
public class Test {
    public static Comparator<String> newComparator(){
      return (a, b) -> a.length() - b.length(); // 这里就是作为返回值
    };

    public static void main(String[] args) {
        String[] arr = {"aaa","bb","c"};
        Arrays.sort(arr,newComparator());
        System.out.println(Arrays.toString(arr));
        // 输出 [c, bb, aaa]
    }
}

3、4大函数式接口 JDK 1.8

主要在java.util.function包中

1、Supplier 接口

java.util.function Interface Supplier<T> 接口中包含了一个抽象方法 T get()。用来获取一个泛型参数指定类型的对象数据。

该接口被称为生产型接口,指定接口是什么类型,那么接口中的get方法就会产生什么类型的数据。

public class SupplierTest {
    public static String getString(Supplier<String> sup){
        return sup.get();
    }

    public static int getInt(Supplier<Integer> sup){
        return sup.get();
    }

    public static void main(String[] args) {
        String str = getString(() -> "heroC");
        /*getString(new Supplier<String>() {
            @Override
            public String get() {
                return "heroC";
            }
        });*/
        System.out.println(str);

        int strInt = getInt(() -> 1130);
        System.out.println(strInt);
    }
}
练习:求数组元素中最大的数
public class ArrayMaxNum {
    public static int getMax(Supplier<Integer> sup){
        return sup.get();
    }

    public static void main(String[] args) {
        int[] arr = {245,54,2366,23,6543,1130};

        int max1 = getMax(() -> {
            int max = arr[0];
            for (int i = 0; i < arr.length-1; i++) {
                if (arr[i] < arr[i+1]) {
                    max = arr[i+1];
                }
            }
            return max;
        });
        
        System.out.println(max1);
    }
}
2、Consumer 接口

java.util.function Interface Consumer<T> 接口正好与Supplier接口相反,它不是生产一个数据,而是消费(使用)一个数据,其数据类型由泛型决定。

抽象方法:void accept(T t) ,意为消费一个数据类型。

public class ConsumerInterfaceImp {
    public static void consumerString(String str, Consumer<String> con){
        con.accept(str);
    }

    public static void main(String[] args) {
        consumerString("heroC",(str) -> {
            // 将字符串反转
            String s = new StringBuffer(str).reverse().toString();
            System.out.println(s);
        });
    }
}

默认方法:default Consumer<T> andThen(Consumer<? super T> after) 需要两个Consumer接口,就可以把两个Consumer接口组合在一起,在对数据进行消费。

// Consumer默认方法源码
default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
}

eg:

public class ConsumerInterfaceImp {

    public static void consumerAndThen(String str, Consumer<String> con1, Consumer<String> con2){
        // 两个Consumer接口对象,使用默认方法andThen,调用accept方法
        // 执行步骤:con1.accept(str); con2.accept(str); 谁在前面谁先对数据进行消费
        // 如果由多个Consumer对象,可以一直链式调用andThen方法
        con1.andThen(con2).accept(str);
    }

    public static void main(String[] args) {
        consumerAndThen("heroC",
                (str) -> System.out.println(str.toUpperCase()),
                (str) -> System.out.println(str));
    }
}
练习:格式化打印信息

下面字符串数组中,存储了很多信息,按照姓名:xxx,性别:xxx格式打印出来。使用Consumer接口的Lambda表达式。

public static void main(String[] args){
    String[] array = {"heroC,男", "欧阳娜娜,女"};
}

解答:

public class ConsumerInterfaceExample {
    public static void printInfo(String[] arr, Consumer<String> con1, Consumer<String> con2){
        for(String str : arr){
            con1.andThen(con2).accept(str);
        }
    }

    public static void main(String[] args) {
        String[] array = {"heroC,男", "欧阳娜娜,女"};
        printInfo(array, str ->{
            System.out.print("姓名:" + str.split(",")[0] + ",");
        }, str ->{
            System.out.println("性别:" + str.split(",")[1]);
        });
    }
}

总结:

优点:

代码简洁,开发迅速 方便函数式编程 非常容易进行并行计算 Java 引入 Lambda,改善了集合操作

缺点:

代码可读性变差 在非并行计算中,很多计算未必有传统的 for 性能要高 不容易进行调试