Java系列笔记第八章:函数式编程

第八章 函数式编程

1. 函数式接口简介

1.1 概念

有且只有一个抽象方法的接口,叫函数式接口。

因为Java中函数式编程的体现就是Lambda,所以函数式接口就是可以用于lambda使用的接口。

接口中只能有一个抽象方法,可以有其他类型的方法,比如普通私有方法、静态私有方法、默认方法等等。

语法糖:

指的是形式上使用更加简便,但是底层原理不变的代码语法。比如for-each写起来简洁,底层其实还是迭代器。

从应用层面来讲,可以说lambda是匿名内部类的语法糖,但是底层原理并不同。

匿名内部类会在编译时生成一个class文件,而lambda表达式不会,所以会降低开销。

1.2 格式

函数式接口格式:

package FunctionInterface;

public interface MyInterface {
    void method();
}

1.3 @FunctionalInterface注解

加上这个注解后,接口就强制只能有一个抽象方法。如果多写了一个抽象方法或者没抽象方法,编译会失败。

package FunctionInterface;

@FunctionalInterface
public interface MyInterface {
    void method();
}

1.4 lambda 的小例子

package FunctionInterface;

public class Demo {
    //参数使用函数式接口
    public static void fun(MyInterface mi) {
        mi.method();
    }

    public static void main(String[] args) {
        //调用fun方法,可以传递接口的实现类方法或者匿名内部类。
        fun(new MyInterface() {
            @Override
            public void method() {
                System.out.println("匿名内部类重写接口的抽象方法。");
            }
        });

        //方法的参数是一个函数式接口,所以可以传递进去lambda表达式
        fun(
                () -> System.out.println("lambda表达式重写接口的抽象方法。")
        );
    }
}

//===============输出===============//
匿名内部类重写接口的抽象方法。
lambda表达式重写接口的抽象方法。

2. 函数式编程

2.1 lambda的延迟执行

有的场景代码执行完毕后,结果不一定会被使用,从而造成性能浪费。而lambda是延迟执行你的,所以可以提升性能。

2.1.1 性能浪费的日志案例

日志记录代码如下:

package FunctionInterface.logDemo;

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

    public static void main(String[] args) {
        int level = 2;
        String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        showLog(level, s1 + s2 + s3);
    }
}

代码的问题:
代码中,如果level不是1,字符串的拼接就是无用的,会浪费性能。

使用lambda来优化:

lambda的特点是延迟加载,所以先定义一个函数式接口。

lambda仅仅是传参数进去,如果level不是1,就不会调用接口去拼接字符串。

package FunctionInterface.logDemo;

public class Logger {
    //传递函数式接口
    public static void showLog(int level, LogBuilderInterface logBuilderInterface) {
        //判断日志等级
        if (level == 1) {
            System.out.println(logBuilderInterface.buildMsg());
        }
    }

    public static void main(String[] args) {
        int level = 1;
        String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        showLog(level,
                () -> s1 + s2 + s3
        );
    }
}

2.2 函数式接口的使用

2.2.1作为方法的参数。

java.lang.Runnable是一个函数式接口,假设有一个startThread方法使用Runnable接口作为参数,就可以使用lambda进行传参,效果等同与new Thread(//lambda表达式).start();

package FunctionInterface.InterAsParams;

public class Demo {
    public static void startThread(Runnable runnable) {
        new Thread(runnable).start();
    }

    public static void main(String[] args) {

        //使用Thread的构造方法启动线程。
        new Thread(
                () -> System.out.println(Thread.currentThread().getName() + "线程启动!")
        ).start();

        //startThread的参数是一个接口,可以使用这个接口的匿名内部类。
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "线程启动!");
            }
        });
        //Runnable接口是函数式接口,作为参数,使用lambda表达式启动线程。
        startThread(
                () -> System.out.println(Thread.currentThread().getName() + "线程启动!")
        );
    }
}


//===============输出===============//
Thread-1线程启动!
Thread-2线程启动!
Thread-0线程启动!

2.2.2 作为方法的返回值

最常见的应用就是比较器java.util.Comparator

如果一个方法的返回值类型是一个接口,那么就可以返回一个lambda表达式。

package FunctionInterface.InterAsReturn;

import java.util.Comparator;

public class Demo {
    public static Comparator<Integer> CompareInt(){
        return ((o1, o2) -> o1-o2);
    }
}

3. 常用的函数式接口

java.util.function

3.1 Supplier接口

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

package FunctionInterface;

import java.util.function.Supplier;

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

    public static void main(String[] args) {
        System.out.println(getMsg(()->"a"+"b"));
    }
}

应用:求数组最大值。

package FunctionInterface;

import java.util.function.Supplier;

public class SupplierDemo {
    public static Integer getMax(Supplier<Integer> sup) {
        return sup.get();
    }

