本系列引入Java 8特性和示例。如lambda表达式、Java流、功能接口和日期时间API更改。
Lambda Expression
Lambda表达式,如果你熟悉其他流行编程语言(比如Scala,C#),对Lambda就不感到陌生。在Java编程语言中,Lambda表达式(或函数)只是一个匿名函数。一个没有名称且未绑定到标识符的函数。它们被精确地写在需要的地方,通常作为其他函数的参数。Lambda表达式最重要的特性是它们在其外观的上下文中执行。因此,相同的lambda表达式可以在其他上下文中以不同的方式执行(例如,逻辑是相同的,但结果会根据传递给函数的不同参数而不同)。
函数式编程相对于面向对象编程(OOP)的好处,大多数OOP语言都是围绕对象和实例发展的,并且只把它们当作自己的第一类公民。另一个重要的实体即功能退居其次。尤其是在java中,函数不能存在于对象之外。在java中,函数本身没有任何意义,除非它与某个对象或实例相关。但是在函数式编程中,您可以定义函数,为它们提供引用变量并将其作为方法参数传递等等。 JavaScript是一个很好的例子,您可以在其中传递回调方法,例如 到Ajax调用。 这是非常有用的功能,并且从一开始就在Java中缺少。 现在使用Java 8,我们也可以使用这些lambda表达式。
Lambda表达式提供的功能:
- 允许将功能视为方法参数,或将代码视为数据。
- 可以不属于任何类而创建的函数。
- lambda表达式可以像对象一样传递并按需执行。
lambda表达式的基本语法为:
lambda operator -> body

- 无参数
() -> System.out.println("无参数");
- 一个参数
(p) -> System.out.println(“参数: " + p);
如果可以从上下文推断出该变量的类型,则不必使用括号
- 多个参数
(p1, p2) -> System.out.println(“多个参数: " + p1 + ", " + p2);
Lambda Expression的协作规则
- lambda表达式可以有零个、一个或多个参数。
- 可以显式声明参数的类型,也可以从上下文推断参数的类型。
- 多个参数用强制括号括起来,并用逗号分隔。空括号用于表示一组空参数。
- 当有单个参数时,如果它的类型是推断出来的,则不强制使用括号。例如a ->返回a*a。
- Lambda表达式的主体可以包含零个,一个或多个语句。
- 如果lambda表达式的主体具有单语句,则不强制使用大括号,且匿名函数的返回类型与主体表达式相同。当主体中有多个语句时,必须用大括号括起来。
以上是Lambda Expression的简单概述,下面会深入介绍Lambda Expression 但在这之前先了解下 functional interfaces。因为这是重要的。
Java 8 functional interface
Lambda表达式就像函数一样,它们也像函数一样接受参数。Lambda表达式基本上表示函数接口的实例
单一抽象方法接口(SAM接口)不是一个新概念。 在Java中,我们已经有许多此类SAM接口的示例。 功能接口是仅包含一个抽象方法的接口。 他们只能展示一种功能。 从Java 8开始,lambda表达式可用于表示功能接口的实例。 功能接口可以具有许多默认方法,通过使用新的注释(即@FunctionalInterface)标记这些接口来实施单一职责规则。 Runnable,ActionListener和Comparable是功能接口的一些示例。在Java 8之前,我们必须创建匿名内部类对象或实现这些接口。
例如,Runnable接口的新定义
@FunctionalInterfacepublic interface Runnable { public abstract void run();}
如果想在任何函数接口中添加新方法,编译器将不允许您这样做,并将抛出编译时错误。怎么和Lambda结合呢?我们知道Lambda表达式是不带名称的匿名函数,它们(大多数)作为参数传递给其他函数。在java中,方法参数始终具有类型,并且在确定方法重载甚至是简单方法调用的情况下,都会寻找此类型信息以确定需要调用哪个方法。因此,基本上每个lambda表达式也必须都可以转换为某种类型才能被接受为方法参数。 转换lambda表达式的类型始终是函数接口类型。让我们通过一个例子来理解它。
列如我们创建个线程,那么最简单的代码将是:
public class FgMainTest { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("创建一个线程"); } }).start(); }}如果我们使用lambda表达式执行此任务,则代码为:
public class FgMainLambdaTest { public static void main(String[] args) { new Thread(()->{ System.out.println("lambda: 创建一个线程"); }).start(); }}Runnable是一个具有单个方法run()的函数接口。因此,当您将lambda表达式传递给Thread类的构造函数时,编译器将尝试将该表达式转换为等效的可运行代码,如第一个代码示例所示。如果编译器成功,那么一切运行良好,如果编译器不能将表达式转换为等效的实现代码,它将会报错。在上面的例子中,lambda表达式被转换为Runnable类型。
附上Thread构造函数:
public Thread(Runnable target) { this(null, target, "Thread-" + nextThreadNum(), 0);}lambda 表达式例子
我列出了一些代码例子,你可以阅读和分析一个lambda表达式如何在日常编程中使用。
遍历一个列表并执行一些操作
import java.util.ArrayList;import java.util.List;public class FgMainLambdaTest_example_1 { public static void main(String[] args) { // 创建List集合 // {1, 2, 3, 4} List<Integer> arrL = new ArrayList<Integer>(); arrL.add(1); arrL.add(2); arrL.add(3); arrL.add(4); // 用lambda expression 打印集合arrL中所有元素 arrL.forEach(n -> System.out.println(n)); arrL.forEach(n -> { if (n % 2 == 0) System.out.println(n); }); }}排序
import java.util.Arrays;import java.util.List;public class FgMainLambdaTest_example_2 { public static void main(String[] ar) { List<Department> departmentList = Arrays.asList( new Department("研发部"), new Department("测试部"), new Department("运营部"), new Department("框架部") ); System.out.println("排序前: " + Arrays.toString(departmentList.toArray())); departmentList.sort(Department::nameCompare); System.out.println("排序后 " + Arrays.toString(departmentList.toArray()));/* 输出结果: 排序前: [研发部, 测试部, 运营部, 框架部] 排序后 [框架部, 测试部, 研发部, 运营部] */ }}class Department { String name; Department(String name) { this.name = name; } public static int nameCompare(Department a1, Department a2) { /** * java string compareTo()方法按字母顺序比较给定字符串和当前字符串。它返回正数,负数或0。 * 它根据字符串中每个字符的Unicode值来比较字符串。 * 如果第一个字符串在词法上大于第二个字符串,则返回正数(字符值之差)。 * 如果第一个字符串在词法上小于第二个字符串,则返回负数;如果第一个字符串在词法上等于第二个字符串,则返回0。 */ return a1.name.compareTo(a2.name); } public String toString() { return name; }}