Java基础系列-Lambda

作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题


代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等


联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

一、概述

JDK1.8引入了函数式编程,重点包括函数式接口、lambda表达式、方法引用等。

所谓函数式编程就是将函数(一段操作)作为一个基本单位进行传递。以前的Java中参数只能是具体的变量,函数式编程打破这一规范,可以将整个方法作为一个参数传递。

Java毕竟是面向对象的编程语言,你要传递的东西,必须是一个类或接口的对象或者一个基本类型变量,所以Java就定义了函数式接口,用来承载传递的函数。

二、函数式接口

2.1 函数式接口

函数式接口是在JDK1.8中提出的新概念,但对应的却是老结构,在以往版本的JDK中就已经存在这种结构,只是没有定义化。

函数式接口就是只有一个抽象方法的接口。常用的函数式接口有Runnable、Comparator等。

JDK1.8将这些接口取了一个统一的名称函数式接口,为了规范化,同时避免用户自定义函数式接口时错误的添加了其他的抽象方法,而定义了一个注解:@FunctionalInterface,凡是由该注解标注的接口,统统为函数式接口,强制性的只有一个抽象方法。

为了函数式接口的扩展,JDK对接口规范进行了进一步修改,接口中除了可以定义抽象方法之外,还能够定义静态方法,和默认方法,而且这两种方法可以拥有自己的实现。其中静态方法一般作为工具方法,而默认方法是可以被继承重写的,还能拥有一个默认的实现。除此之外,函数式接口中还可以重写Object中定义的方法。

            // 典型函数式接口
            @FunctionalInterface
            public interface Runnable {
                public abstract void run();
            }
            // 自定义函数式接口
            @FunctionalInterface
            public interface Itest {
                void test();
                boolean equals(Object obj);// 重写Object中的方法
                default void defaultMethod(){
                    // 这是一个默认方法
                }
                static void staticMethod(){
                    // 这是一个静态方法
                }
            }

2.2 预定义的函数式接口

JDK 1.8为我们预定义了许多函数式接口,它们位于java.util.function包中。

