Java8新特性(一)——Lambda表达式与函数式接口

时间:2022-09-22 09:20:35

一、Java8新特性概述

  1.Lambda 表达式

  2. 函数式接口

  3. 方法引用与构造器引用

  4. Stream API

  5. 接口中的默认方法与静态方法

  6. 新时间日期 API

  7. 其他新特性

  // 其他例如HashMap在JDK8中的提升,将会在HashMap的章节进行拓展

 二、Lambda表达式

  1.为什么使用Lambda

  Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码 像数据一样进行传递)。

  可以写出更简洁、更 灵活的代码。

  2.从匿名内部类到λ表达式

  回顾Java基础中匿名内部类的写法:

  // 原来的匿名内部类
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Runnable!");
}
};

  实际上,上述代码中核心的只有输出语句那一句,首先这个匿名内部类没有名字,其次我们实现Runnable接口时其实就是去重写run()方法这一个方法

所以,如果Java可以自动地帮我们感应出除核心代码之外的东西,那么代码便可以大大简化简洁!

  改造为Lambda表达式:

     // JDK8的Lambda表达式
Runnable r8 = () -> System.out.println("Runnable!");

  // 这里赞一下IDEA,像λ这样的新特性,他也是可以自动感应进行标记的

   关于Lambda表达式更多更实用的案例,可以参见如下代码:

package com.atguigu.java8;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet; import org.junit.Test; public class TestLambda1 { //原来的匿名内部类
@Test
public void test1(){
Comparator<String> com = new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
}; TreeSet<String> ts = new TreeSet<>(com); TreeSet<String> ts2 = new TreeSet<>(new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
} });
} //现在的 Lambda 表达式
@Test
public void test2(){
Comparator<String> com = (x, y) -> Integer.compare(x.length(), y.length());
TreeSet<String> ts = new TreeSet<>(com);
} List<Employee> emps = Arrays.asList(
new Employee(101, "张三", 18, 9999.99),
new Employee(102, "李四", 59, 6666.66),
new Employee(103, "王五", 28, 3333.33),
new Employee(104, "赵六", 8, 7777.77),
new Employee(105, "田七", 38, 5555.55)
); //需求:获取公司中年龄小于 35 的员工信息
public List<Employee> filterEmployeeAge(List<Employee> emps){
List<Employee> list = new ArrayList<>(); for (Employee emp : emps) {
if(emp.getAge() <= 35){
list.add(emp);
}
} return list;
} @Test
public void test3(){
List<Employee> list = filterEmployeeAge(emps); for (Employee employee : list) {
System.out.println(employee);
}
} //需求:获取公司中工资大于 5000 的员工信息
public List<Employee> filterEmployeeSalary(List<Employee> emps){
List<Employee> list = new ArrayList<>(); for (Employee emp : emps) {
if(emp.getSalary() >= 5000){
list.add(emp);
}
} return list;
} //优化方式一:策略设计模式
public List<Employee> filterEmployee(List<Employee> emps, MyPredicate<Employee> mp){
List<Employee> list = new ArrayList<>(); for (Employee employee : emps) {
if(mp.test(employee)){
list.add(employee);
}
} return list;
} @Test
public void test4(){
List<Employee> list = filterEmployee(emps, new FilterEmployeeForAge());
for (Employee employee : list) {
System.out.println(employee);
} System.out.println("------------------------------------------"); List<Employee> list2 = filterEmployee(emps, new FilterEmployeeForSalary());
for (Employee employee : list2) {
System.out.println(employee);
}
} //优化方式二:匿名内部类
@Test
public void test5(){
List<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() {
@Override
public boolean test(Employee t) {
return t.getId() <= 103;
}
}); for (Employee employee : list) {
System.out.println(employee);
}
} //优化方式三:Lambda 表达式
@Test
public void test6(){
List<Employee> list = filterEmployee(emps, (e) -> e.getAge() <= 35);
list.forEach(System.out::println); System.out.println("------------------------------------------"); List<Employee> list2 = filterEmployee(emps, (e) -> e.getSalary() >= 5000);
list2.forEach(System.out::println);
} //优化方式四:Stream API
@Test
public void test7(){
emps.stream()
.filter((e) -> e.getAge() <= 35)
.forEach(System.out::println); System.out.println("----------------------------------------------"); emps.stream()
.map(Employee::getName)
.limit(3)
.sorted()
.forEach(System.out::println);
}
}

   3.Lambda表达式语法格式

    比较关键的是箭头操作符(->)将表达式拆分成两部分:

      左侧——参数列表

      右侧——拉姆达体(实现功能的代码)

    对应到上文中示例的写法,Lambda表达式对应的是实现了接口的匿名内部类,那么左侧的参数列表就对应了它所实现的接口中抽象方法的

