Lambda表达式

函数式接口介绍

如果在一个接口中只声明了一个抽象方法,则此接口就被称为函数式接口(该接口可以包含其他非抽象方法)

  • 在接口上使用@FunctionalInterface注解可以验证该接口是否为函数式接口(如果接口中有多个抽象方法编译器会报错,javadoc生成的文档时 会保留该注解

随着Python,Scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,所以Java8不但可以支持OOP还可以支持OOF(面向函数编程)

  • 面向对象编程思想:完成一件事情需要找一个能解决这个事情的对象然后调用对象的方法
  • 函数式编程思想: 重视结果不重视过程,只要能获取到结果即可,无论谁去做又怎么做
  • 在函数式编程语言当中Lambda表达式的类型是函数,但在Java8中Lambda表达式是对象而不是函数,它们必须依附于函数式接口

Java 内置函数式接口

java.util.function包下定义了Java 8 的丰富的函数式接口

四大核心函数式接口

函数式接口 称谓 参数类型 用途
Consumer 消费型接口 T 对类型为T的对象应用操作,包含方法: void accept(T t)
Supplier 供给型接口 返回类型为T的对象,包含方法:T get()
Function 函数型接口 T 对类型为T的对象应用操作并返回结果R类型的对象,包含方法:R apply(T t)
Predicate 判断型接口 T 确定类型为T的对象是否满足某约束并返回 boolean 值,包含方法:boolean test(T t)

消费型接口: 抽象方法有形参但是返回值类型是void

接口名 抽象方法 描述
BiConsumer void accept(T t, U u) 接收两个对象用于完成功能
DoubleConsumer void accept(double value) 接收一个double值
IntConsumer void accept(int value) 接收一个int值
LongConsumer void accept(long value) 接收一个long值
ObjDoubleConsumer void accept(T t, double value) 接收一个对象和一个double值
ObjIntConsumer void accept(T t, int value) 接收一个对象和一个int值
ObjLongConsumer void accept(T t, long value) 接收一个对象和一个long值

供给型接口: 抽象方法无参但是有返回值

接口名 抽象方法 描述
BooleanSupplier boolean getAsBoolean() 返回一个boolean值
DoubleSupplier double getAsDouble() 返回一个double值
IntSupplier int getAsInt() 返回一个int值
LongSupplier long getAsLong() 返回一个long值

函数型接口: 抽象方法既有参数又有返回值

接口名 抽象方法 描述
UnaryOperator T apply(T t) 接收一个T类型对象,返回一个T类型对象结果
DoubleFunction R apply(double value) 接收一个double值,返回一个R类型对象
IntFunction R apply(int value) 接收一个int值,返回一个R类型对象
LongFunction R apply(long value) 接收一个long值,返回一个R类型对象
ToDoubleFunction double applyAsDouble(T value) 接收一个T类型对象,返回一个double
ToIntFunction int applyAsInt(T value) 接收一个T类型对象,返回一个int
ToLongFunction long applyAsLong(T value) 接收一个T类型对象,返回一个long
DoubleToIntFunction int applyAsInt(double value) 接收一个double值,返回一个int结果
DoubleToLongFunction long applyAsLong(double value) 接收一个double值,返回一个long结果
IntToDoubleFunction double applyAsDouble(int value) 接收一个int值,返回一个double结果
IntToLongFunction long applyAsLong(int value) 接收一个int值,返回一个long结果
LongToDoubleFunction double applyAsDouble(long value) 接收一个long值,返回一个double结果
LongToIntFunction int applyAsInt(long value) 接收一个long值,返回一个int结果
DoubleUnaryOperator double applyAsDouble(double operand) 接收一个double值,返回一个double
IntUnaryOperator int applyAsInt(int operand) 接收一个int值,返回一个int结果
LongUnaryOperator long applyAsLong(long operand) 接收一个long值,返回一个long结果
BiFunction R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperator T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
ToIntBiFunction int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
ToLongBiFunction long applyAsLong(T t, U u) 接收一个T类型和一个U类型对象,返回一个long
DoubleBinaryOperator double applyAsDouble(double left, double right) 接收两个double值,返回一个double结果
IntBinaryOperator int applyAsInt(int left, int right) 接收两个int值,返回一个int结果
LongBinaryOperator long applyAsLong(long left, long right) 接收两个long值,返回一个long结果

判断型接口: 抽象方法特点有参但是返回值类型是boolean结果

接口名 抽象方法 描述
BiPredicate boolean test(T t, U u) 接收两个对象
DoublePredicate boolean test(double value) 接收一个double值
IntPredicate boolean test(int value) 接收一个int值
LongPredicate boolean test(long value) 接收一个long值

接口的使用

消费型接口使用举例

public void happyTime(double money, Consumer<Double> consumer) {
    consumer.accept(money);
}

@Test
public void test04() {
    // 传统写法
    happyTime(1241, new Consumer<Double>() {
        @Override
        public void accept(Double money) {
            System.out.println("突然想回一趟成都了,机票花费" + money);
        }
    });
    System.out.println("------------------------");

    // Lambda表达式
    happyTime(648, money -> System.out.println("学习太累了,奖励自己一发十连,花费" + money));
}

断定型接口使用举例: 根据Predicate的方法给定的规则,过滤集合中的字符串

public List<String> filterString(List<String> strings, Predicate<String> predicate) {
    ArrayList<String> res = new ArrayList<>();
    for (String string : strings) {
        if (predicate.test(string))
            res.add(string);
    }
    return res;
}

@Test
public void test05() {
    List<String> strings = Arrays.asList("东京", "西京", "南京", "北京", "天津", "中京");
    // 传统写法
    List<String> list = filterString(strings, new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.contains("京");
        }
    });
    System.out.println(list);

    System.out.println("------------------------");

    // Lambda表达式
    List<String> res = filterString(strings, s -> s.contains("京"));
    System.out.println(res);
}

