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

引言

在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版本的新特性哦。如果大家有什么问题可以在下方留言交流,共同学习进步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章