Java函数式编程与Lambda表达式

一.Lambda表达式

1.lambda表达式初试

  • lambda表达式是返回了实现指定接口的对象实例
  • 样例代码
public class Test {
    public static void main(String [] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("ok");
            }
        }).start();
        // 等同于
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("ok");
            }
        };
        new Thread(runnable).start();

        // 切换成Lambda表达式的写法如下
        new Thread(()-> System.out.println("okk")).start();
        // 等同于
        Runnable runnable1 = ()-> System.out.println("okk");
        new Thread(runnable1).start();
    }
}

2.lambda表达式的常见写法

  • 如果接口只存在一个抽象方法则可以使用lambda表达式编写实现内容
  • 入参的内容写在lambda表达式的括号内,返回内容写在箭头的右侧
  • 实例代码
interface Interface1 {
    int doubleNum(int i);
}
public class LambdaDemo1 {
    public static void main(String[] args) {
        // 写法1:常见写法
        Interface1 i1 = (i) -> i*2; // 如果是一个参数可以去掉括号, Interface1 i1 = i -> i*2;
        // 写法2:
        Interface1 i2 = (int i) -> i*2;
        // 写法3:
        Interface1 i3 = (int i) -> {
            return i*2;
        };
    }
}

二.JDK8接口新特性

1.函数接口

  • @FunctionalInterface,表示接口是函数接口,注解修饰的函数只存在一个需要实现的抽象方法(需要实现的方法只有一个,不是只有一个方法,还可以有默认方法)
  • 实例代码
@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);
}

2. 默认方法

  • default关键字可以用来定义默认方法,在方法中给出默认实现
  • 实例代码
@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x+y;
    }
}
public class LambdaDemo1 {
    public static void main(String[] args) {
        Interface1 interfaceImpl = i -> i*3;
        System.out.println(interfaceImpl.doubleNum(3)); // 输出结果为9,因为上方实现了对应的方法
        System.out.println(interfaceImpl.add(3, 4)); // 输出结果为7,因为接口中有默认方法
    }
}
  • 如何接口继承的其他接口存在同名的默认方法,编译器会进行提示,要求重新实现对应的默认方法内容,实例如下
@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x+y;
    }
}

@FunctionalInterface
interface Interface2 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x+y+1;
    }
}

@FunctionalInterface
interface Interface3 extends Interface1, Interface2 { // 编译器会提示要求重新实现add方法内容
    @Override
    default int add(int x, int y) {
        return Interface1.super.add(x, y); // 相当于调用了Interface1的接口内容,当然我们也可以写自己的实现
    }
}

public class LambdaDemo1 {
    public static void main(String[] args) {
        Interface3 interfaceImpl = i -> i*3;
        System.out.println(interfaceImpl.doubleNum(3)); // 输出结果为9
        System.out.println(interfaceImpl.add(3, 4)); // 输出结果为7
    }
}

三.JDK8中重要的函数接口

使用现成的函数接口的好处就是不用定义过多的接口,且函数接口可以支持链式操作

1.Function<T, K>

  • 采用Function函数接口可以指定输入及输出类型的函数接口
  • 实例代码
import java.text.DecimalFormat;
import java.util.function.Function;

interface IMoneyFormat {
    String format(int i);
}

class MyMoney {
    private final int money;

    public MyMoney(int money) {
        this.money = money;
    }

//    public void printMoney(IMoneyFormat iMoneyFormat) {
//        System.out.println("我的存款: "+ iMoneyFormat.format(this.money));
//    }

    // 此时由于我们的接口只需要知道输入是什么,输出是什么,故采用Function<T,K>函数接口即可
    public void printMoney(Function<Integer, String> iMoneyFormat) {
        System.out.println("我的存款: "+ iMoneyFormat.apply(this.money));
    }
}

public class MoneyDemo {
    public static void main(String[] args) {
        MyMoney me = new MyMoney(9999);
//        IMoneyFormat iMoneyFormat = (money)-> new DecimalFormat("#,###").format(money);
//        me.printMoney(iMoneyFormat);

        // 采用函数接口的方式实现如下
        me.printMoney((money)-> new DecimalFormat("#,###").format(money));
        // 输出: 我的存款: 9,999

        // 采用函数接口链式操作特性添加前缀实现如下
        Function<Integer, String > moneyFormat = (money)-> new DecimalFormat("#,###").format(money);
        me.printMoney(moneyFormat.andThen( s -> "人民币 " + s));
        // 输出: 我的存款: 人民币 9,999
    }
}

2.常用函数接口

  • 常用函数接口
接口 输入类型 返回类型 说明
Predicate T boolean 断言
Consumer T / 消费一个数据
Function<T, R> T R 输入T输出R的函数
Supplier / T 提供一个数据
UnaryOperator T T 一元函数(输出输入类型相同)
BiFunction<T, U, R> (T, U) R 2个输入的函数
BinaryOperator (T, T) T 二元函数(输出输入类型相同)
  • 对于基本数据类型的函数接口jdk也已经提供,比如Predicate<Integer>也可以写成IntPredicate
  • 实例代码
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
import java.util.function.Predicate;