序号接口名抽象方法说明备注
1Supplier<T>Tget()无输入参数,通过一系列操作产生一个结果返回无中生有
2IntSupplierintgetAsInt()通过操作返回一个int值无中生有
3LongSupplierlonggetAsLong()通过操作返回一个long值无中生有
4DoubleSupplierdoublegetAsDouble()通过操作返回一个double值无中生有
5BooleanSupplierbooleangetAsBoolean()通过操作返回一个boolean值无中生有
6Consumer<T>voidaccept(Tt)一个输入参数,针对参数做一系列操作,无返回值消费掉了
7IntConsumervoidaccept(intvalue)一个int型输入参数,针对参数做一系列操作,无返回值消费掉了
8LongConsumervoidaccept(longvalue)一个long型输入参数,针对参数做一系列操作,无返回值消费掉了
9DoubleConsumervoidaccept(doublevalue)一个double型输入参数,针对参数做一系列操作,无返回值消费掉了
10BiConsumer<T,U>voidaccept(Tt,Uu)两个输入参数,针对参数做一系列操作,无返回值消费掉了
11ObjIntConsumer<T>voidaccept(Tt,intvalue)两个输入参数,一个自定义类型T,另一个指定位int型,针对参数做一系列操作,无返回值消费掉了
12ObjLongConsumer<T>voidaccept(Tt,longvalue)两个输入参数,一个自定义类型T,另一个指定位long型,针对参数做一系列操作,无返回值消费掉了
13ObjDoubleConsumer<T>voidaccept(Tt,doublevalue)两个输入参数,一个自定义类型T,另一个指定位double型,针对参数做一系列操作,无返回值消费掉了
14Function<T,R>Rapply(Tt)一个参数,一个返回值,针对参数生成一个返回值一因一果
15IntFunction<R>Rapply(intvalue)一个int参数,一个自定义返回值,根据给定的int参数生成一个返回值一因一果
16LongFunction<R>Rapply(longvalue)一个long参数,一个自定义返回值,根据给定的long参数生成一个返回值一因一果
17DoubleFunction<R>Rapply(doublevalue)一个double参数,一个自定义返回值,根据给定的double参数生成一个返回值一因一果
18ToIntFunction<T>intapplyAsInt(Tvalue)一个参数,针对参数生成一个int返回值一因一果
19ToLongFunction<T>longapplyAsLong(Tvalue)一个参数,针对参数生成一个long返回值一因一果
20ToDoubleFunction<T>doubleapplyAsDouble(Tvalue)一个参数,针对参数生成一个double返回值一因一果
21BiFunction<T,U,R>Rapply(Tt,Uu)两个输入参数,一个返回值,根据参数生成一个返回值多因一果
22IntToDoubleFunctiondoubleapplyAsDouble(intvalue)一个int参数,根据参数生成一个double结果返回一因一果
23IntToLongFunctionlongapplyAsLong(intvalue)一个int参数,根据参数生成一个long结果返回一因一果
24LongToDoubleFunctiondoubleapplyAsDouble(longvalue)一个long参数,根据参数生成一个double结果返回一因一果
25LongToIntFunctionintapplyAsInt(longvalue)一个long参数,根据参数生成一个int果返回一因一果
26DoubleToIntFunctionintapplyAsInt(doublevalue)一个double参数,根据参数生成一个int结果返回一因一果
27DoubleToLongFunctionlongapplyAsLong(doublevalue)一个double参数,根据参数生成一个long结果返回一因一果
28ToIntBiFunction<T,U>intapplyAsInt(Tt,Uu)两个输入参数,根据参数生成一个int返回值多因一果
29ToLongBiFunction<T,U>longapplyAsLong(Tt,Uu)两个输入参数,根据参数生成一个long返回值多因一果
30ToDoubleBiFunction<T,U>doubleapplyAsDouble(Tt,Uu)两个输入参数,根据参数生成一个double返回值多因一果
31Predicate<T>booleantest(Tt)一个参数,返回校验boolean结果校验参数
32BiPredicate<T,U>booleantest(Tt,Uu)两个参数,返回校验boolean结果校验参数
33IntPredicatebooleantest(intvalue)一个int参数,返回校验boolean结果校验参数
34LongPredicatebooleantest(longvalue)一个long参数,返回校验boolean结果校验参数
35DoublePredicatebooleantest(doublevalue)一个double参数,返回校验boolean结果校验参数
36UnaryOperator<T>Tapply(Tt)一个T型参数,通过操作返回一个T型结果一元操作
37IntUnaryOperatorintapplyAsInt(intoperand)一个int参数,通过操作返回一个int结果一元操作
38LongUnaryOperatorlongapplyAsLong(longoperand)一个long参数,通过操作返回一个long结果一元操作
39DoubleUnaryOperatordoubleapplyAsDouble(doubleoperand)一个double参数,通过操作返回一个double结果一元操作
40BinaryOperator<T>Tapply(Tt1,Tt2)两个T型参数,通过操作返回一个T型结果二元操作
41IntBinaryOperatorintapplyAsInt(intleft,intright)两个int参数,通过操作返回一个int结果二元操作
42LongBinaryOperatorlongapplyAsLong(longleft,longright)两个long参数,通过操作返回一个long结果二元操作
43DoubleBinaryOperatordoubleapplyAsDouble(doubleleft,doubleright)两个double参数,通过操作返回一个double结果二元操作

三、Lambda表达式

Lambda表达式,简化了匿名内部类的操作方式。

Lamnda表达式可以用在两个地方,一种是集合遍历,另一种就是替换匿名内部类。

前者基于Iterable接口和Map接口中定义的forEach方法,后者则依据函数式接口。

3.1 forEach方法

其实forEach方法是对函数式接口的有效利用,将遍历的书写流程简化,我们不用再写一大堆的for循环框架代码。

            public class LanbdaTest {
                public static void main(String[] args) {
                    List<String> list = Collections.EMPTY_LIST;
                    list.forEach(System.out::println);
                    Map<String,Object> map = Collections.EMPTY_MAP;
                    map.forEach((k,v) -> System.out.println(k + ":"+ v));
                }
            }