Lambda表达式语法的使用

匿名内部类

当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable接口来定义任务内容并使用java.lang.Thread类来启动该线程

  • Thread类需要Runnable接口作为参数,其中的抽象run方法是用来指定线程任务内容的核心
  • 为了指定run的方法体需要创建Runnable接口的实现类,为了省去定义一个RunnableImpl实现类的麻烦需要使用匿名内部类
  • 编写的匿名内部类必须覆盖重写抽象run方法,所以方法名称、方法参数、方法返回值都需要再写一遍且不能写错, 而实际上只有方法体才是关键所在
public class UseFunctionalProgramming {
    public static void main(String[] args) {
        // new 接口(){实现类}
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("多线程任务执行!");
            }
        }).start(); // 启动线程
    }
}

Lambda表达式的6种形式

我们可以通过Lambda表达式创建函数式接口的实现类对象代替匿名实现类,Lambda表达式是可以简化函数式接口的变量或形参赋值的语法

  • 因为函数式接口只有一个抽象方法,所以我们才可以省略方法名称,方法参数,方法返回值,@Override函数声明等内容
new Thread(() -> {
	System.out.println("多线程任务执行!");
}).start(); 

Lambda表达式的语法格式如(o1,o2) -> Integer.compare(o1,o2)

  • ->:lambda操作符或箭头操作符
  • ->左边(函数式接口中抽象方法的形参列表):因为有"类型推断"机制形参的数据类型都可以省略,如果只有一个参数,参数的小括号可以省略
  • ->右边{函数式接口中抽象方法的方法体}: 当Lambda体只有一条语句时,return与{}可以省略(return不能单独出现)

语法格式一: 函数式接口的抽象方法无参无返回值

@Test
public void test01(){
    //  传统写法
    Runnable runnable01 = new Runnable() {
        @Override
        public void run() {
            System.out.println("你 的 城 市 好 像 不 欢 迎 我");
        }
    };
    runnable01.run();
    System.out.println("-------------------------");
    // Lambda表达式
    Runnable runnable02 = () -> {
        System.out.println("所 以 我 只 好 转 身 离 开 了");
    };
    runnable02.run();
}

语法格式二: 函数式接口的抽象方法有一个参数但是没有返回值

@Test
public void test03(){
    //1. 传统写法
    Consumer<String> consumer01 = new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };
    consumer01.accept("其实我存过你照片 也研究过你的星座");

    System.out.println("-------------------------");
    // Lambda表达式
    Consumer<String> consumer02 = (String s) -> {
        System.out.println(s);
    };
    consumer02.accept("你喜欢的歌我也会去听 你喜欢的事物我也会想去了解");
}

语法格式三: 函数式接口抽象方法中形参列表的数据类型可以省略,编译器可由"类型推断"机制得出(由声明变量的泛型类型推断得出)

Lambda表达式_第1张图片

@Test
public void test() {
    //类型推断1
    ArrayList<String> list = new ArrayList<>();
    //类型推断2
    int[] arr = {1, 2, 3};

}
@Test
public void test04(){
    // 传统写法
    Consumer<String> consumer01 = new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };
    consumer01.accept("我远比表面上更喜欢你");

    System.out.println("-------------------------");
    // Lambda表达式
    Consumer<String> consumer02 = (s) -> {
        System.out.println(s);
    };
    consumer02.accept("但我没有说");
}

语法格式四: 函数式接口的抽象方法只有一个参数,参数的小括号可以省略

