详解java中的Lambda表达式

Lambda表达式的前世今生(来历与概述)

Lambda表达式的前世------匿名类

以往,使用单一抽象方法的接口被用作函数类型。 它们的实例表示函数(functions)或行动(actions)。 自从 JDK 1.1 于 1997 年发布以来,创建函数对象的主要手段就是匿名类。匿名类,通俗地讲,就是没有类名,直接通过new关键字创建这个类的实例。下面是匿名类的一个例子:

java.util包中的Comparator接口

public interface Comparator<T> {
	int compare(T o1, T o2);
}

使用匿名类创建排序的比较方法(强制排序顺序):

Collections.sort(words, new Comparator<String>() {
	public int compare(String s1, String s2) {
    	return Integer.compare(s1.length(), s2.length());
 }
});

匿名类适用于需要函数对象的经典面向对象设计模式,特别是策略模式,上面的匿名类是排序字符串的具体策略。 然而,匿名类确实过于冗长。

Lambda表达式的今生

在 Java 8 中,语言形式化了这样的概念,即使用单个抽象方法的接口是特别的,应该得到特别的对待。 这些接口现在称为函数式接口,并且该语言允许你使用lambda 表达式或简称 lambda 来创建这些接口的实例。 Lambdas 在功能上与匿名类相似,但更为简洁。 下面的代码使用 lambdas 替换上面的匿名类。清晰明了:

Collections.sort(words,
   (s1, s2) -> Integer.compare(s1.length(), s2.length()));

Lambda表达式语法

JDK1.8之后引入的一种语法,他的写法是使用一个->符号,箭头将Lambda表达式分为左右两部分,左边写的是实现的这个接口中的抽象方法中的形参列表,右边就是对抽象方法的处理

(实现的这个接口中的抽象方法中的形参列表) -> 抽象方法的处理

具体写法

因为Lambda表达式的核心就是实现的这个接口中的抽象方法中的形参列表) -> 抽象方法的处理,因此根据形参列表与返回值的不同,Lambda表达式的具体写法也不相同

无返回值有形参的抽象方法

public class MyTest1 {
    public static void main(String[] args) {
        MyInterface myInterface = new MyInterface() {
            @Override
            public void show(int a, int b) {
                System.out.println(a + b);
            }
        };

        myInterface.show(20, 30);//50

        //简写1:方法名可以自己推断出来
        MyInterface myInterface1 = (int a, int b) -> {
            System.out.println(a + b);
        };

        myInterface1.show(20, 40);//60

        //简写2:可以省略形参列表中的形参类型
        MyInterface myInterface2 = (a, b) -> {
            System.out.println(a + b);//70
        };

        myInterface2.show(20, 50);

        //简写3:如果抽象方法中只有一行代码,可以省略方法体的大括号,当然,如果不止一行,就不能省略
        MyInterface myInterface3 = (a, b) -> System.out.println(a + b);
        myInterface3.show(20, 60);//80
    }
}
public interface MyInterface {
    public abstract void show(int a,int b);
}

主要特点:

  • 可以省略方法名,IDEA会帮你自动检测方法名;
  • 可以省略方法中的形参类型;
  • 如果对抽象方法的实现逻辑只有一行,可以省略方法体的大括号,当然如果不止一行,就不能省略了;

有返回值的抽象方法

public class MyTest2 {
    public static void main(String[] args) {
        MyInterface1 test1 = new MyInterface1() {
            @Override
            public int test(int a, int b) {
                return a - b;
            }
        };
        System.out.println(test1.test(90, 8));//82

        //简写1:
        MyInterface1 test2 = (int a, int b) -> {
            return a - b;
        };
        System.out.println(test2.test(20, 10));//10

        //简写2:
        MyInterface1 test3 = (a, b) -> {return a - b;};
        System.out.println(test3.test(30, 10));//20

        //简写3:这个有返回值的方法,不能直接去掉大括号,还需要去掉return关键字
        MyInterface1 test4 = (a, b) -> a - b;
        System.out.println(test4.test(40, 10));//30
    }
}
public interface MyInterface1 {
    public abstract int test(int a,int b);
}
  • 有返回值的方法,如果要去掉大括号,还需要去掉return关键字;

有一个形参的抽象方法

public class MyTest3 {
    public static void main(String[] args) {
        MyInterface2 myInterface = a -> a-20;
        myInterface.show(20);
    }
}
public interface MyInterface2 {
    public abstract int show(int a);
}
  • 形参列表中只有一个参数,可以去掉形参的括号

Lambda表达式作为参数传递

参数的类型为函数式接口

import java.util.Arrays;
public class MyTest4 {
    public static void main(String[] args) {
        Integer[] ints = {89, 67, 23};
        Arrays.sort(ints, (o1, o2) -> o1-o2);
        System.out.println(Arrays.toString(ints));
        //[23, 67, 89]
    }
}

什么情况下使用

Lambda表达式不是万能的,他需要函数式接口的支持,只有当某个方法需要传入的参数类型为函数式接口或者作为方法返回值时,才能使用。

