java8新特性 函数式编程 Lamda

简介

函数式编程核心以处理数据的方式处理代码

相比较命令式编程的优点:

1. 代码更加简洁优雅,效率更高 
2. 避免了对变量的显式修改和赋值 
3. 函数式风格的代码可以轻松的实现并行化 
4. 代码表达性更强,更直观

函数式接口

理解FunctionalInterface(函数式接口)是学习Java8 Lamd表达式的关键所在。

函数式接口:只定义了单一抽象方法的接口,用作Lamda表达式的类型。

注意:函数式接口只能有一个抽象方法,而不是只能有一个方法。

例子:Runnable接口

@FunctionalInterface
public interface Runnable {

    public abstract void run();
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5

Lamda表达式

Lamda表达式是函数式编程的核心

定义:Lamda表达式即匿名函数,它是一段没有函数名的函数体,可以作为参数直接传递给相关调用者。

Lamda表达式的语法:

(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

几种情形时的简洁写法: 
1、Lamda表达式没有参数,使用空括号()表示没有参数。

() -> { //..... };
  
  
  • 1

2、Lamda表达式只有一个参数,可省略参数括号和参数类型,Javac能够根据上下文推断出参数类型。

param1 -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3、当Lamda表达式只包含一条语句时,可以省略大括号{}。

param1 -> statment
  
  
  • 1

4、绝大多数情况下,参数类型可以省略,编译器都可以从上下文环境中推断出参数类型。

(param1,param2, ..., paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

最简单的例子:

//定义一个接口
public interface Hunman {

    void say(String str);
}
//Lamda使用接口
Hunman h = str -> System.out.println(str);
h.say("Hello World");
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

方法引用

方法引用是Java8用来简化Lamda表达式的一种手段,它通过类名和方法名来定位一个静态方法或者实例方法。

语法:方法引用使用“::“定义,“::“前半部分是类名或者实例名,后半部分表示方法名,如果是构造函数方法名则使用new表示。

1、静态方法引用*:ClassName::methodName

List<String> strs = Arrays.asList("aa","bb","cc");
strs.forEach(System.out::println);
//结果
//aa
//bb
//cc
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、实例方法引用:instanceReference:methodName

class Printer {

    void print(){
        System.out.println("instanceRefence::methodName");
    }

    private void printInfo(){
        //实例方法引用
        new Thread(this::print);
    }
}
//测试
private void test() {
    Printer p = new Printer();
    p.print();
}

//结果
//instanceRefence::methodName
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3、构造方法引用:Class::new

public class Test {

    class User {
        String username;

        User(String username){
            this.username = username;
        }

        public String getUsername(){
            return username;
        }
    }

    @FunctionalInterface
    interface UserFactory<T extends User> {
        T create(String username);
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.test();
    }

    private void test() {
        UserFactory<User> uf = User::new;
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 5; ++i) {
            users.add(uf.create("user"+i));
        }
        users.stream().map(User::getUsername).forEach(System.out::println);
    }
}
//结果
//user0
//user1
//user2
//user3
//user4
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

Lamda表达式在集合中的运用:流Stream

改进集合的迭代方式

集合的迭代:外部迭代、内部迭代。

外部迭代:在使用Java集合时,通用模式是在集合上进行迭代,然后处理返回的每一个元素。每次迭代集合类时,需要写很多样板代码。将for循环改成并行的方式也很麻烦。

图解: 
外部迭代

代码示例:

//使用for循环输出集合学生名字
public void test() {
    List<Student> students = init();
    for (Student stu : students) {
        System.out.println(stu.getStuName());
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

内部迭代:使用流Stream,是函数式编程方式在集合类上进行复杂操作的工具。

图解: 
内部迭代

代码示例:

private void test() {
    List<Student> students = init();
    students.stream().forEach(student -> System.out.println(student.getStuName()));
}
  
  
  • 1
  • 2
  • 3
  • 4

Stream方法介绍

1)collect

collect(toList())方法由Stream里的值生成一个列表。 
Stream的of方法使用一组初始值生成新的Steam。

代码示例:

private void test() {
    List<String> strs = Stream.of("a","b","c").collect(Collectors.toList());
    print(strs);
}
  
  
  • 1
  • 2
  • 3
  • 4

2)map

将一种类型的值转换成另外一种类型,将一个流中的值转换成一个新的流。

对于Stream中包含的元素使用给定的转换函数进行转换操作,新生>成的Stream只包含转换生成的元素。

代码示例:

//使用map操作将字符串转换成大写字母:
private void test() {
    List<String> strs = Stream.of("a","b","c")
            .map(str->str.toUpperCase())
            .collect(Collectors.toList());
    print(strs);
//结果ABC
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3)filter

对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生>成的Stream只包含符合条件的元素。

代码示例:

//使用filter来实现:输出一个数字集合里大于10的数字
private void test() {
    System.out.println("使用filter:");
    List<Integer> numbers = Stream.of(5,10,15).collect(Collectors.toList());
    numbers.stream().filter(x -> x > 10).forEach(System.out::println);
 //结果15
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4)flatMap

和map类似,不同的是其每个元素转换得到的是Stream对象,会把>子Stream中的元素压缩到父集合中;

代码示例:

//将两个数字集合合并成一个集合:
private void test() {
    List<Integer> nums = Stream.of(Arrays.asList(1, 2, 3), Arrays.asList(4, 5))
            .flatMap(numList -> numList.stream())
            .collect(Collectors.toList());
    nums.forEach(num -> System.out.println(num));
}
//结果12345
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

5)reduce

reduce操作可以从一组值中生成一个值。

代码示例:

    /*
     *   例子:如何通过reduce操作对Stream中的数字求和,
     *   以0作为起点(这里的0指的不是坐标而是数字0,求乘积以1为
     *   起点),一个空Stream的求和结果,
     *   每一步都将Stream中的元素累加至sum,
     *   遍历Stream中的所有元素,sum的值就是所有元素的和。
    */
    //使用reduce
    public Integer useReduceToSum(){
         int number= Stream.of(1,2,3).reduce(0,(sum,y)->sum+y);
         return number;
    }

    //使用reduce函数式编程方式,求一个数字列表{2,4,8,12}的乘积。注意相乘是以1作为起点
    public Integer useReduceToMultiply(){
        int number= Stream.of(2,4,8).reduce(1,(multiply,y)->multiply*y);
        return number;
    }
    //结果6 64
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

并行化流parallelStream

Java8中可以在接口不变的情况下,将流改为并行流,这样很自然地使用多线程进行集合中的数据处理。并行化操作流只需要改变一个方法调用,如果已经是一个Stream对象,调用它的parallel方法就能让其拥有并行操作的能力;如果想从一个集合类创建一个流,调用parallelStream就能立即获得一个拥有并行能力的流。

代码示例:

//例1,统计1~1000000内所有的质数的数量。

    //判断一个数是否为质数
    public static boolean isPrime(int number) {
        if (number < 2) {
            return false;
        }
        for (int i = 2; i <= Math.sqrt(number); ++i) {
            if (number % i == 0) {
                return false;
            }
        }
        return true;
    }

    //使用串行操作统计质数
    public void bingxingCount() {
        long startTime = System.currentTimeMillis();
        long count = IntStream.range(1, 1000000).filter(ParallelStream::isPrime).count();
        long endTime = System.currentTimeMillis();
        System.out.println("1000000以内质数的个数:" + count + "\t" + "消耗时间:" + (endTime - startTime));
    }

    //使用并行操作统计质数
    public void chuanxingCount() {
        long startTime = System.currentTimeMillis();
        long count = IntStream.range(1, 1000000).parallel().filter(ParallelStream::isPrime).count();
        long endTime = System.currentTimeMillis();
        System.out.println("1000000以内质数的个数:" + count + "\t" + "消耗时间:" + (endTime - startTime));
    }

    //例2:使用串行和并行的方式,排列数组
    public void sortArray() {
        int[] arr = getNumbers();
        long start = System.currentTimeMillis();
        Arrays.sort(arr);
        System.out.println("串行排序时间:"+(System.currentTimeMillis() - start) + " ms" );

        arr = getNumbers();
        start = System.currentTimeMillis();
        Arrays.parallelSort(arr);
        System.out.println("并行排序时间:"+(System.currentTimeMillis() - start) + " ms" );
    }

    private int[] getNumbers() {
        int[] arr = new int[5000000];
        Random r = new Random();
        for (int i = 0; i < 5000000; ++i) {
            arr[i] = r.nextInt(1000) + 1;
        }
        return arr;
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

输出结果:采用并行流操作方式时间是串行操作的时间一半不到。

增强的Future

1)CompletableFuture

CompletableFuture是Java8新增的一个超大型工具类,它实现了Future接口,CompletionStage接口。通过CompletionStage提供的接口,可以在一个执行结果上进行多次流式调用,以此得到最终结果。

CompletableFuture和Future一样,可以作为函数调用的契约。向CompletableFuture请求一个数据,如果数据还没有准备好,请求线程就会等待。

代码示例:

//例子:用CompletableFuture输出一个整数的平方-------------------------------------

    CompletableFuture<Integer> cf = null;

    //接收CompletableFuture作为其构造函数
    public CompetableFuture(CompletableFuture<Integer> cf) {
        this.cf = cf;
    }

    @Override
    public void run() {
        int tmp = 0;
        try {
            tmp = cf.get() * cf.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(tmp);
    }
    /*
        创建一个CompletableFuture对象实例,将这个对象实例传递给TaskRun。
        TaskRun在执行到tmp = cf.get() * cf.get()会阻塞,
        因为CompletableFuture中没有它所需要的数据,整个CompletableFuture处于未完成状态。
    */

    public static void printResult() {
        final CompletableFuture<Integer> future = new CompletableFuture<>();
        new Thread(new CompetableFuture(future)).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
        future.complete(100);
    }

    //------------------异步执行任务------------------------------------
    //例子2,异步计算10的3次方
    public static void caculateResult() {
        final CompletableFuture<Integer> future =
                CompletableFuture.supplyAsync(() -> calculate(100));
        try {
            //如果当前计算为完成,调用get()的线程就会等待
            System.out.println(future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("exit");
    }

    private static int calculate(int x) {
        int res = 0;
        try {
            Thread.sleep(1000);
            res = x * x * x;
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
        return res;
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

2)CompletionStage流式调用

CompletionStage有约40个方法是为函数式编程做准备的,通过CompletionStage提供的接口,可以在一个执行结果上进行多次流式调用,以此得到最终结果。

代码示例:


    /*   例子,异步计算100的2次方,然后转换成字符串+str,最后输出。
     *   supplyAsync()方法执行一个异步任务,接着连续使用流式调用对任务的处理结果进行再加工,直到最后输出结果。
     */
    public static void printResult() {
        final int num = 100;
        final CompletableFuture<Void> future =
                CompletableFuture.supplyAsync(() -> calculate(num))
                        .thenApply(x -> Integer.toString(x))
                        .thenApply((str) -> num + "的平方: " + str)
                        .thenAccept(System.out::println);
        try {
            future.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("exit");
    }

    private static int calculate(int x) {
        int res = 0;
        try {
            Thread.sleep(1000);
            res = x * x;
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
        return res;
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

3)组合多个CompletableFuture 
方法1:使用thenCompose(),一个CompletableFuture可以在执行完成后,将执行结果通过Function传递给下一个ComposeStage进行处理。

代码示例:

//例子:计算100的2次方,然后除以2,最后输出。
    public static void printResult() {
        final int num = 100;
        final CompletableFuture<Void> future =
                CompletableFuture.supplyAsync(()->calculate(num))
                        .thenCompose((i) -> CompletableFuture.supplyAsync(()->divi(i)))
                        .thenApply((str) -> num + "的平方除以2: " + str)
                        .thenAccept(System.out::println);
        try {
            future.get();
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("exit");
    }

    private static int calculate(int x) {
        int res = 0;
        try {
            Thread.sleep(1000);
            res = x * x;
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
        return res;
    }

    private static int divi(int i){
        return i/2;
    }
    //结果:5000
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

方法2:使用thenCombine()

代码示例:

 /* 例子:计算10的平方,50除以2,然后将二者的结果求和。
    *
    * thenCombine()方法首先完成当前CompletableFuture和other的执行,
    * 接着,将这两者的执行结果传递给BiFunction,并返回BiFunction实例的CompletableFuture对象。
    */
public static void printResult() {

        CompletableFuture<Integer> future1= CompletableFuture.supplyAsync(()->calculate(10));
        CompletableFuture<Integer> future2= CompletableFuture.supplyAsync(()->divi(50));
        CompletableFuture<Void> cf = future1.thenCombine(future2,(x,y) -> (x + y))
                .thenAccept(System.out::println);
        try {
            cf.get();
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("exit");
    }

    private static int calculate(int x) {
        int res = 0;
        try {
            Thread.sleep(1000);
            res = x * x;
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
        return res;
    }

    private static int divi(int i){
        return i/2;
    }
    //结果:125
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

Lamda表达式的测试和在Junit5中的使用

1)lambda表达式没有名字,无法直接在测试代码中调用。应该讲重点放在方法的行为上。

代码示例:

@Test
public void TestLamda() throws Exception{
    List<String> wordsOne = Arrays.asList("a","b","c");
    List<String> res = allToUpperCase(wordsOne);

    List<String> wordsTwo = Arrays.asList("A","B","C");
    Assert.assertEquals(wordsTwo,res);
}

public List<String> allToUpperCase(List<String> words) {
    return words.stream()
            .map(word->word.toUpperCase())
            .collect(Collectors.toList());
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2)Junit5和Lamda

JUnit 5完全使用当前的Java 8重写了所有代码,因此JUnit 5的运行条件是Java 8环境。 
JUnit 5允许在断言中使用Lambda表达式,这个特性可以从开源的断言库AssertJ中可以看到。

JUnit 5的测试看上去与JUnit 4相同:同样是创建类,添加测试方法,使用@Test注释。但是,JUnit 5还提供了全新的一套注释集合,而且断言方法从JUnit 4的org.junit.Assert包移到了JUnit 5的org.junit.gen5.api.Assertions包。

JUnit 5的断言方法与JUnit 4相似,断言类提供了assertTrue、assertEquals、assertNull、assertSame以及相反的断言方法。不同之处在于JUnit 5的断言方法支持Lambda表达式。而且还有一个名为分组断言(Grouped Assertions)的新特性。分组断言允许执行一组断言,且会一起报告。

代码示例:

public class Junit5AndLamda {
    @Test
    public void lambdaExpressions() {
        // lambda expression for condition
        assertTrue(() -> "".isEmpty(), "string should be empty");
        // lambda expression for assertion message
        assertEquals("foo", "foo", () -> "message is lazily evaluated");
    }

    @Test
    public void groupedAssertions() {
        Dimension dim = new Dimension(100, 60);
        assertAll("dimension",
                () -> assertTrue(dim.getWidth() == 100, "width"),
                () -> assertTrue(dim.getHeight() == 60, "height"));
    }

    @Test
    public void exceptions() {
        // assert exception type
        assertThrows(RuntimeException.class, () -> {
            throw new NullPointerException();
        });
        // assert on the expected exception
        Throwable exception = expectThrows(RuntimeException.class, () -> {
            throw new NullPointerException("should not be null");
        });
        assertEquals("should not be null", exception.getMessage());
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

使用Lamda重构面向对象的设计模式

1)命令者模式 
2)策略模式 
3)观察者模式 
4)模板方法模式

猜你喜欢

转载自blog.csdn.net/hua_faded/article/details/78708091