人生倒计时
- 今日已经过去小时
- 这周已经过去天
- 本月已经过去天
- 今年已经过去个月
易哥,毕业于浙江大学。高级软件架构师、网络工程师、数据库工程师、注册电气工程师。目前从事软件架构架构设计工作。
1 概述
Java 8 于 2014 年 3 月 18 日由公司发布,从那时起已经过去了几年。然而,直到今天仍有很多软件开发者对其相关特性一无所知,这可能是Java基础教材更新缓慢的主要原因。为了让大家对Java8的特性有一个全面、系统的了解,本公众号将分几篇连续介绍Java8的特性。
Java 8 的主要新特性是:
本文描述了其中的表达式。其他功能将在后续文章中介绍。
2个表达式
我经常听到的一个概念:闭包。闭包是可以读取其他函数内部变量的函数。比如在 中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解为“函数内部定义的函数”。本质上,闭包是函数内部和函数外部之间的桥梁。
实际上,它是一个表达式,允许将函数用作方法的参数(函数作为参数传递给方法)。
2.1 发生原因
面向对象编程应该是纯面向对象的java lambda 方法引用,所以你经常会看到这样的写法:如果你要写一个方法,你必须把它放在一个类中,然后new出来对象,对象调用方法。最大的问题是它的冗余语法。有人开玩笑说匿名类型会导致“高问题”( ):匿名内部类中的大多数多行代码只有一行完成实际工作。因此,JAVA8提供了这种“函数式编程”方法——表达式,让我们更简洁地实现内部匿名类的功能。
也就是说,你真的只需要一个函数来做一件事,甚至它叫什么都没关系。表达式可用于执行此操作。
2.2 语法介绍
2.2.1个功能接口
功能接口 ( ):定义的接口。接口中必须只有一个抽象方法(可以有默认方法和静态方法)。这样的接口变成了功能接口。在可以使用表达式的地方,方法声明必须包含功能接口。
如果我们提供的接口包含多个,那么使用表达式会报错。因为这不是一个功能接口。
例如:
@FunctionalInterface interface MathOperation { int operation(int a, int b); }
Java 8 为函数式接口引入了一个新的注解@,主要用于编译级别的错误检查。有了这个注解,当你写的接口不符合函数式接口定义时,编译器会报错。如果您不添加它,则不会进行任何检查。
当心:
任何功能接口都可以使用表达式替换。比如系统已经有:,,。JDK 8 之前存在的功能接口:
java.lang.Runnable java.util.concurrent.Callable java.security.PrivilegedAction java.util.Comparator java.io.FileFilter java.nio.file.PathMatcher java.lang.reflect.InvocationHandler java.beans.PropertyChangeListener java.awt.event.ActionListener javax.swing.event.ChangeListener
Java SE 8 中新增了一个包:java.util.,其中包含常用的功能接口,例如:
Predicate——接收T对象并返回boolean Consumer ——接收T对象,不返回值 Function ——接收T对象,返回R对象 Supplier ——提供T对象(例如工厂),不接收值 UnaryOperator ——接收T对象,返回T对象 BinaryOperator ——接收两个T对象,返回T对象
另外,还有很多很多的接口,具体可以去java.util。包看看。
那么参数在哪里这些接口,我们可以直接使用表达式!
2.2.2 个表达式
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; }
简单的表达式示例:
// 1. 不需要参数,返回值为 5 () -> 5 // 2. 接收一个参数(数字类型),返回其2倍的值 x -> 2 * x // 3. 接受2个参数(数字),并返回他们的差值 (x, y) -> x – y // 4. 接收2个int型整数,返回他们的和 (int x, int y) -> x + y // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void) (String s) -> System.out.print(s)
2.3 特点及注意事项
2.3.1 表达式中 this 的概念
在 中,this 并不指向表达式生成的 SAM 对象java lambda 方法引用,而是声明它的外部对象。
没有例子,这是真的。
2.3.2 类型推导
首先是一个小例子:
public class TestBean { public static void main(String[] args) { MathOperation addition = (a, b) -> a + b; Integer ans = addition.operation(7,9); System.out.println(ans.toString()); } interface MathOperation { int operation(int a, int b); } }
编译器负责推断表达式的类型。它是使用表达式上下文中预期的类型推导出来的,这种预期的类型称为目标类型。也就是说我们传入的参数不需要写类型!
因此,在定义函数时:
我们没有在输入参数中指定输入参数a和b的类型,因为如果没有类型推导,我们要写:
(int a, int b) -> a + b;
2.3.3 函数嵌套
我们知道系统为我们创建了几个功能接口,比如:
Function——接收T对象,返回R对象
其代码如下:
@FunctionalInterface public interface Function{ R apply(T t); default Function compose(Function super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default Function andThen(Function super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } static Function identity() { return t -> t; } }
它有两个内置方法,编写了默认实现,基于哪些函数可以嵌套。
public class TestBean { public static void main(String[] args) { Functionfun1 = input -> input + ">"; Function fun2 = input -> input + "+"; System.out.println(fun1.apply("a")); System.out.println(fun2.apply("a")); System.out.println(fun1.compose(fun2).apply("b")); System.out.println(fun1.andThen(fun2).apply("c")); } } --- a> a+ b+> c>+
首先,这是一个函数式接口,只有一个真正的抽象函数。
下面我们来看看为什么()和()的两个默认实现可以实现级联操作:首先,我们定义两个,fun1和fun2。然后,对于 fun1.apply("a") 操作,我们只是将 "a" 作为参数传递给函数 fun1,然后 fun1 执行它,因此输出 "a>"。
我们重点分析的是pose(fun2).apply("b")。分析函数,=fun2。因此,(V v) -> apply(.apply(v)) 最终变成: (V v ) -> apply(fun2.apply(v)),即:fun1.apply(fun2.apply(v)),所以v首先经过fun2处理得到" b+";然后将"b+"传递给fun1得到"b+>"。这样就实现了两个函数的嵌套。
同样,fun1.(fun2).apply("c"), after=fun2, so (T t) -> after.apply(apply(t)); 是:fun2.apply(apply(t)).apply(t)是给fun1的,所以fun1处理完后,再由fun2处理。
但是,这两种方法是系统内置的,您可以使用它们。
3 总结
Java8提供的表达式使Java程序更加方便和灵活。但是在使用表达式的时候,也要注意相关的指向问题,以免迷路。
Java 8 中的表达式介绍到此为止。我们将在后续文章中介绍Java 8 中的其他特性。
易哥,毕业于浙江大学。高级软件架构师、网络工程师、数据库工程师、注册电气工程师。目前从事软件架构架构设计工作。