这四种内置的函数式接口提供了四种不同类型的代码写法,以 Consumer 为例。 Consumer 是一种消费型接口,提供了一个参数、无返回值的接口方法,就和消 费一样,花出去就没有了。
import java.util.function.Consumer;
public class ConsumerTest {
public static void main(String[] args) { consume(100,money -> { System.out.println("消费了"+money+"元"); }); }
public static void consume(double money, Consumer consumer){ consumer.accept(money); } }
在上面的例子中,consume 方法有两个入参,一个 double 类型的金额和一 个 Consumer 函数接口,方法的实现就是执行 Consumer 函数接口默认的 accept 方法。在使用 consume 方法中,就可以直接通过 Lambda 表达式来实现 函数式接口的调用,让代码更加简洁。
1.5 方法引用与构造器引用
方法引用使得开发者可以直接引用现存的方法、Java 类的构造方法或者实例对 象。构造器引用和方法引用类似,主要用途往往是创建对象。方法引用和构造器 引用主要是配合 Lambda 表达式,使得表达式变得更加简单。 比如要输出一个 List 中的数据,使用方法引用的话通 过 System.out::println 就实现了。
import java.util.Arrays;import java.util.List;
public class Test2 {
public static void main(String[] args) { List list = Arrays.asList("a","b"); list.forEach(System.out::println); } }
1.6 stream 表达式
JDK8 中两个最重大的改变,一个是 Lambda 表达式,另外一个就是 Stream API。Stream 是 JDK8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的 操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。 也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种 高效且易于使用的处理数据的方式。 比如我要获取 List 中大于 50 的数据,就可以这样写
import java.util.Arrays;import java.util.List;import java.util.stream.Co llectors;public class StreamTest {
public static void main(String[] args) { List list = Arrays.asList(1,2,3,51,52,53); List filterList = list.stream().filter(x -> x>50).collec t(Collectors.toList()); fliterList.forEach(System.out::println); } }
上面这段代码将大于 50 的数据传到了 filterList 中并输出。大家可以打开右 边的实验环境验证最终的输出结果。
1.7 接口中的默认方法和静态方法
在 JDK8 之前,接口中的方法只能声明无法实现,在 JDK8 中,接口中可以添加 默认方法和静态方法了。 首先介绍默认方法,通过 default 修饰的方法可以在接口中增加实现。
public interface MyInterface {
default void defaultMethod(){ System.out.println("hello"); } }
另外是接口中的静态方法:
public interface MyInterface {
public static void staticMethod(){ System.out.println("hello"); } }
更多的介绍会在后面的实验中给出。
1.8 Optional
Optional 类是 JDK8 的新特性,是一个可以为 null 的容器对象,Optional 往 往被用来做空值的判断。 集合类型的判空在某些场景下会十分啰嗦,比如当你接收到的 Map 是这样的
{"user":{"info":{"address":"hz"}}}
这种时候如果按照常规的写法,需要写多层 if 语句进行判空
if (map.get("user")!=null){ Map user = (Map) map.get("user");
if (user.get("info")!=null){ Map info = (Map) user.get("info");
if (info.get("address")!=null){ String address = (String) info.get("address"); System.out.println(address); } } }
if 里面套着 if,结构十分复杂,这个时候我们就可以使用 Optional
String address=Optional.ofNullable(map) .map(m->(Map)m.get("user")) .map(user->(Map)user.get("info")) .map(info->(String)info.get("address")) .orElse(null);
一下子就变得简洁明了。
1.9 新的时间日期 API
时间类一直是代码开发中经常用到的东西,时间类更新到 JDK8 版本期间,一共 迭代了三次。分别是 Date 类、Calendar 类和 LocalDateTime 类。 这一次推出来的 LocalDateTime 类,终于让 Java 中的时间类变得很好用了
import java.time.Instant;import java.time.LocalDate;import java.time.Loc alDateTime;import java.time.LocalTime;
public class DateTest {
public static void main(String[] args) { Instant now = Instant.now();
//获取纳秒级别的时间戳
System.out.println(now.toEpochMilli());LocalDate localDate=LocalDate.now(); LocalTime localTime=LocalTime.now(); LocalDateTime ldt = LocalDateTime.now(); System.out.println(localDate); System.out.println(localTime); System.out.println(ldt); } }
大家能猜出上面的这段代码会返回怎样的数据吗?
1.10 实验总结
本次实验对 JDK8 中的一些重要特性做了简单的介绍,可以看到这些新特性让 Java 变得更加有趣了。在接下来的实验中,针对上述的内容会有更详细的介绍。
2.学透 Lambda 表达式
2.1 实验介绍
Java8 是一个跨时代的更新,在这个版本中,出现了很多新特性,其中 Lambda 表 达式就是 Java8 中十分重要的新特性。本次实验将基于 Java8 详细介绍 Lambda 表达式的使用,简化代码的编写。
知识点
-
匿名类
-
函数式接口
-
Lambda 表达式介绍
4.Lambda 表达式的变量作用域
5.Lambda 表达式在实际中的应用
2.1 匿名类
在正式介绍 Lambda 表达式之前,首先需要知道的一个知识点是匿名类。 因为 Java 中的 Lambda 表达式最重要的用法就是简化匿名类的使用。匿名类 可以同时声明和实例化一个类,匿名类和本地类十分相似,只是匿名类没有名称。 比如下面有一个叫 Person 的接口,里面有一个 sayHello 方法
public interface Person {
void sayHello(); }
接着创建一个类 Son 实现 Person 接口,实现里面的 sayHello 方法。 在不用匿名类的情况下,就需要新建一个类实现 Person,然后重写 sayHello 方 法,就像下面这样
public class Son implements Person{
@Override
public void sayHello() { System.out.println("son say hello"); } }
如果这个 Son 类只是用来在某个类中执行特定任务的话,就可以使用更加简洁 的匿名内部类写法,而无需创建这个 Son 类
public class AnonymousDemo {
public static void main(String[] args) { Person person = new Person(){
@Override
public void sayHello() { System.out.println("anonymous say hello"); } }; person.sayHello(); } }
在上面这个例子中,虽然没有创建实体的类,但是通过一个匿名类的方式实现了 本地类的效果。总结下来,匿名类是不能有名字的类,它们不能被引用,只能在 创建时用 new 语句来声明它们。
2.2 函数式接口
在正式学习 Lambda 表达式之前,还有一个很重要的概念需要了解,那就 是函数式接口。函数式接口主要就是给 Lambda 表达式使用的。 函数式接口可以使用 @FunctionalInterface 来修饰,如果一个接口是函数式接 口,那么这个接口就可以使用 Lambda 表达式来写。 函数式接口有下面几个特征:
. 只能有一个抽象方法。
. 允许定义默认方法
. 允许定义静态方法
. 允许定义 java.lang.Object 里的 public 方法
拿 java.util 包下的 Comparator 接口举个例子,下面是这个接口的一部分代码
@FunctionalInterfacepublic interface Comparator {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator reversed() {
return Collections.reverseOrder(this); }
public static super T>> Comparator reverse Order() {
return Collections.reverseOrder(); } }
从代码中可以看到,Comparator 完美符合了函数式接口的特性,只能有一个抽 象方法 compare,有默认方法,有静态方法,有 java.lang.Object 里的 public 方 法。
2.3 Lambda 表达式
匿名类的一个问题是,如果匿名类的实现非常简单,例如只包含一个方法的接口, 那么匿名类的语法可能看起来笨拙且不清楚。因此 Lambda 表达式出现了,
Lambda 允许通过表达式来代替功能接口,使用 Lambda 可以让代码变得更加 简洁。
Lambda 只能在函数式接口中使用,上面这段使用匿名类的方法,换成 Lambda
表达式之后就变成了下面这样:
public class AnonymousDemo {
public static void main(String[] args) { Person person = () -> System.out.println("anonymous say hello"); person.sayHello(); } }
一下子变得更加简单了。
Lambda 表达式的语法格式如下所示:
(parameters) -> expression
或
(parameters) ->{ statements; }
lambda 表达式的写法主要有下面几种
无参数,无返回值
开头的例子中就是一个无参数和无返回值的写法,在这个例子中,接口中定 义的抽象方法没有入参也没有返回值:
public class AnonymousDemo {
public static void main(String[] args) { Person person = () -> System.out.println("anonymous say hello"); person.sayHello(); }}
有参数,无返回值
先写一个这样的接口:
public interface Student {
void getAge(int age); }
在以前的代码中,需要首先写一个类去继承这个接口,或者是写一个匿名内部类, 如:
Student student=new Student() {
@Override
public void getAge(int age) { System.out.println(age); } };
使用 Lambda 就变得简单了
Student student2=(age) -> System.out.println(age);
如果只有一个参数,小括号可以不写
Student student3=age -> System.out.println(age);
如果想要在 Lambda 表达式中写一些复杂的方法,在语法上可以在大括号内部 写实现方法。
public class Main {
public static void main(String[] args) { Student student4 = age -> {
// do something
System.out.println(age); }; student4.getAge(10); } }
上面的这段代码最终会输出 10。
有参数,有返回值
首先写一个有参数,有返回值的接口方法
public interface Student {
int getAge(int age); }使用 Lambda 表达式时代码如下
public class Main {
public static void main(String[] args) { Student student = age -> {
return age * 5;};
int age = student.getAge(10); System.out.println(age); } }
在上面的代码中,由于 Student 的 getAge 方法是有返回值的,因此在写带有 大括号的实现方法时也必须带上 return。 如果实现方法用单行代码就能写完,可以将大括号和 return 进行简化,代码就 变成了下面这样:
public class Main {
public static void main(String[] args) { Student student = age -> age * 5;
int age = student.getAge(10); System.out.println(age); } }
2.4 Lambda 表达式的变量作用域
Lambda 内部如果使用了外部定义的局部变量,那这个变量是一定不能被修改的, 看下面这段代码
public class AnonymousDemo {
public static void main(String[] args) {
int num = 1; Person person = ()->{
int a = num; }; num++; } }
在 Lambda 表达式中使用了 num,又在外部对 num 进行了修改,就会出现下 面这段报错
Variable used in lambda expression should be final or effectively final
不过如果这个变量是对象变量或者静态变量,那么就没有不能修改的限制了。
public class AnonymousDemo {
static int num = 1;
public static void main(String[] args) { Person person = ()->{
int a = num; }; num++; }}