Lambda表达式不是Java最早使用的,很多语言就支持Lambda表达式,例如:C++,C#,Python,Scala等。如果有Python或者Javascript的语言基础,对理解Lambda表达式有很大帮助,可以这么说lambda表达式其实就是实现SAM接口的语法糖,使得Java也算是支持函数式编程的语言。Lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的匿名内部类。
1、Lambda表达式引入
示例1:Runnable实现线程
public void test1(){
new Thread(new Runnable(){
public void run(){
System.out.println("do something..");
}
}).start();
}
public void test2(){
new Thread(() -> System.out.println("do something..")).start();
}
示例2:foreach遍历
public void test3(){
List<String> list = Arrays.asList("hello","java","bdit","lambda");
for (String string : list) {
System.out.println(string);
}
}
public void test4(){
List<String> list = Arrays.asList("hello","java","bdit","lambda");
list.forEach(System.out::println);
}
示例3:FileFilter文件过滤
public void test5(){
//1.文件目录
File fileDir=new File("D:/resource");
//2.创建筛选规则,帅选出所有.java文件
FileFilter filter=new FileFilter() {
@Override
public boolean accept(File file) {
if(!file.isDirectory()&&file.getName().endsWith(".java")){
return true;
}
return false;
}
};
//3.得到筛选文件
File[] files=fileDir.listFiles(filter);
for (File file : files) {
System.out.println(file);
}
}
public void test6(){
//1.文件目录
File fileDir=new File("D:/resource");
//2.得到筛选文件,帅选出所有.java文件
File[] files=fileDir.listFiles((file) -> !file.isDirectory() && file.getName().endsWith(".java"));
//3、遍历查看结果
Arrays.asList(files).forEach(System.out::println);
}
2、函数式接口概念
Lambda表达式是用来实现SAM接口的,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法。
其实只要满足“SAM”特征的接口都可以称为函数式接口,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。
JDK1.8之前,核心类库中就已经存在很多SAM接口了,例如:
(1)java.lang.Runnable
(2)java.util.concurrent.Callable
(3)java.util.Comparator
(4)java.lang.Comparable
(5)java.lang.Iterable
(6)java.io.FileFilter
(7)java.lang.reflect.InvocationHandler
…等
但是在JDK1.8,只有(1)(2)(3)(6)加了@FunctionalInterface,那些没有加@FunctionalInterface的SAM接口,现在使用Lambda表达式实现,但是存在将来增加抽象方法变成非SAM接口的风险,因此建议只对加了@FunctionalInterface的接口使用Lambda表达式实现。
JDK1.8在java.util.function包增加了很多函数式接口,不过他们可以归纳为四类:消费型接口、供给型接口、功能型接口、判断型接口,一共43个,基本上可以满足开发中函数式接口的基本使用需求,如你在开发中需要设计函数式接口,请先从以下接口中选择是否有满足需求的,如果有几不需要重新设计了。
1、消费性接口
这类接口的抽象方法特点:有形参,但是返回值类型是void
接口名 | 抽象方法 | 描述 |
---|---|---|
Consumer< T> | void accept(T t) | 接收一个对象用于完成功能 |
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、供给型接口
这类接口的抽象方法特点:无参,但是有返回值
接口名 | 抽象方法 | 描述 |
---|---|---|
Supplier< T> | T get() | 返回一个对象 |
BooleanSupplier | boolean getAsBoolean() | 返回一个boolean值 |
DoubleSupplier | double getAsDouble() | 返回一个double值 |
IntSupplier | int getAsInt() | 返回一个int值 |
LongSupplier | long getAsLong() | 返回一个long值 |
3、判断型接口
这里接口的抽象方法特点:有参,但是返回值类型是boolean结果。
接口名 | 抽象方法 | 描述 |
---|---|---|
Predicate< T> | boolean test(T t) | 接收一个对象 |
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值 |
4、功能型接口
这类接口的抽象方法特点:既有参数又有返回值
接口名 | 抽象方法 | 描述 |
---|---|---|
Function<T,R> | R apply(T t) | 接收一个T类型对象,返回一个R类型对象结果 |
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结果 |
3、Lambda表达式
Lambda表达式是用来实现SAM接口的,它相当于一个匿名函数,语法格式如下:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
简单的说就是:
(形参列表) -> {Lambda体}
这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
(1)左侧:指定了 Lambda 表达式需要的参数列表,它其实就是函数式接口的抽象方法的形参列表
(2)右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能,它其实就是实现函数式接口的抽象方法的方法体。
例如:Lambda表达式只写了Runnable接口的抽象方法public void run()的()空参列表以及对run()方法的实现代码。
public void test1(){
new Thread(new Runnable(){
public void run(){
System.out.println("do something..");
}
}).start();
}
public void test2(){
new Thread(() -> System.out.println("do something..")).start();
}
格式要求:
1、关于(形参列表)
(1)如果没有形参,那么()不可以省略;
(2)如果有形参,并且形参只有一个,并且形参类型已知或可推断,那么可以省略()和数据类型,只写形参名;
(3)如果形参不止一个,那么()不可以省略,但是如果形参类型已知或可推断,那么可以数据类型。
2、关系{Lambda体}
(1)如果函数式接口的抽象方法有返回值,即返回值类型不是void,那么Lambda体必须要有“return 返回值;”语句;
(2)如果{Lambda体}只有一个语句,那么{}可以省略,如果{}省略了,那么语句后面的;也要省略,如果{Lambda体}只有一个return 返回值;语句,那么{return;}都可以省略,Lambda体只写返回值即可。
形式一:无参无返回值
代码示例:Runnable函数式接口
package com.bdit.lambda.learn;
import org.junit.Test;
public class TestLambda1 {
public void test1() {
//不使用Lambda表达式
new Thread(new Runnable() {
public void run() {
System.out.println("do something..");
}
}).start();
}
public void test2() {
//使用Lambda表达式
new Thread(() -> {
System.out.println("do something..");
}).start();
}
public void test3() {
//省略了可以省略的部分
new Thread(() -> System.out.println("do something..")).start();
}
}
形式二:有参无返回值
代码示例:Consumer接口
在JDK1.8中Collection集合接口的父接口Iterable接口中增加了一个默认方法:default void forEach(Consumer<? super T> action) ,遍历Collection集合的每个元素,执行“xxx消费型”操作。
在JDK1.8中Map集合接口中增加了一个默认方法:default void forEach(BiConsumer<? super K,? super V> action)遍历Map集合的每对映射关系,执行“xxx消费型”操作。
package com.bdit.lambda.learn;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
public class TestLambda2 {
public void test1(){
//不使用Lambda表达式
List<String> list = Arrays.asList("hello","java","lambda","bdit");
for (String string : list) {
System.out.println(string);
}
HashMap<Integer,String> map = new HashMap<>();
map.put(1, "hello");
map.put(2, "java");
map.put(3, "lambda");
map.put(4, "bdit");
Set<Entry<Integer, String>> entrySet = map.entrySet();
for (Entry<Integer, String> entry : entrySet) {
System.out.println(entry.getKey() + "->" + entry.getValue());
}
}
public void test2(){
//使用Lambda表达式
List<String> list = Arrays.asList("hello","java","lambda","bdit");
list.forEach((String s) -> {System.out.println(s);});
HashMap<Integer,String> map = new HashMap<>();
map.put(1, "hello");
map.put(2, "java");
map.put(3, "lambda");
map.put(4, "bdit");
map.forEach((Integer k,String v) -> {System.out.println(k+"->"+v);});
}
public void test3(){
//省略了可以省略的部分
List<String> list = Arrays.asList("hello","java","lambda","bdit");
list.forEach(s -> System.out.println(s));
HashMap<Integer,String> map = new HashMap<>();
map.put(1, "hello");
map.put(2, "java");
map.put(3, "lambda");
map.put(4, "bdit");
map.forEach((k,v) -> System.out.println(k+"->"+v));
}
}
形式三:无参无返回值
代码示例:Supplier接口
在JDK1.8中增加了StreamAPI,Stream是一个数据流,一个不同于集合的数据流。关于Stream的详细介绍请看我博客Stream API。
package com.bdit.lambda.learn;
import java.util.stream.Stream;
public class TestLambda3 {
public void test1(){
//使用Lambda表达式
Stream<Double> stream = Stream.generate(() -> {return Math.random();});
stream.forEach((Double num) -> {System.out.println(num);});
}
public void test2(){
//省略了可以省略的部分
Stream<Double> stream = Stream.generate(() -> Math.random());
stream.forEach(num -> System.out.println(num));
}
}
形式四:有参有返回值
代码示例:Funtion<T,R>接口
在JDK1.8时Map接口增加了很多方法,其中一个是:default void replaceAll(BiFunction<? super K,? super V,? extends V> function) 按照function指定的操作替换map中的value。
package com.bdit.lambda.learn;
import java.util.HashMap;
public class TestLambda4 {
public void test1(){
HashMap<String,Employee> map = new HashMap<>();
map.put("张三", new Employee("张三", 8000));
map.put("李四", new Employee("李四", 9000));
map.put("王五", new Employee("王五", 12000));
map.put("赵六", new Employee("赵六", 11000));
//把薪资低于10000的工资修改为10000
map.replaceAll((String k,Employee v) -> {
if(v.getSalary()<10000){
v.setSalary(10000);
}
return v;
});
map.forEach((String k, Employee v) -> {System.out.println(k+"->"+v);});
}
public void test2(){
HashMap<String,Employee> map = new HashMap<>();
map.put("张三", new Employee("张三", 8000));
map.put("李四", new Employee("李四", 9000));
map.put("王五", new Employee("王五", 12000));
map.put("赵六", new Employee("赵六", 11000));
//把薪资低于10000的工资修改为10000
//省略了可省略部分
map.replaceAll((k,v) -> {if(v.getSalary()<10000)v.setSalary(10000);return v;});
map.forEach((k, v) -> System.out.println(k+"->"+v));
}
}
package com.bdit.lambda.bean;
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
super();
this.name = name;
this.salary = salary;
}
public Employee() {
super();
}
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 [name=" + name + ", salary=" + salary + "]";
}
}
代码示例:Predicate接口
JDK1.8时,Collecton接口增加了一下方法,其中一个如下:default boolean removeIf(Predicate<? super E> filter) 用于删除集合中满足filter指定的条件判断的。
public void test3(){
ArrayList<Employee> list = new ArrayList<>();
list.add(new Employee("张三", 8000));
list.add(new Employee("李四", 9000));
list.add(new Employee("王五", 12000));
list.add(new Employee("赵六", 11000));
//删除那些薪资低于10000的
list.removeIf((Employee e) -> {return e.getSalary()>10000;});
list.forEach((Employee e) -> {System.out.println(e);});
}
public void test4(){
ArrayList<Employee> list = new ArrayList<>();
list.add(new Employee("张三", 8000));
list.add(new Employee("李四", 9000));
list.add(new Employee("王五", 12000));
list.add(new Employee("赵六", 11000));
//删除那些薪资低于10000的
//省略了可以省略的部分
list.removeIf(e -> e.getSalary()>10000);
list.forEach(e -> System.out.println(e));
}
4、方法引用和构造器引用
当Lambda体的实现是通过调用一个现有的方法来完成功能时,那么可以考虑再次简化代码,使用方法引用和构造器引用。
此时要求函数式接口的抽象方法的形参列表与返回值类型与该方法(要调用的方法)的形参列表与返回值类型要对应。
方法引用的语法格式:
类或对象::方法名
构造器引用的语法格式:
类名或数组类型::new
1、对象::实例方法名
例如:Consumer的抽象方法void accept(T t),它的Lambda体是通过调用System.out.println(x)来完成,它们都是有参无返回值。
示例代码:
public void test1(){
List<String> list = Arrays.asList("hello","java","lambda","bdit");
//使用Lambda表达式
list.forEach(e -> System.out.println(e));
}
public void test2(){
List<String> list = Arrays.asList("hello","java","lambda","bdit");
//使用方法引用
list.forEach(System.out::println);
}
例如:Comparator接口的抽象方法int compare(T t1,T t2),它的Lambda体是通过调用Collator文本校对器对象的compare(t1,t2)方法来完成,他们的形参列表和返回值类型完成可以对上。
示例代码:
public void test3(){
//使用Lambda表达式
TreeSet<String> set=new TreeSet<>((t1,t2)->Collator.getInstance().compare(t1,t2));
set.add("张三");
set.add("李四");
set.add("王五");
set.add("赵六");
set.forEach(e->System.out.println(e));
}
public void test4(){
//使用方法引用
TreeSet<String> set=new TreeSet<>(Collator.getInstance()::compare);
set.add("张三");
set.add("李四");
set.add("王五");
set.add("赵六");
set.forEach(System.out::println);
}
2、类::静态方法名
例如:Supplier的抽象方法T get(),它的lambda体是通过调用Math.random()来完成的,它们都是无参有返回值。
示例代码:
public void test5(){
//使用Lambda表达式
Stream<Double> stream = Stream.generate(() -> Math.random());
stream.forEach(num -> System.out.println(num));
}
public void test6(){
//使用方法引用
Stream<Double> stream = Stream.generate(Math::random);
stream.forEach(System.out::println);
}
3、类::实例方法名
例如:Comparator接口的抽象方法int compare(T t1,T t2),它的Lambda体是通过调用String对象的compareToIgnoreCase(String o)来完成的,而且这里调用compareToIgnoreCase方法是用compare()方法的第一个参数,而compare()方法剩下的参数正好是给compareToIgnoreCase的实参,它俩的返回值类型都是int。
示例代码:
public void test7(){
String[] arr = {"Hello","hello","abc","world","ABC"};
//使用generate
Arrays.sort(arr, (s1,s2)-> s1.compareToIgnoreCase(s2));
System.out.println(Arrays.toString(arr));
}
public void test8(){
String[] arr = {"Hello","hello","abc","world","ABC"};
//使用方法引用
Arrays.sort(arr, String::compareToIgnoreCase);
System.out.println(Arrays.toString(arr));
}
4、类名::new
例如:在JDK1.8中增加了一个工具类Optional,它是一个容器,可以用来包装一个对象,如果所包装的对象不为null,那么通过get()方法可以获取到这个对象,如果所包装的对象是null ,调用get()方法会报异常,所以很多时候需要通过调用orElseGet(Supplier<? extends T> other) 来获取,即如果该对象存在,就返回该对象,否则返回由Supplier接口实现所提供的对象。关于Optional类的详细介绍请看我的博客Optinal类
示例代码:
public void test9(){
HashMap<String,Employee> map = new HashMap<>();
map.put("张三", new Employee("张三", 8000));
map.put("李四", new Employee("李四", 9000));
Optional<Employee> opt = Optional.ofNullable(map.get("王五"));
//使用Lambda表达式
Employee emp = opt.orElseGet(() -> new Employee());
System.out.println(emp);
}
public void test10(){
HashMap<String,Employee> map = new HashMap<>();
map.put("张三", new Employee("张三", 8000));
map.put("李四", new Employee("李四", 9000));
Optional<Employee> opt = Optional.ofNullable(map.get("王五"));
//使用构造器引用
Employee emp = opt.orElseGet(Employee::new);
System.out.println(emp);
}
5、数组类型::new
package com.bdit;
/**
* 数组的方法引用
*/
public class Test13 {
public static void main(String[] args) {
int[]nums1=new int[10];
int[]nums2=initArray(5,s->new int[s]);
System.out.println(nums2.length);
//方法引用
int[]nums3=initArray(6,int[]::new);
System.out.println(nums3.length);
}
public static int[] initArray(int length,MyArrays myArrays){
return myArrays.buildArray(length);
}
}
@FunctionalInterface
interface MyArrays{
/**
* 根据length返回一个数组对象
* @param length
* @return
*/
int[]buildArray(int length);
}
6、通过this引用成员方法
使用this::成员方法 的语法来引用
package com.bdit;
public class Test14 {
public static void main(String[] args) {
}
public void method1(){
show(()-> System.out.println("哈哈哈哈"));
}
public void method2(){
show(()->this.method3());
}
public void method3(){
show(this::method2);
}
public void show(MyThis myThis){
myThis.test();
}
}
@FunctionalInterface
interface MyThis{
public void test();
}