    public static void main(String[] args) {
        int[] ints = {1, 2, 3, 4, 5};
        System.out.println(getMax(
                () -> {
                    int max = ints[0];
                    for (int i : ints) {
                        max = Math.max(i, max);
                    }
                    return max;
                }));
    }
}

3.2 Consumer接口

消费一个数据,类型由泛型决定。

void accept(T t)方法。

具体怎么使用,需要自定义。

package FunctionInterface;

import java.util.function.Consumer;

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

    public static void main(String[] args) {
        String s = "abc";
        fun(s,
                (str) -> System.out.println("传递进来的字符串是:" + str)
        );
    }
}

消费者接口的另一种写法:方法引用

fun(s, System.out::println);


Consumer接口有一个默认方法:andThen

需要两个Consumer接口,可以把两个Consumer接口组合在一起。

谁写在前面谁先消费。

例子:定义一个方法,传进去一个字符串和两个 Consumer接口。

  • 要么调用两次accept方法。

  • 要么使用andThen方法。

package FunctionInterface.logDemo;

import java.util.function.Consumer;

public class ConsumerAndThenDemo {
    public static void fun(String str, Consumer<String> consumer1, Consumer<String> consumer2) {
//        consumer1.accept(str);
//        consumer2.accept(str);
        consumer1.andThen(consumer2).accept(str);
    }

    public static void main(String[] args) {
        String s = "Hello World!";
        fun(s,
                (str) -> System.out.println(str.toLowerCase()),
                (str) -> System.out.println(str.toUpperCase())
        );
    }
}

练习:格式化打印信息。

package FunctionInterface.logDemo;

import java.util.ArrayList;
import java.util.function.Consumer;

public class ConsumerAndThenDemo {
    public static void printInfo(Consumer<String> c1, Consumer<String> c2, String[] array) {
        for (String str : array) {
            c1.andThen(c2).accept(str);
        }
    }

    public static void main(String[] args) {
        String[] strings = {"张三,22", "李四,21"};
        printInfo(
                (s -> System.out.println("姓名:" + s.split(",")[0])),
                (s -> System.out.println("年龄:" + s.split(",")[1])),
                strings
        );
    }

}

//===============输出===============//
姓名:张三
年龄:22
姓名:李四
年龄:21

3.3 Predicate接口

3.3.1 接口的使用

对某种类型的数据进行判断,返回一个boolean值。boolean test(T t)

package FunctionInterface;

import java.util.function.Predicate;

public class PredicateDemo {
    public static boolean fun(String s, Predicate<String> predicate) {
        return predicate.test(s);
    }

    public static void main(String[] args) {
        String s = "abc";
        boolean b = fun(s, 
                (o) -> o.contains("A")
        );
        System.out.println("字符串中含有A吗?:" + b);
    }
}

3.3.2 接口中的三个默认方法

  1. and 方法。

传入两个Predicate接口。

package FunctionInterface.PredicateDemo;

import java.util.function.Predicate;

public class PredicateAndDemo {
    public static boolean check(String str, Predicate<String> p1, Predicate<String> p2) {
        return p1.and(p2).test(str);
    }

    public static void main(String[] args) {
        String s = "Abc";
        boolean b = check(s,
                (o -> s.contains("A")),
                (o -> s.length() > 5)
        );
        System.out.println("字符串中既有A,长度又大于5吗?:" + b);
    }
}
  1. or 方法。

return p1.or(p2).test(str);

  1. negate 非操作
public static boolean check(String str, Predicate<String> p) {
    return p.negate().test(str);
}

3.4 Function接口

转换类型的接口。把一个类型的数据转换为另一个类型。

Function<T, R>,把T类型转换为R类型。

package FunctionInterface.FunctionDemo;

import java.util.function.Function;

public class Demo {
    public static void fun(String s, Function<String, Integer> fun) {
        System.out.println(fun.apply(s));
    }

    public static void main(String[] args) {
        String s = "12";
        fun(s,
                (str) -> Integer.parseInt(str)
        );
        
        //方法引用
        fun(s,
                Integer::parseInt
        );
    }
}

Function接口的默认方法:andThen。用来进行组合操作。

示例:将传进来的字符串转成数字,加上10,再转为字符串返回。

package FunctionInterface.FunctionDemo;

import java.util.function.Function;

public class andThenDemo {
    public static String fun(String str, Function<String, Integer> f1, Function<Integer, String> f2) {
        return f1.andThen(f2).apply(str);
    }

    public static void main(String[] args) {
        String s1 = "123";
        String s2 = fun(s1,
                (s -> Integer.parseInt(s) + 10),
                (i -> String.valueOf(i))
        );
        System.out.println(s2);
    }
}

(i -> String.valueOf(i))这行代码可以使用方法引用改写为:
(String::valueOf)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章