1. Java8新特性:Lambda表达式
1.1 关于Java8新特性简介
Java 8 (又称为 JDK 8或JDK1.8) 是 Java 语言开发的一个主要版本。 Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。
特性:
- 速度更快
- 代码更少(增加了新的语法:Lambda表达式)
- 强大的 Stream API
- 便于并行
- 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
- Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过
parallel()
与sequential()
在并行流与顺序流之间进行切换。
- 最大化减少空指针异常:
Optional
- Nashorn引擎,允许在JVM上运行JS应用
- 发音“nass-horn”,是德国二战时一个坦克的命名
- javascript运行在jvm已经不是新鲜事了,Rhino早在jdk6的时候已经存在。现在替代Rhino,官方的解释是Rhino相比其他JavaScript引擎(比如google的V8)实在太慢了,改造Rhino还不如重写。所以Nashorn的性能也是其一个亮点。
- Nashorn 项目在 JDK 9 中得到改进;在JDK11 中
Deprecated
,后续JDK15版本中remove
。在JDK11中取以代之的是GraalVM。(GraalVM是一个运行时平台,它支持Java和其他基于Java字节码的语言,但也支持其他语言,如JavaScript,Ruby,Python或LLVM。性能是Nashorn的2倍以上。)
1.2 冗余的匿名内部类
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable
接口来定义任务内容,并使用java.lang.Thread
类来启动该线程。代码如下:
public class UseFunctionalProgramming { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("多线程任务执行!"); } }).start(); // 启动线程 } }
本着“ 一切皆对象”的思想,这种做法是无可厚非的:首先创建一个Runnable
接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。
代码分析:
对于Runnable
的匿名内部类用法,可以分析出几点内容:
Thread
类需要Runnable
接口作为参数,其中的抽象run
方法是用来指定线程任务内容的核心;- 为了指定
run
的方法体,不得不需要Runnable
接口的实现类; - 为了省去定义一个
RunnableImpl
实现类的麻烦,不得不使用匿名内部类; - 必须覆盖重写抽象
run
方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错; - 而实际上,似乎只有方法体才是关键所在。
1.3 Lambda 及其使用举例
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
- 从匿名类到 Lambda 的转换举例1
- 从匿名类到 Lambda 的转换举例2
1.4 语法
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->
” , 该操作符被称为 Lambda 操作符
或箭头操作符
。它将 Lambda 分为两个部分:
- 左侧:指定了 Lambda 表达式需要的参数列表
- 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能。
语法格式一: 无参,无返回值
@Test public void test1(){ //未使用Lambda表达式 Runnable r1 = new Runnable() { @Override public void run() { System.out.println("我爱Java"); } }; r1.run(); System.out.println("***********************"); //使用Lambda表达式 Runnable r2 = () -> { System.out.println("我爱Java"); }; r2.run(); }
语法格式二: Lambda 需要一个参数,但是没有返回值。
@Test public void test2(){ //未使用Lambda表达式 Consumer<String> con = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; con.accept("你问我爱你有多深?"); System.out.println("*******************"); //使用Lambda表达式 Consumer<String> con1 = (String s) -> { System.out.println(s); }; con1.accept("月亮代表我的心"); }
语法格式三: 数据类型可以省略,因为可由编译器推断得出,称为“ 类型推断 ”
@Test public void test3(){ //语法格式三使用前 Consumer<String> con1 = (String s) -> { System.out.println(s); }; con1.accept("月亮代表我的心"); System.out.println("*******************"); //语法格式三使用后 Consumer<String> con2 = (s) -> { System.out.println(s); }; con2.accept("月亮代表我的心"); }
语法格式四: Lambda 若只需要一个参数时,参数的小括号可以省略
@Test public void test4(){ //语法格式四使用前 Consumer<String> con1 = (s) -> { System.out.println(s); }; con1.accept("月亮代表我的心"); System.out.println("*******************"); //语法格式四使用后 Consumer<String> con2 = s -> { System.out.println(s); }; con2.accept("月亮代表我的心"); }
语法格式五: Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
@Test public void test5(){ //语法格式五使用前 Comparator<Integer> com1 = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); } }; System.out.println(com1.compare(12,21)); System.out.println("*****************************"); //语法格式五使用后 Comparator<Integer> com2 = (o1,o2) -> { System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); }; System.out.println(com2.compare(12,6)); }
语法格式六: 当 Lambda 体只有一条语句时,return 与大括号 都可以省略
@Test public void test6(){ //语法格式六使用前 Comparator<Integer> com1 = (o1,o2) -> { return o1.compareTo(o2); }; System.out.println(com1.compare(12,6)); System.out.println("*****************************"); //语法格式六使用后 Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2); System.out.println(com2.compare(12,21)); } @Test public void test7(){ //语法格式六使用前 Consumer<String> con1 = s -> { System.out.println(s); }; con1.accept("一个是听得人当真了,一个是说的人当真了"); System.out.println("*****************************"); //语法格式六使用后 Consumer<String> con2 = s -> System.out.println(s); con2.accept("一个是听得人当真了,一个是说的人当真了"); }
1.5 关于类型推断
在语法格式三 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断
”。
举例:
@Test public void test() { //类型推断1 ArrayList<String> list = new ArrayList<>(); //类型推断2 int[] arr = {1, 2, 3}; }
2. Java8新特性:函数式(Functional)接口
2.1 什么是函数式接口
- 只包含
一个抽象方法
(Single Abstract Method,简称SAM)的接口,称为函数式接口。当然该接口可以包含其他非抽象方法。 - 可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
- 我们可以在一个接口上使用
@FunctionalInterface
注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。 - 在
java.util.function
包下定义了Java 8 的丰富的函数式接口
2.2 如何理解函数式接口
- Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,即Java不但可以支持OOP还可以支持OOF(面向函数编程)
- Java8引入了Lambda表达式之后,Java也开始支持函数式编程。
- Lambda表达式不是Java最早使用的。目前C++,C#,Python,Scala等均支持Lambda表达式。
- 面向对象的思想:
- 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。
- 函数式编程思想:
- 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。
- 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
- 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
2.3 举例
举例1:
举例2:
作为参数传递 Lambda 表达式:
作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
2.4 Java 内置函数式接口
2.4.1 常见的函数式接口
函数式接口,比如:
java.lang.Runnable
public void run()
java.lang.Iterable
public Iterator iterate()
java.lang.Comparable
public int compareTo(T t)
java.util.Comparator
public int compare(T t1, T t2)
2.4.2 四大核心函数式接口
函数式接口 | 称谓 | 参数类型 | 用途 |
Consumer<T> |
消费型接口 | T | 对类型为T的对象应用操作,包含方法: void accept(T t) |
Supplier<T> |
供给型接口 | 无 | 返回类型为T的对象,包含方法:T get() |
Function<T, R> |
函数型接口 | T | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t) |
Predicate<T> |
判断型接口 | T | 确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t) |
2.4.3 其它接口
类型1:消费型接口
消费型接口的抽象方法特点:有形参,但是返回值类型是void
接口名 | 抽象方法 | 描述 |
BiConsumer<T,U> |
void accept(T t, U u) | 接收两个对象用于完成功能 |
DoubleConsumer |
void accept(double value) | 接收一个double值 |
IntConsumer |
void accept(int value) | 接收一个int值 |
LongConsumer |
void accept(long value) | 接收一个long值 |
ObjDoubleConsumer<T> |
void accept(T t, double value) | 接收一个对象和一个double值 |
ObjIntConsumer<T> |
void accept(T t, int value) | 接收一个对象和一个int值 |
ObjLongConsumer<T> |
void accept(T t, long value) | 接收一个对象和一个long值 |
类型2:供给型接口
这类接口的抽象方法特点:无参,但是有返回值
接口名 | 抽象方法 | 描述 |
BooleanSupplier | boolean getAsBoolean() | 返回一个boolean值 |
DoubleSupplier | double getAsDouble() | 返回一个double值 |
IntSupplier | int getAsInt() | 返回一个int值 |
LongSupplier | long getAsLong() | 返回一个long值 |
类型3:函数型接口
这类接口的抽象方法特点:既有参数又有返回值
接口名 | 抽象方法 | 描述 |
UnaryOperator<T> | T apply(T t) | 接收一个T类型对象,返回一个T类型对象结果 |
DoubleFunction<R> | R apply(double value) | 接收一个double值,返回一个R类型对象 |
IntFunction<R> | R apply(int value) | 接收一个int值,返回一个R类型对象 |
LongFunction<R> | R apply(long value) | 接收一个long值,返回一个R类型对象 |
ToDoubleFunction<T> | double applyAsDouble(T value) | 接收一个T类型对象,返回一个double |
ToIntFunction<T> | int applyAsInt(T value) | 接收一个T类型对象,返回一个int |
ToLongFunction<T> | long applyAsLong(T value) | 接收一个T类型对象,返回一个long |
DoubleToIntFunction | int applyAsInt(double value) | 接收一个double值,返回一个int结果 |
DoubleToLongFunction | long applyAsLong(double value) | 接收一个double值,返回一个long结果 |
IntToDoubleFunction | double applyAsDouble(int value) | 接收一个int值,返回一个double结果 |
IntToLongFunction | long applyAsLong(int value) | 接收一个int值,返回一个long结果 |
LongToDoubleFunction | double applyAsDouble(long value) | 接收一个long值,返回一个double结果 |
LongToIntFunction | int applyAsInt(long value) | 接收一个long值,返回一个int结果 |
DoubleUnaryOperator | double applyAsDouble(double operand) | 接收一个double值,返回一个double |
IntUnaryOperator | int applyAsInt(int operand) | 接收一个int值,返回一个int结果 |
LongUnaryOperator | long applyAsLong(long operand) | 接收一个long值,返回一个long结果 |
BiFunction<T,U,R> | R apply(T t, U u) | 接收一个T类型和一个U类型对象,返回一个R类型对象结果 |
BinaryOperator<T> | T apply(T t, T u) | 接收两个T类型对象,返回一个T类型对象结果 |
ToDoubleBiFunction<T,U> | double applyAsDouble(T t, U u) | 接收一个T类型和一个U类型对象,返回一个double |
ToIntBiFunction<T,U> | int applyAsInt(T t, U u) | 接收一个T类型和一个U类型对象,返回一个int |
ToLongBiFunction<T,U> | long applyAsLong(T t, U u) | 接收一个T类型和一个U类型对象,返回一个long |
DoubleBinaryOperator | double applyAsDouble(double left, double right) | 接收两个double值,返回一个double结果 |
IntBinaryOperator | int applyAsInt(int left, int right) | 接收两个int值,返回一个int结果 |
LongBinaryOperator | long applyAsLong(long left, long right) | 接收两个long值,返回一个long结果 |
类型4:判断型接口
这类接口的抽象方法特点:有参,但是返回值类型是boolean结果。
接口名 | 抽象方法 | 描述 |
BiPredicate<T,U> | boolean test(T t, U u) | 接收两个对象 |
DoublePredicate | boolean test(double value) | 接收一个double值 |
IntPredicate | boolean test(int value) | 接收一个int值 |
LongPredicate | boolean test(long value) | 接收一个long值 |
2.4.4 内置接口代码示例
举例1:
import java.util.Arrays; import java.util.List; public class TestConsumer { public static void main(String[] args) { List<String> list = Arrays.asList("java","c","python","c++","VB","C#"); //遍历Collection集合,并将传递给action参数的操作代码应用在每一个元素上。 list.forEach(s -> System.out.println(s)); } }
举例2:
import java.util.function.Supplier; public class TestSupplier { public static void main(String[] args) { Supplier<String> supplier = () -> "hello world"; System.out.println(supplier.get()); } }
举例3:
import java.util.ArrayList; public class TestPredicate { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("hello"); list.add("java"); list.add("ok"); list.add("yes"); System.out.println("删除之前:"); list.forEach(t-> System.out.println(t)); //用于删除集合中满足filter指定的条件判断的。 //删除包含o字母的元素 list.removeIf(s -> s.contains("o")); System.out.println("删除包含o字母的元素之后:"); list.forEach(t-> System.out.println(t)); } }
举例4:
import java.util.function.Function; public class TestFunction { public static void main(String[] args) { //使用Lambda表达式实现Function<T,R>接口,可以实现将一个字符串首字母转为大写的功能。 Function<String,String> fun = s -> s.substring(0,1).toUpperCase() + s.substring(1); System.out.println(fun.apply("hello")); } }
2.4.5 示例
练习1:无参无返回值形式
假如有自定义函数式接口Call如下:
public interface Call { void shout(); }
在测试类中声明一个如下方法:
public static void callSomething(Call call){ call.shout(); }
在测试类的main方法中调用callSomething方法,并用Lambda表达式为形参call赋值,可以喊出任意你想说的话。
public class TestLambda { public static void main(String[] args) { callSomething(()->System.out.println("回家吃饭")); callSomething(()->System.out.println("我爱你")); callSomething(()->System.out.println("滚蛋")); callSomething(()->System.out.println("回来")); } public static void callSomething(Call call){ call.shout(); } } interface Call { void shout(); }
练习2:消费型接口
代码示例:Consumer
接口
在JDK1.8中Collection集合接口的父接口Iterable
接口中增加了一个默认方法:
public default void forEach(Consumer action)
遍历Collection集合的每个元素,执行“xxx消费型”操作。
在JDK1.8中Map集合接口中增加了一个默认方法:
public default void forEach(BiConsumer action)
遍历Map集合的每对映射关系,执行“xxx消费型”操作。
案例:
(1)创建一个Collection系列的集合,添加一些字符串,调用forEach
方法遍历查看
(2)创建一个Map系列的集合,添加一些(key,value)键值对,调用forEach
方法遍历查看
示例代码:
@Test public void test1(){ List<String> list = Arrays.asList("hello","java","lambda"); list.forEach(s -> System.out.println(s)); } @Test public void test2(){ HashMap<Integer,String> map = new HashMap<>(); map.put(1, "hello"); map.put(2, "java"); map.put(3, "lambda"); map.forEach((k,v) -> System.out.println(k+"->"+v)); }
练习3:供给型接口
代码示例:Supplier
接口
在JDK1.8中增加了StreamAPI,java.util.stream.Stream是一个数据流。这个类型有一个静态方法:
public static Stream generate(Supplier s)
可以创建Stream的对象。而又包含一个forEach方法可以遍历流中的元素:public void forEach(Consumer action)
。
案例:
现在请调用Stream的generate方法,来产生一个流对象,并调用Math.random()方法来产生数据,为Supplier函数式接口的形参赋值。最后调用forEach方法遍历流中的数据查看结果。
@Test public void test2(){ Stream.generate(() -> Math.random()).forEach(num -> System.out.println(num)); }
练习4:功能型接口
代码示例:Function
接口
在JDK1.8时Map接口增加了很多方法,例如:
public default void replaceAll(BiFunction function)
按照function指定的操作替换map中的value。
public default void forEach(BiConsumer action)
遍历Map集合的每对映射关系,执行“xxx消费型”操作。
案例:
(1)声明一个Employee员工类型,包含编号、姓名、薪资。
(2)添加n个员工对象到一个HashMap集合中,其中员工编号为key,员工对象为value。
(3)调用Map的forEach遍历集合
(4)调用Map的replaceAll方法,将其中薪资低于10000元的,薪资设置为10000。
(5)再次调用Map的forEach遍历集合查看结果
Employee类:
class Employee{ private int id; private String name; private double salary; public Employee(int id, String name, double salary) { super(); this.id = id; this.name = name; this.salary = salary; } public Employee() { super(); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } @Override public String toString() { return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]"; } }
测试类:
import java.util.HashMap; public class TestLambda { public static void main(String[] args) { HashMap<Integer,Employee> map = new HashMap<>(); Employee e1 = new Employee(1, "张三", 8000); Employee e2 = new Employee(2, "李四", 9000); Employee e3 = new Employee(3, "王五", 10000); Employee e4 = new Employee(4, "赵六", 11000); Employee e5 = new Employee(5, "钱七", 12000); map.put(e1.getId(), e1); map.put(e2.getId(), e2); map.put(e3.getId(), e3); map.put(e4.getId(), e4); map.put(e5.getId(), e5); map.forEach((k,v) -> System.out.println(k+"="+v)); System.out.println(); map.replaceAll((k,v)->{ if(v.getSalary()<10000){ v.setSalary(10000); } return v; }); map.forEach((k,v) -> System.out.println(k+"="+v)); } }
JDK8中的新特性(Lambda、函数式接口、方法引用、Stream)(二):https://developer.aliyun.com/article/1416368