什么是函数式接口:
函数式接口的定义是: 只包含一个抽象方法的接口,称为函数式接口;
其实我们的Lambda表达式就是对函数式接口的一种简写方式,所以只有是函数式接口,我们才能用Lambda表达式;再换句话说,Lambda表达式需要函数式接口的支持,那函数式接口我们可以自己定义,当然JDK1.8也给我们提供了一些现成的函数式接口;

//自定义一个函数式接口
@FunctionalInterface//此注解用来表明这是一个函数式接口
public interface MyInterFace<T> {
//函数式接口只有一个抽象方法
void getValue(T t);
}
//自定义的函数式接口的Lambda表达式
MyInterFace<String> face=(x)->System.out.println(x);
  • 可以通过 Lambda 表达式来创建该接口的对象,我们可以在任意函数式接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口;
  • 为什么只能有一个抽象方法,如果有多个抽象方法,这个接口不是函数式接口,简写的时候省略了方法名,IDEA不能知道到底重写的是哪一个方法,不能推断出来;
  • 注解写在接口声明上面,如果不报错,就不是函数式接口;
  • JDK1.8之后,提供了很多函数式接口,作为参数传递;

常用的函数式接口

Java 8在java.util.function包下预定义了大量的函数式接口供我们使用

我们重点学习下面4个接口

  • Supplier接口
  • Consumer接口
  • Predicate接口
  • Function接口

Supplier接口

Supplier< T >:包含一个无参的方法

  • T get():获得结果
  • 该方法不需要参数,他会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
  • Supplier< T >接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会产生什么类型的数据供我们使用
import java.util.function.Supplier;

public class SupplierDemo {
    public static void main(String[] args) {
        String s = getString(()->"张三");
        int i = getInteger(()->18);

        System.out.println(s+","+i);
    }

    //定义一个方法,返回一个字符串数据
    private static String getString(Supplier<String> sup){
        return sup.get();
    }

    //定义一个方法,返回一个整数数据
    private static Integer getInteger(Supplier<Integer> sup){
        return sup.get();
    }
}

Consumer接口

Consumer< T >:包含两个方法

  • void accept(T t):对给定的参数执行此操作
  • default Consumer < T > andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行after操作
  • Consumer< T >接口也被称为消费型接口,它消费的数据类型由泛型指定
import java.util.function.Consumer;

public class ConsumerDemo {
    public static void main(String[] args) {

        operatorString("张三", (s) -> System.out.println(s));

        operatorString("张三", (s) -> System.out.println(s), (s)-> System.out.println(new StringBuilder(s).reverse().toString()));
    }

    //定义一个方法,消费一个字符串数据
    private static void operatorString(String name, Consumer<String> con) {
        con.accept(name);
    }

    //定义一个方法,用不同的方式消费同一个字符串两次
    private static void operatorString(String name, Consumer<String> con1,Consumer<String> con2) {
//        con1.accept(name);
//        con2.accept(name);
        //返回一个组合的Consumer
        con1.andThen(con2).accept(name);
    }
}

Predicate接口

Predicate< T >:常用的四个方法

  • boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
  • default Predicate< T > negate():返回一个逻辑的否定,对应逻辑非
  • default Predicate< T > and():返回一个组合判断,对应短路与
  • default Predicate< T > or():返回一个组合判断,对应短路或
  • isEqual():测试两个参数是否相等
  • Predicate< T >:接口通常用于判断参数是否满足指定的条件
import java.util.function.Predicate;

public class ConsumerTest {
    public static void main(String[] args) {
        boolean string = chenkString("张三", s -> s.equals("张三"));
        System.out.println(string);

        boolean hello = chenkString("hello", s -> s.length() > 8, s -> s.length() < 18);
        System.out.println(hello);
    }

    //判定给定的字符串是否满足要求
//    private static boolean chenkString(String s, Predicate<String> pre){
//        return pre.test(s);
//    }

    private static boolean chenkString(String s, Predicate<String> pre){
        return pre.negate().test(s);
    }

//    private static boolean chenkString(String s, Predicate<String> pre, Predicate<String> pre1){
//        return pre.and(pre1).test(s);
//    }

    private static boolean chenkString(String s, Predicate<String> pre, Predicate<String> pre1){
        return pre.or(pre1).test(s);
    }
}

Function接口

Runction<T,R>:常用的两个方法

  • R apply(T t):将此函数应用于给定的参数
  • default< V >:Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
  • Function<T,R>:接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新值
import java.util.function.Function;

public class ConsumerTest {
    public static void main(String[] args) {
        convert("18", s -> Integer.parseInt(s));
        convert(20, integer -> String.valueOf(integer + 18));
        convert("245", s -> Integer.parseInt(s), integer -> String.valueOf(integer + 18));
    }   
    //定义一个方法,把一个字符串转换成int类型,在控制台输出
    private static void convert(String s, Function<String, Integer> fun) {
        int i = fun.apply(s);
        System.out.println(i);
    }    
    //定义一个方法,把int类型数据加上一个整数之后,转换为字符串在控制台输出
    private static void convert(int i, Function<Integer, String> fun) {
        String s = fun.apply(i);
        System.out.println(s);
    }   
    //定义一个方法,把一个字符串转换int类型,把int类型的数据加上一个整数后,转换成字符串在控制台输出
    private static void convert(String s, Function<String, Integer> fun1, Function<Integer, String> fun2) {
        String s1 = fun2.apply(fun1.apply(s));
        System.out.println(s1);
    }
}