public class Test2 {
    public static void main(String[] args) {
        // 断言函数: 用于判断输入是否大于0
        Predicate<Integer> predicate = i -> i > 0;
        System.out.println(predicate.test(-2)); // 输出: false
        // 对于基本数据类型,jdk有对应数据类型,上述内容等同如下
        IntPredicate predicate1 = i -> i > 0;

        // 消费函数
        Consumer<String> customer = s -> System.out.println("输入的内容是: " + s);
        customer.accept("巴啦啦小魔仙"); // 输出: 输入的内容是: 巴啦啦小魔仙

        // 二元函数
        BinaryOperator<Integer> testAdd = (a, b) -> a + b;
        System.out.println(testAdd.apply(2, 3)); // 输出:5

    }
}

3.方法引用

  • 静态方法的方法引用: 类名::方法名
  • 非静态方法的方法引用
    • 对象实例::方法名
    • 类名::方法名 - 注意第一个参数是对象实例
  • 构造函数的方法引用
    • 类名::new
  • 实例代码
import java.util.function.*;

class Dog {
    private String name = "哮天犬";

    public Dog() {
    }

    public Dog(String name) {
        this.name = name;
    }

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

    public static void bark(Dog dog) {
        System.out.println(dog + "叫了");
    }

    public static void sleep(String name) {
        System.out.println(name + "默默的睡觉...");
    }

    private int food = 10;

    /**
     * 吃狗粮
     *
     * @param num
     * @return
     */
    public int eat(int num) {
        System.out.println("吃了"+ num + "斤狗粮");
        this.food -= num;
        return this.food;
    }
}

public class MethodReferenceDemo {
    public static void main(String[] args) {
        Consumer<String> consumer = s -> System.out.println(s);
        consumer.accept("使用函数接口"); // 输出: 使用函数接口
        // 如果内部方法的参数跟参数相同,则可以变成 方法引用 的方式进行书写
        Consumer<String> consumer1 = System.out::println;
        consumer1.accept("使用方法引用"); // 输出: 使用方法引用

        // 静态方法的方法引用: 使用类名::方法名
        Consumer<Dog> dogConsumer = Dog::bark;
        Dog dog = new Dog();
        dogConsumer.accept(dog); // 输出: Dog{name='哮天犬'}叫了

        Consumer<String> simpleConsumer = Dog::sleep;
        simpleConsumer.accept("旺财"); // 输出: 旺财默默的睡觉...

        // 非静态方法
        // 使用对象实例来引用
        IntUnaryOperator function = dog::eat;
        System.out.println("还剩下" + function.applyAsInt(2) + "斤"); // 输出: 吃了2斤狗粮 还剩下8斤
        // 使用类名来引用,费静态方法其实默认第一个参数是当前实例this,故直接给定实例为入参即可
        BiFunction<Dog, Integer ,Integer> biFunction = Dog::eat;
        System.out.println("还剩下"+ biFunction.apply(dog, 3)+"斤"); // 输出: 吃了3斤狗粮 还剩下5斤

        // 构造函数的方法引用, 构造函数的输入是指定参数, 输出是实例对象
        // 方法名是new
        // 无参构造函数如下
        Supplier<Dog> supplier = Dog::new;
        System.out.println("创建了一个新对象: " + supplier.get()); // 输出: 创建了一个新对象: Dog{name='哮天犬'}
        // 有参构造函数如下
        Function<String, Dog> dogFunction = Dog::new;
        System.out.println("创建了个一个来福对象: " + dogFunction.apply("来福")); // 输出: 创建了个一个来福对象: Dog{name='来福'}
    }
}

4.类型推断

  • 通常我们可以采用类型推断自动匹配参数内容
  • 实例代码
@FunctionalInterface
interface IMath1 {
    int add(int x, int y);
}

@FunctionalInterface
interface IMath2 {
    int add(int x, int y);
}

public class Test3 {
    public static void main(String[] args) {
        // 变量定义
        IMath1 lambda = (x, y) -> x+y;

        // 数组中
        IMath1[] lambdas = {(x, y) -> x+y};

        // 强转
        Object lambda2 = (IMath1)(x,y) -> x+y;

        // 通过方法返回类型
        IMath1 createLambda = createLambda();


        // 当有二义性的时候,需要使用强转指定对应的接口
        Test3 test3 = new Test3();
        test3.test((IMath1) (x, y) -> x+y); // 如果转换类型则会报错
    }

    // 当有重载方法时,无法直接匹配
    public void test(IMath1 iMath1) {

    }

    public void test(IMath2 iMath2) {

    }

    static IMath1 createLambda() {
        return ((x, y) -> x+y);
    }
}

5.级联表达式及柯里化

  • 柯里化:把多个参数的函数转换为只有一个参数的函数
  • 柯里化的目的:函数标准化
  • 高阶函数:返回函数的函数
  • 实例代码
import java.util.function.Function;

public class Test4 {
    public static void main(String[] args) {
        // 实现了x+y的级联表达式
        Function<Integer, Function<Integer, Integer>> fun = x -> y -> x+y;
        System.out.println(fun.apply(2).apply(3)); // 输出5

        Function<Integer, Function<Integer, Function<Integer, Integer>>> fun2 = x -> y -> z -> x+y+z;
        System.out.println(fun2.apply(1).apply(2).apply(3));
    }
}
发布了258 篇原创文章 · 获赞 332 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_34829447/article/details/104092084