@Test
public void test04(){
    // 传统写法
    Consumer<String> consumer01 = new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };
    consumer01.accept("我远比表面上更喜欢你");

    System.out.println("-------------------------");
    // Lambda表达式
    Consumer<String> consumer02 = s -> {
        System.out.println(s);
    };
    consumer02.accept("但我没有说");
}

语法格式五: 接口的抽象方法有两个或以上参数,方法有返回值, 方法体有多条执行语句

@Test
public void test02() {
    // 传统的写法
    Comparator<Integer> comparator01 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        }
    };
    System.out.println(comparator01.compare(95, 27));
    System.out.println("-------------------------");

    // Lambda表达式
    Comparator<Integer> comparator02 = (o1, o2) -> {
        System.out.println(o1);
        System.out.println(o2);
        return o1.compareTo(o2);
    };
    System.out.println(comparator02.compare(12, 21));
}

语法格式六: 当Lambda体只有一条语句时(可能是return语句),return与{}可以省略(return不能单独出现)

public void test02() {
    // 传统写法
    Comparator<Integer> comparator01 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1.compareTo(o2);
        }
    };
    System.out.println(comparator01.compare(95, 27));
    System.out.println("-------------------------");

    // Lambda表达式
    Comparator<Integer> comparator02 = (o1, o2) -> o1.compareTo(o2);
    System.out.println(comparator02.compare(12, 21));
}

方法引用和构造器引用

方法引用

当要传递给Lambda体的操作已经有实现的方法了可以使用方法引用,方法引用和构造器引用就是为了简化Lambda表达式

  • 方法引用可以看做是Lambda表达式深层次的表达,方法引用本质还是Lambda表达式所以也是函数式接口的一个实例
  • 通过方法的名字来指向一个方法可以认为是Lambda表达式的一个语法糖

语法糖(Syntactic sugar)也译为糖衣语法是指计算机语言中添加的某种对语言的功能没有影响但是更方便程序员使用的语法

  • 使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会

方法引用格式: 使用方法引用操作符 “::” 将类或对象与方法名分隔开来,要求Lambda体只有一句语句并且是调用一个对象/类的方法

  • 因为引用的方法的形参和返回值和函数式接口抽象方法的形参和返回值都相同,所以引用方法的形参可以省略
  • 情况1:对象::实例方法名
  • 情况2:类::静态方法名
  • 情况3:类::实例方法名

先写一个Employee实体类

public class Employee {
    private String name;
    private Integer id;
    // get和set方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    // 构造方法
    public Employee() {
    }
    public Employee(Integer id) {
        this.id = id;
        this.name = name;
    }
    public Employee(Integer id,String name) {
        this.id = id;
        this.name = name;
    }
}

**对象::非静态方法:接口抽象方法a在被重写时使用了某一个对象的方法b,如果方法a和b的形参列表,返回值类型都相同,则可以使用方法b实现对方法a的重写替换 **

  • Consumer中的void accept(T t)和PrintStream中的void println(T t)形参列表均为(T t)以及返回值均为void
  • Supplier中的T get()和Employee中的String getName()形参列表均为空以及返回值均为String
@Test
public void test() {
    // 使用Lambda表达式
    Consumer<String> consumer01 = s -> System.out.println(s);
    consumer01.accept("她的手只有我的手四分之三那麼大");
    System.out.println("-----------------------------");
    // 使用方法引用
    //PrintStream printStream = System.out;
    //Consumer consumer02 = printStream::println;
    Consumer<String> consumer02 = System.out::println;
    consumer02.accept("可我還是沒能抓住");
}
@Test
public void test() {
    Employee emp = new Employee(1001,"Tom");
    // 使用Lambda表达式
    Supplier<String> sup1 = () -> emp.getName();
    System.out.println(sup1.get());
    System.out.println("*******************");
    // 使用方法引用
    Supplier<String> sup2 = emp::getName;
    System.out.println(sup2.get());

}

类::静态方法: :接口抽象方法a在被重写时使用了某一个类的静态方法b,如果方法a和b的形参列表,返回值类型都相同,则可以使用方法b实现对方法a的重写替换

  • Comparator中的int compare(T t1,T t2)和Integer中的int compare(T t1,T t2)形参列表均为(T t1,T t2)以及返回值均为int
  • Function中的R apply(T t)和Math中的Long round(Double d)返回值和参数列表为泛型
@Test
public void test07() {
    // 使用Lambda表达式
    Comparator<Integer> comparator01 = (o1, o2) -> Integer.compare(o1, o2);
    System.out.println(comparator01.compare(20, 77));
    System.out.println("----------------------------");
    // 使用方法引用
    Comparator<Integer> comparator02 = Integer::compare;
    System.out.println(comparator02.compare(94, 21));
}