forEach方法的参数是Consumer或者BiConsumer,主要用于消费资源,即需要提供参数,但是没有返回值的方法(函数或操作)。

forEach方法最开始是在Iterable接口和Map接口中定义的,这是以默认方法的方式定义的,分别以Consumer和BiConsumer作为入参。

Iterable中的forEach方法:

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

Iterable的实现类均可以通过重写该方法来自定义遍历的方式。

比如以数组为底层结构的ArrayList、CopyOnWriteArrayList、CopyOnWriteArraySet等都是以普通for循环来实现的遍历。而以链表为底层结构的LinkedList则没有重写forEach方法,采用默认方法中简化的for循环,编译器会对其进行处理,将其采用Iterator进行遍历。

Map中的forEach方法:

            public interface Map<K,V> {
                default void forEach(BiConsumer<? super K, ? super V> action) {
                    Objects.requireNonNull(action);
                    for (Map.Entry<K, V> entry : entrySet()) {
                        K k;
                        V v;
                        try {
                            k = entry.getKey();
                            v = entry.getValue();
                        } catch(IllegalStateException ise) {
                            // this usually means the entry is no longer in the map.
                            throw new ConcurrentModificationException(ise);
                        }
                        action.accept(k, v);
                    }
                } 
            }

在常用的HashMap和TreeMap中都对该方法进行了重写,HashMap采用数组+链表(红黑树)的方式实现,但是遍历的时候采用的就是数组+链表双重遍历,因为在HashMap中的红黑树同时还是一个双向链表。而TreeMap中则是使用树结构的中序遍历方式实现的。

3.2 替换匿名内部类

Lambda替换匿名内部类有一个前提,那就是这个匿名内部类的接口类型必须为函数式接口,如果不是函数式接口,是无法使用Lambda替换的。

常用的函数式接口为Runnable,使用匿名内部类方式如下:

            public class LambdaTest {
                public static void main(String[] args) {
                    Thread t = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("采用匿名内部类");
                        }
                    });
                }
            }

使用Lambda替换如下:

            public class LambdaTest {
                public static void main(String[] args) {
                    Thread t1 = new Thread(()->System.out.println("采用Lambda方式"));
                }
            }

Lambda表达式最大的作用其实就是替换匿名内部类,简化这种写法。

四、方法引用

方法引用出现的目的是为了解决所需的操作已经存在的情况。

当我们需要传递的操作已经存在,那就不必再费尽心思的再写一个出来啦,直接使用方法引用来将已有的方法给它就行了。

方法引用使用“::”双英文冒号组成的操作符来指定方法。

使用方法引用之后,你会很不适应,因为参数哪去啦???

是的,参数不再是显式传递,采用方法引用之后,参数会自动传递,我们举个例子看看简单的原理:

            public class LanbdaTest {
                public static String getName(Supplier<String> supplier){
                    return supplier.get();
                }
                public static void main(String[] args) {
                    Person person = new Person("huahua");
                    System.out.println(getName(person::getName));
                }
            }
            class Person{
                private String name;
                public Person(String name){
                    this.name = name;
                }
                public String getName() {
                    return name;
                }
                public void setName(String name) {
                    this.name = name;
                }
            }

执行结果为:

            huahua

解析:
首先我们使用了方法引用:person::getName,Person类中有已定义好的获取name的方法,这里就可以直接引用该方法。Supplier是供应者,可以无中生有,也就是不需要参数,产生一个返回值。

Person中的getName方法,明显就符合Supplier的格式,没有参数,但是返回了一个结果,所以这里就可以直接传递person::getName。

方法引用的种类:

  • 类的构造器引用:ArrayList::new、String[]::new
  • 类的静态方法引用:String::valueOf、Integer::valueOf
  • 类的实例方法引用:String::length、Person::getName
  • 对象的实例方法引用:sring::length、person::getName

方法引用于Lambda可以算是平等,并列的关系,Lambda用于自定义操作,方法引用用于引用已存在的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值