Java中lambda表达式及其应用

目录

1、lambda表达式的语法

(1)为什么需要使用lambda表达式?

2、使用lambda表达式的条件:函数式接口

(1)Predicate 接口

(2)Consumer 接口

(3)Function 接口

3、方法引用

(1)为什么说使用方法引用时,没有实际调用这个方法?

(2)方法引用分类

4、复合lambda表达式

(1)比较器复合

(2)谓词复合/Predicate接口

(3)函数复合/Function接口


2022年圣诞节到来啦,很高兴这次我们又能一起度过~

1、lambda表达式的语法

        java中lambda表达式通常写成下边这个样子:

  • 参数列表——这里采用了Comparator接口中compare方法的参数,两个Apple对象。
  • 箭头——箭头 -> 把参数列表与Lambda主体分隔开。
  • Lambda主体——比较两个Apple的重量。

        抽象下,lambda表达式的语法就是下边的模样,看起来还是比较简单的

(parameters) -> {expression}

不能省略大括号情况:

  • 方法体有返参时不能省略大括号
  • 方法体有多行逻辑代码时不能省略大括号

省略小括号情况:

  • 方法只有一个参数,而且这个参数的类型可以推导出,那么可以省略小括号

(1)为什么需要使用lambda表达式?

        通过下边这断代码,可以清晰的领略到使用lambda表达式的好处:

public class Sorting {

    public static void main(String... args) {

        //排序数据:根据重量进行排序
        List<Apple> inventory = new ArrayList<>();
        inventory.addAll(Arrays.asList(new Apple(80, "green"), new Apple(155, "green"), new Apple(120, "red")));

        //1、单独定义一个比较器类
        inventory.sort(new AppleComparator());
        System.out.println(inventory);

        //2、使用内部类
        inventory.sort(new Comparator<Apple>() {
            public int compare(Apple a1, Apple a2) {
                return a1.getWeight().compareTo(a2.getWeight());
            }
        });
        System.out.println(inventory);

        //3、使用lambda表达式
        inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
        System.out.println(inventory);

        //4、使用方法引用
        inventory.sort(comparing(Apple::getWeight));
        System.out.println(inventory);
    }

    public static class Apple {
        private Integer weight = 0;
        private String color = "";

        public Apple(Integer weight, String color) {
            this.weight = weight;
            this.color = color;
        }

        public Integer getWeight() {
            return weight;
        }

        public void setWeight(Integer weight) {
            this.weight = weight;
        }

        public String getColor() {
            return color;
        }

        public void setColor(String color) {
            this.color = color;
        }

        public String toString() {
            return "Apple{" +
                    "color='" + color + '\'' +
                    ", weight=" + weight +
                    '}';
        }
    }

    static class AppleComparator implements Comparator<Apple> {
        public int compare(Apple a1, Apple a2) {
            return a1.getWeight().compareTo(a2.getWeight());
        }
    }
}

        同样是排序操作,你有没有发觉使用lambda表达式后,整个代码都清晰简单多了。

2、使用lambda表达式的条件:函数式接口

        用一句话来说,函数式接口就是只定义一个抽象方法的接口。接口中现在还可以拥有默认方法(后续再探讨这个)。哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。

        用函数式接口可以干什么呢? Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。具体说来,就是函数式接口一个具体实现的实例。// lambda表达式是函数式接口一个具体实现的实例

    public static void main(String[] args) {
        // 传统方式方式:这是一个Runnable接口具体实现的实例
        Runnable runnable = new Runnable() { 
            @Override
            public void run() {
                System.out.println("Hello");
            }
        };
        //lambda方式:这也是一个Runnable接口具体实现的实例
        Runnable runnable1 = () -> System.out.println("Hello"); 
    }

        所以,Lambda 表达式本质上就是函数式接口实例化过程的简化版

        你可能会问,那为什么只有在需要函数式接口的时候才可以传递Lambda呢?在需要其他非函数式接口的时候就不能用吗?这其实就是语言设计者的一个设计方式的问题,特定的语法只能在特定的条件下使用,一方面带来便利,一方面也避免语言变得更加复杂。//锦上添花之作

        Java 8的 java.util.function 包中引入了一些新的函数式接口。比如Predicate、ConsumerFunction等。

