提及java8的新特性,不少人第一反应都是lambada表达式和流式的API,那么到底什么是lambada表达式,为何要引入lambada表达式,以及引入lambada表达式为
java8带来了哪些改变呢,本文接来下会一一讨论。
直白的先让你们有个第一印象,在java8以前,在建立一个线程的时候,咱们可能这么写:css
Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); } };
这段代码使用了匿名类,Runnable 是一个接口,这里new 了一个类实现了 Runnable 接口,而后重写了 run方法,run方法没有参数,方法体也只有一行打印语句。
这段代码咱们其实只关心中间打印的语句,其余都是多余的。
java8后,咱们采用lambada表达式后,咱们就能够简写为:html
Runnable r = () -> System.out.println("Hello");
Lambda 表达式是一种匿名函数(对 Java 而言这并不彻底正确,但如今姑且这么认为),简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。java
你能够将其想作一种速记,在你须要使用某个方法的地方写上它。当某个方法只使用一次,并且定义很简短,使用这种速记替代之尤为有效,这样,你就没必要在类中费力写声明与方法了。正则表达式
lambda 表达式的语法格式以下:算法
(parameters) -> expression
或
(parameters) ->{ statements; }
如下是lambada表达式的一些例子:sql
(int a, int b) -> { return a + b; } () -> System.out.println("Hello World"); (String s) -> { System.out.println(s); } () -> 42 () -> { return 3.1415 };
Java 是一流的面向对象语言,除了部分简单数据类型,Java 中的一切都是对象,即便数组也是一种对象,每一个类建立的实例也是对象。
在 Java 中定义的函数或方法不可能彻底独立,也不能将方法做为参数或返回一个方法给实例。express
在Java的面向对象的世界里面,“抽象”是对数据的抽象,而“函数式编程”是对行为进行抽象,在现实世界中,数据和行为并存,程序也是如此。
因此java8中lambada表达式的出现也就弥补java在对行为进行抽象方面的缺失。编程
函数式接口(Functional Interface)是Java 8对一类特殊类型的接口的称呼。 这类接口只定义了惟一的抽象方法的接口(除了隐含的Object对象的公共方法),
所以最开始也就作SAM类型的接口(Single Abstract Method)。数组
首次看到这个概念的时候,有些迷茫。由于接口中的方法都是public abstract 的(即使省略掉这两个关键字也是ok的,接口中每个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其余修饰符都会报错)),那么上面的定义就变成了:只有一个方法声明的接口就是函数式接口。
可是实际上在代码中看到的函数式接口有包含一个方法的,也有包含多个方法的,这就让我迷茫了。
例以下面的两个函数式接口:Runnable 和 Consummer:多线程
@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 public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t); /** * Returns a composed {@code Consumer} that performs, in sequence, this * operation followed by the {@code after} operation. If performing either * operation throws an exception, it is relayed to the caller of the * composed operation. If performing this operation throws an exception, * the {@code after} operation will not be performed. * * @param after the operation to perform after this operation * @return a composed {@code Consumer} that performs in sequence this * operation followed by the {@code after} operation * @throws NullPointerException if {@code after} is null */ default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
最后才了解了缘由在于:函数式接口中除了那个抽象方法外还能够包含静态方法和默认方法。
一个或者多个静态方法不会影响SAM接口成为函数式接口。
由于默认方法不是抽象方法,因此不影响咱们判断一个接口是不是函数式接口。
参考连接: Java 8函数式接口functional interface的秘密
为何会单单从接口中定义出此类接口呢?
缘由是在Java Lambda的实现中, 开发组不想再为Lambda表达式单独定义一种特殊的Structural函数类型,
称之为箭头类型(arrow type), 依然想采用Java既有的类型系统(class, interface, method等), 缘由是增长一个结构化的函数类型会增长函数类型的复杂性,
破坏既有的Java类型,并对成千上万的Java类库形成严重的影响。 权衡利弊, 所以最终仍是利用SAM 接口做为 Lambda表达式的目标类型。
函数式接口表明的一种契约, 一种对某个特定函数类型的契约。 在它出现的地方,实际指望一个符合契约要求的函数。
Lambda表达式不能脱离上下文而存在,它必需要有一个明确的目标类型,而这个目标类型就是某个函数式接口。
换句话说:什么地方能够用lambada表达式呢? 全部须要FI (Functional Interface)实例的地方,均可以使用lambada表达式。
Java 不会强制要求你使用@FunctionalInterface注解来标记你的接口是函数式接口, 然而,做为API做者,
你可能倾向使用@FunctionalInterface指明特定的接口为函数式接口, 这只是一个设计上的考虑, 可让用户很明显的知道一个接口是函数式接口。
提及函数式接口的原由就不得不提lambada表达式,提及lambada表达式的原由就不得不说函数式编程,函数式编程相比命令式编程有诸多的优势:(最突出的优势有2点:
引用透明-->函数的运行不依赖于外部的状态;没有反作用-->函数的运行不改变外部的状态),java8为了使用函数式编程的优势,从而就使用了lambada表达式,从而
就定义了一种规范和约束,这个规范和约束就是函数式接口。
关于函数式编程的一些基础概念会在下面将。(注意:函数式编程和函数式接口是不一样的概念。函数式编程是一种编程范式,与之在同一个维度的有:命令式编程、逻辑式编程)
这里就列举这几个,还有其余的暂时就不列举了。
接口 | 参数 | 返回类型 | 描述 |
---|---|---|---|
Predicate<T> | T | boolean | 用于判别一个对象。好比求一我的是否为男性 |
Consumer<T> | T | void | 用于接收一个对象进行处理但没有返回,好比接收一我的并打印他的名字 |
Function<T, R> | T | R | 转换一个对象为不一样类型的对象 |
Supplier<T> | None | T | 提供一个对象 |
UnaryOperator<T> | T | T | 接收对象并返回同类型的对象 |
BinaryOperator<T> | (T, T) | T | 接收两个同类型的对象,并返回一个原类型对象 |
- 其中 Cosumer 与 Supplier 对应,一个是消费者,一个是提供者。
- Predicate 用于判断对象是否符合某个条件,常常被用来过滤对象。
- Function 是将一个对象转换为另外一个对象,好比说要装箱或者拆箱某个对象。
- UnaryOperator 接收和返回同类型对象,通常用于对对象修改属性。BinaryOperator 则能够理解为合并对象。
若是之前接触过一些其余 Java 框架,好比 Google Guava,可能已经使用过这些接口,对这些东西并不陌生。
关于这个问题也有一些争议,有人把函数式归结为声明式的子集,还有一些别的七七八八的东西,这里就不作阐述了。
声明式编程:专一于”作什么”而不是”如何去作”。在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。
如, css, 正则表达式,sql 语句,html,xml…
相比于命令式编程关心解决问题的步骤,函数式编程是面向数学的抽象,关心数据(代数结构)之间的映射关系。函数式编程将计算描述为一种表达式求值。
在狭义上,函数式编程意味着没有可变变量,赋值,循环和其余的命令式控制结构。即,纯函数式编程语言。
在广义上,函数式编程意味着专一于函数
函数式编程中的函数,这个术语不是指命令式编程中的函数,而是指数学中的函数,即自变量的映射(一种东西和另外一种东西之间的对应关系)。
也就是说,一个函数的值仅决定于函数参数的值,不依赖其余状态。
在函数式语言中,函数被称为一等函数(First-class function),与其余数据类型同样,做为一等公民,处于平等地位,能够在任何地方定义,在函数内或函数外;
能够赋值给其余变量;能够做为参数,传入另外一个函数,或者做为别的函数的返回值。
纯函数是这样一种函数,即相同的输入,永远会获得相同的输出,并且没有任何可观察的反作用。
函数式的最主要的好处主要是不变性带来的:
引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或”状态”,只依赖于输入的参数,任什么时候候只要参数相同,
引用函数所获得的返回值老是相同的。
其余类型的语言,函数的返回值每每与系统状态有关,不一样的状态之下,返回值是不同的。这就叫”引用不透明”,很不利于观察和理解程序的行为。
没有可变的状态,函数就是引用透明(Referential transparency)
反作用(side effect),指的是函数内部与外部互动(最典型的状况,就是修改全局变量的值),产生运算之外的其余结果。
函数式编程强调没有”反作用”,意味着函数要保持独立,全部功能就是返回一个新的值,没有其余行为,尤为是不得修改外部变量的值。
函数即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置,这样写的代码容易进行推理,不容易出错。这使得单元测试和调试都更容易。
还有一个好处是,因为函数式语言是面向数学的抽象,更接近人的语言,而不是机器语言,代码会比较简洁,也更容易被理解。
没有反作用使得函数式编程各个独立的部分的执行顺序能够随意打乱,(多个线程之间)不共享状态,不会形成资源争用(Race condition),
也就不须要用锁来保护可变状态,也就不会出现死锁,这样能够更好地进行无锁(lock-free)的并发操做。
尤为是在对称多处理器(SMP)架构下可以更好地利用多个处理器(核)提供的并行处理能力。
惰性求值(lazy evaluation,也称做call-by-need)是这样一种技术:是在将表达式赋值给变量(或称做绑定)时并不计算表达式的值,
而在变量第一次被使用时才进行计算。
这样就能够经过避免没必要要的求值提高性能。
总而言之,函数式编程因为其不依赖、不改变外部状态的基本特性,衍生出了不少其余的有点,尤为简化了多线程的复杂性,提高了高并发编程的可靠性。
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处连接和本声明。posted from openWrite