一、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(),传入一个消费式接口即可(参见源码!)
我们通过一段代码加注释来进行学习了解:
// 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版本)
四、方法引用与构造器引用
方法引用
当要传递给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;
}