(1)Predicate 接口

        java.util.function.Predicate<T> 接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。所以,在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。//接收参数,返回一个boolean值

        比如,Stream中的 filter 方法,接收一个 Predicate 类型的入参

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

//Stream中的filter方法
Stream<T> filter(Predicate<? super T> predicate);

        使用示例://大多数情况下,lambda都会结合Stream来使用

    public static void main(String[] args) {
        List<String> list = new ArrayList<>(Arrays.asList("A","B","C"));
        list.stream().filter(r -> !r.equals("A")).collect(Collectors.toList());
    }

(2)Consumer 接口

        java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回值(void)。如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。//接收参数,无返回值

        比如,Stream中的 forEach 方法,接收一个 Consumer 类型的入参

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

//Stream中的forEach方法
void forEach(Consumer<? super T> action);

        在下面的代码中,使用forEach方法,接受一个String的列表,并配合Lambda来打印列表中的所有元素(对其中每个元素执行操作)。

    public static void main(String[] args) {
        List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
        list.stream().forEach(System.out::println);
    }

(3)Function 接口

        java.util.function. Function<T,R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口。//接收一个类型,返回另一个类型

        比如,Stream中的 map 方法,接收一个 Function 类型的入参

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

//Stream中的map方法
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

        在下面的代码中,利用map方法,以将一个String列表映射到一个新的String列表。

    public static void main(String[] args) {
        List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
        List<String> collect = list.stream().map(String::toLowerCase).collect(Collectors.toList());
    }

        最后,汇总一些Lambda及函数式接口的例子:

        //在介绍函数式接口时,使用了Stream中的相关方法进行举例。实际上,我们大多数的应用场景都可以借助Stream来进行解决,可以理解为Stream是在函数式接口上做了进一步的封装,在它的基础上使用lambda也非常的方便

        请注意,任何函数式接口都不允许抛出受检异常checked exception)。如果你需要Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个 try/catch 块中。

3、方法引用

        方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法//方法引用是一个lambda表达式的快捷写法

        它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。

        它是如何工作的呢?当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。例如,String::toLowerCase 就是引用了String类中定义的方法 toLowerCase。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(String s) -> ss.toLowerCase() 的快捷写法。

        你可以把方法引用看作仅仅针对涉及单一方法的Lambda的语法糖,因为你表达同样的事情时要写的代码更少了。

(1)为什么说使用方法引用时,没有实际调用这个方法?

        方法引用是一种简化 Lambda 表达式的机制,在方法引用中,你指定了一个现有方法的名称,但并没有在这个引用中执行该方法。相反,该引用仅代表了对方法的引用,以便在需要时按需调用。什么意思?通过下边的一段代码便清晰了

// Lambda 表达式
Consumer<String> consumerLambda = s -> System.out.println(s);

// 方法引用
Consumer<String> consumerMethodRef = System.out::println;

        在上面的例子中,System.out::println 是一个方法引用,指向了 println 方法。但在创建 consumerMethodRef 时,并没有立即执行 println 方法,而是创建了一个 Consumer 的实例,这个实例能够在需要时调用 println 方法。//所以,方法引用只是一段待执行的逻辑,与Lambda的本质是一样的