参数列表,而右边就是抽象方法的 实现(关于若接口中有多个抽象方法,将会在函数式接口中进行阐述)

    我们通过以下一些实例结合注释来加强对拉姆达表达式格式的理解:

       // 语法格式一:无参数数无返回值
Runnable r8 = () -> System.out.println("Runnable!");
// 语法格式二:有一个参数无返回值(Consumer为JDK的一个接口,具体可以参考源码)
Consumer<String> con = (x) -> System.out.println(x);
con.accept("Hello Lambda!");
// 语法格式三:只有一个参数时,小括号可以省略(当然不太建议省略)
Consumer<String> consumer = x -> System.out.println(x);
// 语法格式四:多个参数、多条语句(使用大括号)、有返回值
Comparator<Integer> c1 = (x, y) -> {
System.out.println("函数式接口!");
return Integer.compare(x, y);
};
// 语法格式五:多个参数、只有一条语句(可以省略大括号)、有返回值(可以省略return)
Comparator<Integer> c2 = (x, y) -> Integer.compare(x, y);
// 语法格式六:Lambda表达式的参数列表的参数类型可以省略!是因为JVM编译时可以通过上下文推断出数据类型:也就是类型推断!

  当然了,本质上Lambda表达式还是一个语法糖,但是如此简洁的表达一方面使得我们的代码变得简洁,但另外一方面也给调试 和维护的人员带来了不小的麻烦(尤其是还未接触JDK8新特性的人),所以即使好用,也需要慎用!

三、函数式接口

  什么是函数式接口

  只包含一个抽象方法的接口,称为函数式接口

  由上文我们也知道Lambda表达式是依赖函数式接口的(不然它也不知道它所实现的接口对应的是哪个方法),(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方 法上进行声明)

  可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口

@FunctionalInterface
public interface Flyable<T> {
int fly(T t);
}

  一个基本的示例如下:

// 接口
@FunctionalInterface
public interface Oprator {
Integer oprator(Integer num1);
} public Integer oprator(Integer num, Oprator op){
return op.oprator(num);
}
oprator(10, (x) -> x + x);

  四大内置核心函数式接口

  通过Lambda表达式呢我们可以写出非常简洁的代码,但是也暴露出一个问题:每实现一个功能(例如上文的操作一个数,或者其他的比较两个数以及更多的需求功能),每一个我们都必须新建一个相应的函数式接口——即使它只有一个抽象方法,这就导致了接口的暴涨,于是Java8中引入了内置函数式接口来解决此问题!

  像更新了函数式接口后,集合的遍历也变得更加简单(虽然底层不变),例如list.forEach(),传入一个消费式接口即可(参见源码!)

  Java8新特性(一)——Lambda表达式与函数式接口

  我们通过一段代码加注释来进行学习了解:

