Lambda&方法引用&函数式接口

58 阅读4分钟

1、Lambda表达式

1.1、简介

Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 表达式规定接口中只能有一个需要被实现的方法而不是规定接口中只能有一个方法,被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。

1.2、标准格式

(参数类型 参数名称,参数类型 参数名称,...) -> {
    代码体;
}

1.3、省略规则

  • 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
  • 如果参数有且仅有一个,那么小括号可以省略
  • 如果大括号内有且仅有一个语句,则可以同时省略大括号、return关键字及语句分号

1.4、使用前提(函数式接口)

  • 有一个接口
  • 接口中有且仅有一个抽象方法

1.3、Lambda表达式练习

1.3.1、无参无返回值抽象方法的练习

//接口
public interface Eatable {
    void eat();
}
​
//测试类
public class EatableDemo {
    public static void main(String[] args) {
        //匿名内部类
        useEatable(new Eatable() {
            @Override
            public void eat() {
                System.out.println("没事多吃点");
            }
        });
        //Lambda表达式
        useEatable(() -> {
            System.out.println("没事多吃点");
        });
    }
​
    private static void useEatable(Eatable e) {
        e.eat();
    }
​
}

1.3.2、有参无返回值抽象方法的练习

//接口
public interface Flyable {
    void fly(String s);
}
//测试类
public class FlyableDemo {
    public static void main(String[] args) {
        //在主方法中调用useFlyable方法
        //匿名内部类
        useFlyable(new Flyable() {
            @Override
            public void fly(String s) {
                System.out.println(s);
            }
        });
        System.out.println("--------");
        //Lambda
        useFlyable((String s) -> {
            System.out.println(s);
        });
    }
    private static void useFlyable(Flyable f) {
        f.fly("我要飞得更高");
    }
}

1.3.3、有参有返回值抽象方法的练习

//接口
public interface Addable {
    int add(int x,int y);
}
//测试类
public class AddableDemo {
    public static void main(String[] args) {
        //在主方法中调用useAddable方法
        useAddable((int x,int y) -> {
            return x + y;
        });
    }
    private static void useAddable(Addable a) {
        int sum = a.add(10, 20);
        System.out.println(sum);
    }
}

1.6、Lambda表达式和匿名内部类的区别

  • 所需类型不同

    • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
    • Lambda表达式:只能是接口
  • 使用限制不同

    • 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
    • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
  • 实现原理不同

    • 匿名内部类:编译之后,产生一个单独的.class字节码文件
    • Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成

2、函数式接口

2.1、概念

有且仅有一个抽象方法的接口就是函数式接口,并且可以通过在类上标注@FunctionalInterface注解进行检测。

2.2、内置接口(四大内置函数式接口)

2.2.1、Supplier接口:供给型接口(无参有返回值的接口)

public class LambdaDemo2 {
    public static void main(String[] args) {
        Supplier<Integer> supplier = () -> Math.max(10, 20);
        Integer result = supplier.get();
        System.out.println(result);//20
    }
}

2.2.2、Consumer接口:消费型接口(有参无返回值的接口)

public class lambdaDemo3 {
    public static void main(String[] args) {
        //printStr(s -> System.out.println(s.toUpperCase()));
        printStr(s -> System.out.println(s.toUpperCase()), s -> System.out.println(s.toLowerCase()));
    }
​
    public static void printStr(Consumer<String> consumer) {
        consumer.accept("Hello,World");//HELLO,WORLD
    }
​
    public static void printStr(Consumer<String> consumer1, Consumer<String> consumer2) {
        consumer1.andThen(consumer2).accept("Hello,World");
        //HELLO,WORLD
        //hello,world
    }
}

两个参数的可以参考BiConsumer类

2.2.3、Function接口:函数型接口(有参有返回值的接口,第一个参数为输入值,第二个参数为输出值)

public class LambdaDemo4 {
    public static void main(String[] args) {
        printNum(s -> Integer.parseInt(s));
​
        Function<Integer, Integer> times2 = i -> i * 2;
        Function<Integer, Integer> squared = i -> i * i;
​
        //32,先执行apply(4),再4×4,然后16×2
        System.out.println(times2.compose(squared).apply(4));
​
        //64,先执行apply(4),再4×2,然后8×8
        System.out.println(times2.andThen(squared).apply(4));
​
    }
​
    public static void printNum(Function<String, Integer> function) {
        Integer result = function.apply("10");
        System.out.println(result);//10
    }
    
}

两个输入值可参考BiFunction类

2.2.4、Predicate接口:断定式接口(用于条件判断的场景)

public class LambdaDemo5 {
    public static void main(String[] args) {
        printResult(s -> s.contains("H"));
        printAnd(s -> s.contains("H"), s -> s.contains("W"));
        printOr(s -> s.contains("H"), s -> s.contains("W"));
        printNegate(s -> s.contains("H"));
    }
​
    public static void printResult(Predicate<String> predicate) {
        boolean result = predicate.test("Hello,World");
        System.out.println(result);//true
    }
​
    public static void printAnd(Predicate<String> predicate1, Predicate<String> predicate2) {
        boolean result = predicate1.and(predicate2).test("Hello,World");
        System.out.println(result);//true
    }
​
    public static void printOr(Predicate<String> predicate1, Predicate<String> predicate2) {
        boolean result = predicate1.or(predicate2).test("Hello,World");
        System.out.println(result);//true
    }
​
    public static void printNegate(Predicate<String> predicate) {
        boolean result = predicate.negate().test("Hello,World");
        System.out.println(result);//false
    }
​
}

3、方法引用

3.1、相关概念

符号表示:::

符号描述:双冒号为方法引用运算符,而他所在的表达式被称为方法引用。

应用场景:如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用。

3.2、常见形式:

  • 对象::方法名

    Date date = new Date();
    Supplier<Long> supplier = date::getTime;
    Long time = supplier.get();
    System.out.println(time);
    
  • 类名::静态方法

    Supplier<Long> supplier = System::currentTimeMillis;
    Long time = supplier.get();
    System.out.println(time);
    
  • 类名::普通方法

    Function<String, Integer> function = String::length;
    Integer length = function.apply("Hello");
    System.out.println(length);
    
  • 类名::new 调用对象的构造器

    Function<String, String> function = String::new;
    String result = function.apply("Hello");
    System.out.println(result);
    
  • 数据类型[]::new 调用数组的构造器

    Function<Integer, int[]> function = int[]::new;
    int[] result = function.apply(10);
    System.out.println(Arrays.toString(result));