java中不太常见的东西(2) - Lambda表达式

    xiaoxiao2021-04-18  68

    引言

    在JDK1.8的新特性中,引入了一个叫Lambda表达式的东西,或许有些小伙伴到现在都没有写过。它的核心理念就是“可以像对象一样传递匿名函数”。在本篇博文中,我会详细介绍Lambda的概念,同时介绍几个JDK1.8中新增加的一些类和关键性的注解,在后面会给出一些简单的Lambda的例子供大家交流。笔者目前整理的一些blog针对面试都是超高频出现的。大家可以点击链接:http://blog.csdn.net/u012403290

    技术点

    1、匿名内部类 在java中,继承一个父类或者实现一个接口的时候,为了书写简单,可以使用匿名内部类,比如说下面这段代码:

    //一个接口,一个待实现的方法 package com.brickworkers; /** * * @author Brickworker * Date:2017年4月14日下午2:24:23 * 关于类LamdaInterface.java的描述:匿名内部类展示 * Copyright (c) 2017, brcikworker All Rights Reserved. */ @FunctionalInterface public interface LamdaInterface { void print(); } //在类中采用静态内部类直接实现接口方法 package com.brickworkers; public class LamdaImpl{ public static void main(String[] args) { LamdaInterface lamdaInterface = new LamdaInterface() {//匿名内部类直接实现接口 @Override public void print() { System.err.println("helloworld"); } }; lamdaInterface.print(); } } //输出:helloworld

    上面这个例子中就是我们用匿名内部类实现了接口中的方法,其实还有更常见的,在我们新写一个线程的时候:

    Thread t1 = new Thread(new Runnable() {//匿名内部类直接实现Runnable接口 @Override public void run() { System.out.println("t1 run"); } }); t1.start();

    通过匿名内部类实现接口相信大家有所了解了,那么我再继承中如何实现呢?比如说我要重写父类中的某个方法:

    package com.brickworkers; import java.util.ArrayList; import java.util.List; public class LamdaImpl{ public static void main(String[] args) { List<String> str = new ArrayList<String>(){ @Override public String toString() { return "匿名内部类直接重写List的toString方法"; } }; System.out.println(str.toString()); } } //输出:匿名内部类直接重写List的toString方法

    这样一来,就不需要重写写一个类专门处理一些方法的重写了,匿名内部类能够很好的简化代码,也可以增强代码的可读性。

    2、函数接口@FunctionalInterface 简单来说就是一个接口中只有唯一的一个函数。比如说我们常用的Runnable接口,就只存在一个待实现的函数,以下是Runnable的源码:

    */ @FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }

    同时,如果这个接口是函数接口,那么需要用注解标注@FunctionalInterface,能否使用Lambda表达式要看这个接口是否存在这个注解,这个注解中,JDK对它的解释是:

    * <p>Note that instances of functional interfaces can be created with * lambda expressions, method references, or constructor references.

    所以Runable这个接口是可以使用Lambda表达式的。

    但是这里要说明一点,因为所有的类都继承Object类,所以如果是重写了toString等属于Object的方法并不属于接口中的一个函数。

    3、Lambda表达式

    “Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包 -百度词条

    之所以称之为匿名函数,因为 比如说它在表达x+y的时候是这么玩的: (x,y) -> x+y; 再比如void函数: () - > {system.out.println()} 具体不再详细介绍,看下面例子就可以了。

    3、介绍几个JDK1.8中新的类和函数接口

    ①Function函数接口:主要是接收一个类型的参数,返回另外一个类型的结果,比如说入参是String,返回是Intger,那么就是一个类型转换的功能。

    ②Consumer函数接口:主要是接收一个类型参数,void方法,没有返回。

    ③Predicate函数接口:主要是接收一个类型参数,boolean方法,可以进行条件判断。

    简单的Lambda例子

    ①我们可以用Lambda表达来写上面描述到的匿名内部类,比如说你现在要写一个线程,再也不用需要用上面用匿名内部类来表示了,你可以直接:

    package com.brickworkers; public class LamdaImpl{ public static void main(String[] args) { Thread t1 = new Thread(() -> {System.out.println("t1 执行");}); t1.start(); } } //输出:t1 执行

    你不需要去指定Runable接口,JVM会自己会根据上下文进行识别,是不是比原来方便了很多呢?然后我们对“() -> {System.out.println(“t1 执行”);}”这部分进行解析,()表示入参,这个表示没有入参; ->表示run方法,为什么表示run方法?因为函数接口中只会存在一个方法。鼠标放在这个上面会展示详细信息: {System.out.println(“t1 执行”);}其实就是放在run方法中的需要执行的代码块。

    ②在JDK1.5中引入了增强for循环,称为for-each,在JDK1.8中引入更简便的迭代方式,就是用Lanbda来替代for-each遍历,请看下面的例子:

    package com.brickworkers; import java.util.ArrayList; import java.util.List; public class LamdaImpl{ public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10; i++) { list.add(i+""); } System.out.print("for-each遍历:"); for (String string : list) { System.out.print(string+" "); } System.out.print("Lanbda遍历:"); list.forEach(o -> {System.out.print(o+" ");}); } } //输出结果: for-each遍历:0 1 2 3 4 5 6 7 8 9 Lanbda遍历:0 1 2 3 4 5 6 7 8 9

    其实是在Iterable接口中多了这个forEach的方法,它的底层其实还是for - each的迭代方式,以下是forEach的源码:

    default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }

    之所以能使用Lambda表达式是因为入参是Consumer这个函数接口。

    稍高端的Lambda表达式例子

    ③在JDK1.8中还增加了Stream API,充分利用现代多核CPU,可以写出更加简洁的代码,这里我们不考虑多线程,我们就简单说说Lambda表达式在流中是怎么操作的。 比如说我们在在一个String的List中找到包含名叫“brickworker”的人,代码如下:

    package com.brickworkers; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public class LamdaImpl{ public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("Amy"); list.add("Gary"); list.add("tom"); list.add("tom"); list.add("brickworker"); list.add("brickworker2"); list.add("brickworker3"); List<String> brickworkers = list.stream().filter(s -> s.indexOf("brickworker") >-1).collect(Collectors.toList()); brickworkers.forEach(s -> System.out.println(s)); } } //输出结果 //brickworker //brickworker2 //brickworker3

    仔细观察上面代码,其实分为了几步: a、list.stream()把list中的数据转化成一个流 b、filter把流中的数据中保留包含brickwork的数据,其他的数据删除 c、collect把流重写转换成一个List

    在来一个更难的例子,比如说,在开发中发现有一个String的List,它主要存储了用户的年龄,我们希望能够拿出年龄在20岁以下的用户并单独存储起来,那么我们就可以通过流和Lambda的形式快速实现:

    package com.brickworkers; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public class LamdaImpl{ public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("13"); list.add("14"); list.add("15"); list.add("20"); list.add("33"); list.add("22"); list.add("45"); List<Integer> ages = list.stream().map(s -> new Integer(s)).filter(s -> s < 20).collect(Collectors.toList()); ages.forEach(s -> System.out.println(s)); } } //输出结果: //13 //14 //15

    在这个过程中,在比较的过程中需要用int型进行比较,但是因为为是一个String的链表,我们需要先通过map的方法将他进行转型。在map的这个方法中,入参其实就是我们上面提到的Function函数接口,它支持入参一个类型,返回另外一个类型。以下是map的源码:

    /** * Returns a stream consisting of the results of applying the given * function to the elements of this stream. * * <p>This is an <a href="package-summary.html#StreamOps">intermediate * operation</a>. * * @param <R> The element type of the new stream * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>, * <a href="package-summary.html#Statelessness">stateless</a> * function to apply to each element * @return the new stream */ <R> Stream<R> map(Function<? super T, ? extends R> mapper);//Function接口进行转型

    当然了,在Collectors的静态方法中不仅仅只有toList,还可以toMap和toSet等等,具体使用要看具体的场景。还有很多简洁方便的使用方式,希望大家自己去探究。

    尾记

    关于Lambda就介绍这么多,个人意见:在日常的开发中,如果你没有见到过项目中有这样的东西,就不要用Lambda来写了,因为大多的程序员没有了解到这些东西,阅读代码会造成很大的困扰。同时,不常用肯定是有原因的,要么不方便,要么性能不好。但是面试的时候,可能面试官会考察你JDK版本的新特性哦。如果大家有什么问题可以在下方留言交流,共同学习进步。

    转载请注明原文地址: https://ju.6miu.com/read-675584.html

    最新回复(0)