// 1.Consumer<T>——消费型接口——“有去无回”
public void happy(double money, Consumer<Double> con) {
// 利用Consumer接口进行消费
con.accept(money);
}
@Test
public void test1() {
// 单参数、无返回值
happy(100, (x) -> System.out.println("‘大保健消费...’"));
} // 2.Supplier<T>——供给型接口——“空手套白狼”
public List<Integer> getNumList(int num, Supplier<Integer> sup) {
// 利用Supplier接口产生指定个数个数字放入集合并返回
List<Integer> list = new ArrayList<>();
Random random = new Random();
for (int i = 0; i<num; i++) {
list.add(sup.get());
}
return list;
}
@Test
public void test2() {
// 无参数,有返回值
List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));
System.out.println(numList);
} // 3.Function<T, R>——函数型接口——“礼尚往来”
public String strHandler(String str, Function<String, String> fun) {
// 对字符串进行处理(具体处理逻辑将在Lambda表达式中实现)
return fun.apply(str);
}
@Test
public void test3() {
// 单参数、有返回值
String s = strHandler("hello", (x) -> x.toUpperCase());
System.out.println(s);
} // 4.Predicate<T>——断言型接口——“明辨是非”
public List<String> filterStr(List<String> list, Predicate<String> pre) {
List<String> strList = new ArrayList<>();
for (String s : list) {
// 对符合条件的字符串进行过滤
if (pre.test(s)) {
strList.add(s);
}
}
return strList;
}
@Test
public void test4() {
List<String> list = Arrays.asList("hello", "world", "Hello");
// 单参数,有返回值(boolean)
List<String> list1 = filterStr(list, (x) -> x.equalsIgnoreCase("hello"));
System.out.println(list1);
}

  当然如果四大内置函数接口无法满足的话,可以尝试其他的拓展接口:未全部列出,可以去java.lang.function中查看(注意JDK版本)

  Java8新特性(一)——Lambda表达式与函数式接口

四、方法引用与构造器引用

  方法引用

  当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

 (实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)

  方法引用是Lambda表达式的一种形式!

  方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来

     对象::实例方法

     类::静态方法

     类::实例方法

  先来看一个实例:

  @Test
public void test1() {
// Lambda的写法
Consumer<String> con1 = (x) -> System.out.println(x);
// 也就是System.out.println()的返回值和参数列表与接口Consumer中抽象方法一致
// PrintStream ps = System.out;
// Consumer<String> con2 = ps::println;
// 方法引用的写法 对象::实例方法名
Consumer<String> con2 = System.out::println;
con2.accept("hello method reference");
}
@Test
public void test2() {
Employee emp = new Employee("小明", 18);
// Lambda写法
Supplier<String> sup1 = () -> emp.getName();
// 方法引用(注意这里的方法是不需要写小括号的)
Supplier<String> sup2 = emp::getName;
String s = sup2.get();
System.out.println(s);
}

  从上文也可以看出,方法引用的条件是Lambda表达式中的方法体已经有方法实现了(该方法的参数列表与返回值一致!)

  上述示例就是通过对象::实例方法的形式写出的方法引用,接下来介绍其他的几种形式:注意点见注释!

 // 类::静态方法的形式
@Test
public void test3() {
// Lambda表达式的写法
Comparator<Integer> c1 = (x, y) -> Integer.compare(x, y);
// 改造为方法引用(这里使用的是Integer的静态方法)
Comparator<Integer> c2 = Integer::compare;
}
// 类::实例方法的形式
@Test
public void test4() {
// Lambda方法
BiPredicate<String, String> bp = (x, y) -> x.equalsIgnoreCase(y);
// 方法引用,需要满足条件:当需要引用方法的第一个参数是调用对象,并且第二个参数是需要引 用方法的第二个参数(或无参数)时
BiPredicate<String, String> bp2 = String::equalsIgnoreCase;
}

  构造器引用

  语法形式:ClassName::new

  // 构造器引用
@Test
public void test5() {
// Lambda表达式
Supplier<Employee> sup1 = () -> new Employee();
// 方法引用(这里自动匹配无参构造器),实际中是按照抽象方法中有几个参数便调用几个参数的构造器
Supplier<Employee> sup2 = Employee::new;
}