方法引用

先来看一下什么是方法引用:

方法引用其实是Lambda表达式的另一种写法,当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
注意: 实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!
方法引用:使用操作符::将方法名和对象或类的名字分隔开来,三种主要使用情况为:

对象::实例方法
类::静态方法
类::实例方法

对象::实例方法

import java.util.function.Consumer;

public class MyTest {
    public static void main(String[] args) {
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        consumer.accept("aaaaaaaaaaaaaa");
        //aaaaaaaaaaaaaa

        //简写1:
        Consumer<String> consumer1 = (String s) -> {
            System.out.println(s);
        };
        consumer1.accept("abc");
        //abc

        //简写2:
        Consumer<String> consumer2 = (s) -> System.out.println(s);
        consumer2.accept("bcd");
        //bcd

        //简写3:
        Consumer<String> consumer3 = System.out::println;
        consumer3.accept("abc");
        //abc
    }
}

为什么可以写成上述方式?
因为:System.out.println(s);与void accept(String s)一样,都是使用s作为参数,返回值是void,因此就可以简写为简写3;

类::静态方法

import java.util.function.BinaryOperator;

public class MyTest1 {
    public static void main(String[] args) {
        BinaryOperator<Double> operator = new BinaryOperator<Double>(){
            @Override
            public Double apply(Double o, Double o2) {
                return Math.max(o,o2);
            }
        };

        System.out.println(operator.apply(2.13, 3.12));//3.12

        BinaryOperator<Double> operator2 = (o, o2) -> Math.max(o,o2);
        System.out.println(operator2.apply(2.13, 3.12));//3.12

        BinaryOperator<Double> operator3 = Math::max;

        Double max = operator3.apply(5.0, 20.0);
        System.out.println(max);//20.0

    }
}

因为Math.max()所需要的参数以及返回值与重写的accpet()一样,因此可以简写为类::静态方法

下面再举一个例子

import java.util.Comparator;
public class MyTest2 {
    public static void main(String[] args) {
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };

        System.out.println(comparator.compare(20, 12));//1

        Comparator<Integer> comparator1 = Integer::compareTo;
        System.out.println(comparator1.compare(20, 12));//1
    }
}

类::实例方法

import java.util.Comparator;
public class MyTest2 {
    public static void main(String[] args) {
        Comparator<String> comparator = new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        };

        System.out.println(comparator.compare("20", "12"));//1

        Comparator<String> comparator1 = String::compareTo;
        System.out.println(comparator1.compare("20", "12"));//1
    }
}

为什么可以这样写?、
传递过来的两个参数,一个作为调用者,一个作为参数,这时候,使用类::实例方法简写;

构造引用

格式:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!

public class MyTest4 {
    public static void main(String[] args) {
        Student student = new Student("张三", 23);
        BiFunction<String, Integer, Student> function = new BiFunction<String, Integer, Student>() {
            @Override
            public Student apply(String s, Integer integer) {
                return student;
            }
        };

        BiFunction<String, Integer, Student> function1 = (s, integer) -> student;

        BiFunction<String, Integer, Student> function2 = Student::new;
    }
}

  • 11
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java 函数的 Lambda 表达式是 Java 8 引入的一种新特性。Lambda 表达式可以简化代码并提高开发效率,特别是在需要使用函数接口(Functional Interface)的情况下。函数接口是只有一个抽象方法的接口,Lambda 表达式可以实现该方法来创建该接口的实例。Lambda 表达式的语法如下: (parameter1, parameter2, ...) -> { statement1; statement2; ... } 其,参数和语句块可以根据具体需求变化。例如,一个求和函数可以用如下 Lambda 表达式实现: (a, b) -> a + b Lambda 表达式的类型是由泛型的函数接口确定的。在 Java 已经定义了许多常用的函数接口,例如 Supplier、Consumer、Predicate 和 Function 等。 Lambda 表达式可以简化代码并提高可读性。例如,以下代码使用了匿名内部类实现 Runnable 接口: new Thread(new Runnable() { @Override public void run() { System.out.println("Hello World!"); } }).start(); 可以通过 Lambda 表达式简化为: new Thread(() -> System.out.println("Hello World!")).start(); Lambda 表达式还可以配合 Stream API 使用,提供了更加方便的数据处理方法。例如,以下代码使用 Lambda 表达式和 Stream API 输出大于等于 10 的偶数: IntStream.range(1, 11) .filter(n -> n % 2 == 0) .forEach(System.out::println); 总之,Lambda 表达式是 Java 8 引入的一个重要的特性,可以大大简化代码和提高开发效率。需要注意的是,Lambda 表达式只能用于函数接口,且语法灵活,需要根据具体需求选择不同的写法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小猹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值