Lambda表达式

函数式接口

参考:Java 8函数式接口functional interface的秘密
函数式接口是指只定义了唯一的抽象方法的接口(除了隐含的Object对象的公共方法), 因此最开始也就做SAM类型的接口(Single Abstract Method)。除了Java API里面已有的一些接口(Runnable,Comparator,FileFilter,InvocationHandler,ActionListener等)被定义为了函数是接口外,又新增了几个函数式接口,其中主要的有4个函数式接口Consumer,Supplier,Function,Predicate。并新增了一个注解类型@FunctionalInterface用来标记函数式接口。

  • Consumer<T>接口:
    传入一个参数,无返回值,纯消费。 方法为void accept(T t)
  • Supplier<T>接口:
    无参数传入,返回一个结果,方法为T get()
  • Function<T, R>接口:
    传入一个参数,返回一个结果,方法为R apply(T t)
  • Predicate<T>接口:
    传入一个参数,返回一个bool结果, 方法为boolean test(T t)

Lambda表达式

参考:深入浅出 Java 8 Lambda 表达式
Lambda 表达式是一种简化书写方式,他通常用于简化函数式接口的书写,简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。其体现的是一种函数式编程的思想,即只有映射过程,没有储存变量,将函数作为变量去传递,从而在某些情况下避免出现了匿名类的形式。因此,从现在开始,要以一种“将过程/方法当作参数进行传递”的思想去看待接下来的内容。
其书写规则如下:

  • 一个 Lambda 表达式可以有零个或多个参数
  • 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同
  • 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
  • 空圆括号代表参数集为空。例如:() -> 42
  • 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a
  • Lambda 表达式的主体可包含零条或多条语句
  • 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
  • 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空

箭头”->”操作符

Java 中的 Lambda 表达式通常使用(argument) -> (body) 语法书写。并简化了new 构造函数 单一方法名等信息以下例子摘自深入浅出 Java 8 Lambda 表达式
Runnable()接口的简化:

//旧方法:
new Thread(new Runnable() {
@Override
public void run() {
    System.out.println("Hello from thread");
}
}).start();

//新方法:
new Thread(
() -> System.out.println("Hello from thread")
).start();

事件处理ActionListener的简化:

//Old way:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
    System.out.println("The button was clicked using old fashion code!");
}
});

//New way:
button.addActionListener( (e) -> {
    System.out.println("The button was clicked. From Lambda expressions !");
});

新增四大函数式接口的Lambda->写法

例子来源:二、函数式接口

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.junit.Test;

/**
 * Java8内置四大核心函数式接口
 * 
 * Consumer<T> : 消费型接口
 *      void accept(T t);
 * 
 * Supplier<T> : 供给型接口
 *      T get();
 * 
 * Function<T, R> : 函数型接口
 *      R apply(T t);
 * 
 * Predicate<T> : 断言型接口
 *      boolean test(T t);
 * 
 * @author TongWei.Chen
 * @date 2017年3月31日21:31:48
 */
public class TestFunction {

    //测试消费型接口
    @Test
    public void test1() {
        happy(100, x -> System.out.println(x));
    }

    //消费型接口
    public void happy(double money, Consumer<Double> consumer) {
        consumer.accept(money);
    }

    //测试供给型接口
    @Test
    public void test2() {
        //生成10个随机数
        List<Integer> list = getNumList(10, () -> (int)(Math.random() * 100));
        for(Integer integer : list) {
            System.out.println(integer);
        }
    }

    /**
     * 供给型接口
     * 产生指定个数的整数并放入集合中
     * 
     * @param num:生成数字的个数
     * @param supplier
     * @return
     */
    public List<Integer> getNumList(int num, Supplier<Integer> supplier) {
        List<Integer> list = new ArrayList<>();
        for(int i = 0; i < num; i ++) {
            Integer n = supplier.get();
            list.add(n);
        }
        return list;
    }

    //测试函数型接口
    @Test
    public void test3() {
        String str1 = getString("\t\t\t 我爱Java,Java使我快乐。", (str) -> str.trim());
        System.out.println("去完空格后的字符串:" + str1);

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

        String str2 = getString("我爱Java,Java使我快乐。", str -> str.substring(0, 6));
        System.out.println("截取后的字符串:" + str2);
    }

    //需求:处理各种字符串
    //函数型接口
    public String getString(String str, Function<String, String> function) {
        return function.apply(str);
    }