(2)方法引用分类

        1)指向静态方法的方法引用,例如 Integer 的 parseInt 方法,写作 Integer::parseInt。

    public static void main(String[] args) {
        List<String> list = new ArrayList<>(Arrays.asList("1", "2", "3"));
        list.stream().map(Integer::parseInt).forEach(System.out::println);
    }

        2)指向任意类型的实例方法的方法引用,例如 String 的 length方法,写作 String: : length。

    public static void main(String[] args) {
        List<String> list = new ArrayList<>(Arrays.asList("Hello", "Hi", "World"));
        list.stream().map(String::length).forEach(System.out::println);
    }

        3)指向现有对象的实例方法的方法引用,假设你有一个局部变量 Employee e,它支持实例方法 getName,那么你就可以写成 e::getName。

    public static void main(String[] args) {
        Employee e = new Employee("jack", 20);
        Runnable runnable = e::getName; //Runnable接口的实现
    }

        4)构造函数的引用。对于一个现有构造函数,可以利用它的名称和关键字new来创建它的一个引用:ClassName :: new。它的功能与指向静态方法的引用类似。例如,假设有一个构造函数没有参数,它适合 Supplier 的签名 () -> HashMap。你可以这样做:

    public static void main(String[] args) {
        Supplier<Map<String, Object>> supplier = HashMap::new;
    }

        以下是四种分类方式的汇总:

类型语法例子
引用静态方法ContainingClass::staticMethodNamePerson::compareByAge
引用特定对象的实例方法ContainingObject::instanceMethodNamemyComparisonProvider::compareByName
myApp::appendStrings2
特定类型任意对象的实例方法的引用ContainingType::methodNameString::compareToIgnoreCase
String::concat
构造函数的引用ClassName::new HashSet::new

4、复合lambda表达式

        复合lambda表达式就是把多个简单的Lambda复合成复杂的表达式。比如,你可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输人。你可能会想,函数式接口中怎么可能有更多的方法呢?毕竟,这违背了函数式接口的定义啊!窍门在于,这些方法都是默认方法,也就是说它们不是抽象方法。

(1)比较器复合

        逆序:Comparator 接口有一个默认方法 reverse 可以使给定的比较器逆序。

    public static void main(String[] args) {
        Employee e = new Employee("jack", 20);
        List<Employee> list = new ArrayList<>(Arrays.asList(e));
        //逆序
        list.sort(Comparator.comparing(Employee::getSalary).reversed());
    }

        比较器链:如果想对一个Comparator比较的结果来做进一步的比较,那么使用thenComparing方法就可以提供第二个Comparator。

Comparator.comparing(Employee::getSalary).thenComparing(Employee::getName);

(2)谓词复合/Predicate接口

        谓词接口包括三个方法:negate、and or,让你可以重用已有的 Predicate 来创建更复杂的谓词。比如,你可以使用 negate 方法来返回一个 predicate 的非:

    public static void main(String[] args) {
        Employee e0 = new Employee("jack", 20);
        Employee e1 = new Employee("Mark", 50);
        List<Employee> list = new ArrayList<>(Arrays.asList(e0, e1));

        Predicate<Employee> predicate = s -> s.getName().equals("jack");
        Predicate<Employee> negate = predicate.negate(); //取非
        Predicate<Employee> and = predicate.and(employee -> employee.getSalary() > 30) //连接词
                .or(employee -> employee.getSalary() < 20);

        list.stream().filter(predicate); //实际使用
    }

(3)函数复合/Function接口

        Function接口拥有 andThencompose 两个默认方法,它们都会返回Function的一个实例。

    public static void main(String[] args) {
        Function<Integer, Integer> f0 = x -> x + 1;
        Function<Integer, Integer> f1 = x -> x * 2;

        Function<Integer, Integer> andThen = f0.andThen(f1);
        Integer apply0 = andThen.apply(2); // (2+1)*2 -> 6

        Function<Integer, Integer> compose = f0.compose(f1);
        Integer apply1 = compose.apply(2); // (2*2)+1 -> 5
    }

        如上例所示,根据执行结果可知,andThen是在一个步骤之后进行连接操作,compose是在一个步骤之前进行连接操作。

        关于Stream流的相关操作,可以参考我的这篇文章《Java中的Stream流和流操作

        想详细了解Java函数式接口的相关内容,可以参考这篇文章《Java函数式接口

        至此,本文结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

swadian2008

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

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

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

打赏作者

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

抵扣说明:

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

余额充值