@Test
public void test08(){
    // 使用Lambda表达式
    Function<Double,Long> function01 = aDouble -> Math.round(aDouble);
    System.out.println(function01.apply(3.141));
    System.out.println("------------------------------");
    // 使用方法引用
    Function<Double,Long> function02 = Math::round;
    System.out.println(function02.apply(2.717));
}

类::实例方法: 抽象方法a在被重写时使用了某一个对象的方法b,如果方法a和b的返回值类型相同但方法b的形参少一个,则可以使用方法b实现对方法a的重写替换

  • 方法a的形参列表中有n个参数方法b有n-1个参数,方法a的第1个参数作为方法b的调用者,方法a的后n-1个参数与方法b的n-1个参数匹配(类型相同或满足多态)
  • Comparator中的int comapre(T t1,T t2)方法和String中的int t1.compareTo(t2)方法
  • BiPredicate中的boolean test(T t1, T t2)方法和String中的boolean t1.equals(t2)方法
  • Function中的R apply(T t)方法和Employee中的String toString()方法
@Test
public void test5() {
    // 使用Lambda表达式
    Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
    System.out.println(com1.compare("abc","abd"));
    System.out.println("*******************");
    // 使用方法引用
    Comparator<String> com2 = String :: compareTo;
    System.out.println(com2.compare("abd","abm"));
}

@Test
public void test10(){
    // 使用Lambda表达式
    BiPredicate<String,String> biPredicate01 = (o1, o2) -> o1.equals(o2);
    System.out.println(biPredicate01.test("Kyle", "Kyle"));// true
    System.out.println("----------------------------------");
    // 使用方法引用
    BiPredicate<String,String> biPredicate02 = String::equals;
    System.out.println(biPredicate02.test("Violet", "Violet"));// true
}

@Test
public void test7() {
    Employee employee = new Employee(1001, "Jerry");
    // 使用Lambda表达式
    Function<Employee,String> func1 = e -> e.getName();
    System.out.println(func1.apply(employee));
    System.out.println("*******************");
    // 使用方法引用
    Function<Employee,String> func2 = Employee::getName;
    System.out.println(func2.apply(employee));
}


@Test
public void test11(){
    Ememployee employee = new Employee(1001, "Jerry");
    Function<Stu,String> function01 = employee -> employee.toString();
    System.out.println(function01.apply(employee));
    System.out.println("------------------------------");
    Function<Employee,String> function02 = Employee::toString;
    System.out.println(function02.apply(employee employee));
} 

构造器引用

类名::new: 要求Lambda体只有一句语句并且是用来创建一个对象,构造方法的形参列表和返回值(构造器对应类的对象)要与接口中抽象方法一致才可以替换

  • Supplier中的T get()和Employee的无参构造方法Employee()
  • Function中的R apply(T t)和Employee的有参构造方法Employee(id)
  • BiFunction中的R apply(T t,U u)和Employee的有参构造方法Employee(id,name)
@Test
public void test1(){
    // 传统写法
    Supplier<Employee> sup = new Supplier<Employee>() {
        @Override
        public Employee get() {
            return new Employee();
        }
    };
    System.out.println("*******************");
    // Lambda表达式
    Supplier<Employee>  sup1 = () -> new Employee();
    System.out.println(sup1.get());
    System.out.println("*******************");
    // 构造器引用
    Supplier<Employee>  sup2 = Employee :: new;
    System.out.println(sup2.get());
}

@Test
public void test2(){
    // Lambda表达式
    Function<Integer,Employee> func1 = id -> new Employee(id);
    Employee employee = func1.apply(1001);
    System.out.println(employee);
    System.out.println("*******************");
    // 构造器引用
    Function<Integer,Employee> func2 = Employee :: new;
    Employee employee1 = func2.apply(1002);
    System.out.println(employee1);

}

@Test
public void test3(){
    // Lambda表达式
    BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
    System.out.println(func1.apply(1001,"Tom"));
    System.out.println("*******************");
    // 构造器引用
    BiFunction<Integer,String,Employee> func2 = Employee :: new;
    System.out.println(func2.apply(1002,"Tom"));

}

数组构造引用

数组类型名[]::new: 要求Lambda体只有一条语句并且是创建一个数组对象,接口中抽象方法的形参接收的是数组对象的长度

  • Function中的R apply(T t)和String[]
@Test
public void test4(){
    // Lambda表达式
    Function<Integer,String[]> func1 = length -> new String[length];
    String[] arr1 = func1.apply(5);
    System.out.println(Arrays.toString(arr1));
    System.out.println("*******************");
    // 数组引用
    Function<Integer,String[]> func2 = String[] :: new;
    String[] arr2 = func2.apply(10);
    System.out.println(Arrays.toString(arr2));

}

你可能感兴趣的:(JavaSe,开发语言,java)