    //测试断言型接口
    @Test
    public void test4() {
        List<String> list = Arrays.asList("Hello", "tongwei", "Lambda", "www", "ok");
        //假设长度大于3就算满足条件
        List<String> strList = filterStr(list, str -> str.length() > 3);

        for(String str : strList) {
            System.out.println(str);
        }
    }

    //断言型接口
    //需求:将满足条件的字符串,放入集合中
    public List<String> filterStr(List<String> list, Predicate<String> pre) {
        List<String> strings = new ArrayList<>();
        for(String str : list) {
            if(pre.test(str)) {
                strings.add(str);
            }
        }
        return strings;
    }
}

双冒号”::”操作符–方法引用

参考:Java 8中的::(双冒号)运算符
从上一节我们可以看出Lambda表达式所作的事情就是将自己编写的方法直接当作参数传递给新的方法。那么,难道对于一些已经存在的好方法我们就不可以当作参数传递给新的方法了吗?基于这个想法,Java引入了双冒号操作符,其目的就是为了将已有的方法当作参数传递给新的方法。看一个例子:

import java.util.function.Function;
public class Test{
    public static void main(String[] args) {
        //old way:
        Function<Integer ,Integer> abs1 = new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer a) {
                return Math.abs(a);
            }
        };
        System.out.println(abs1.apply(-1));

        //use the lambda expression
        Function<Integer, Integer> abs2 = (a) -> {return Math.abs(a);};
        System.out.println(abs2.apply(-2));

        //use the double colon
        Function<Integer, Integer> abs3 = Math :: abs;
        System.out.println(abs3.apply(-3));
    }       
}

从上面这个例子可以看出, 当我们需要给函数式接口传递一个方法时,我们可以用lambda表达式进行简化,如果此时要传递的方法为已经存在的方法(要保证参数一致,即已有方法的输入和输出参数和接口的输入输出参数要一样),则可以不用自己写lambda表达式,而是直接通过运用双冒号来引用方法进行传递。
具体的双冒号使用方法
- 引用静态方法: 类名::方法名
- 引用实例方法: 实例变量名::方法名
- 引用构造函数: 类名::构造器名
- 引用构造函数创建数组: 类名[]::构造器名

注意,由于进行了简化,所以一定要保证引用的方法的输入输出参数个数以及类型要和得到该方法的函数式接口的输入输出参数个数以及类型相匹配。

forEach()

在Java 8中,interface Iterable<T>接口有一个默认的default void forEach​(Consumer<? super T> action)方法,该方法可以接受一个lambda表达式或者一个实现Consumer接口的类,用以对集合中每个元素进行操作。

HashMap<Integer, String> map = new HashMap<>();
ArrayList<Integer> list = new ArrayList<>();
map.forEach((k, v) -> System.out.println(k+", "+v));
list.forEach(System.out::println);

Stream API

参考:
Java 8 中的 Streams API 详解
Java官方API: Package java.util.stream
通过Stream类,可以将现有的集合元素、输入元素、网络元素等转换成一个流,然后利用函数式编程的思想,通过一系列的方法对流内元素进行加工,最终得到符合要求的数据集合。值得注意的是,由于中间过程的一系列方法都符合函数式编程的思想,因此并不会在每个方法中单独进行(具有这种性质的中间方法称为惰性方法。),而是等待最终调用的方法执行后,才正式开始加工数据。另一方面,同样由于采用了函数式编程的思想,Stream类在处理并行数据是也十分方便。关于流和数据集合的区别可以通过API中的描述看出:

  • No storage. A stream is not a data structure that stores elements; instead, it conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.
  • Functional in nature. An operation on a stream produces a result, but does not modify its source. For example, filtering a Stream obtained from a collection produces a new Stream without the filtered elements, rather than removing elements from the source collection.
  • Laziness-seeking. Many stream operations, such as filtering, mapping, or duplicate removal, can be implemented lazily, exposing opportunities for optimization. For example, “find the first String with three consecutive vowels” need not examine all the input strings. Stream operations are divided into intermediate (Stream-producing) operations and terminal (value- or side-effect-producing) operations. Intermediate operations are always lazy.
  • Possibly unbounded. While collections have a finite size, streams need not. Short-circuiting operations such as limit(n) or findFirst() can allow computations on infinite streams to complete in finite time.
  • Consumable. The elements of a stream are only visited once during the life of a stream. Like an Iterator, a new stream must be generated to revisit the same elements of the source.

更为具体的使用方法可以参见Java 8 中的 Streams API 详解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值