JDK8新特性——Lambda表达式

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 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 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 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 R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperator< T> T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
ToIntBiFunction int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
ToLongBiFunction 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 action) ,遍历Collection集合的每个元素,执行“xxx消费型”操作。

在JDK1.8中Map集合接口中增加了一个默认方法:default void forEach(BiConsumer 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接口

在JDK1.8时Map接口增加了很多方法,其中一个是:default void replaceAll(BiFunction 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 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 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();
}

你可能感兴趣的:(java)