zoukankan      html  css  js  c++  java
  • Java8函数式编程

    Java8函数式编程

    为什么要用Java8

    Java8在并行处理大型集合上有很大优势。可以更好的利用多核处理器的优势。Java8可以用Lambda表达式很简便的写出复杂的处理集合的逻辑。

    函数式编程

    函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,逻辑式编程,常见的面向对象编程是也是一种命令式编程。

    命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机的指令序列。而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式。

    函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。在函数式语言中,函数作为一等公民,可以在任何地方定义,在函数内或函数外,可以作为函数的参数和返回值,可以对函数进行组合。

    Java是一门面向对象的语言,对函数式编程的支持并不全面,如果想深入理解学习函数式编程可以学习Scala、Clojure,这两种语言都是可以在jvm上运行的。

    lambda表达式

    Java8最大的改变就是加入了lambda表达式。什么是lambda表达式呢?

    简单来说,编程中提到的 lambda 表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。

    比如有些函数你只会使用一次,但是你却给他定义一次,这个函数就成了污染函数,匿名函数就是用来干这个的。

    举个栗子:

    faculties.sort(new Comparator<Faculty>() {
                @Override
                public int compare(Faculty o1, Faculty o2) {
                    return o1.getName().compareTo(o2.getName());
                }
            });
    

    为了给这个专业列表(faculties)排序,你专门写了一个匿名内部类,来实现比较大小的方法。这样看起来又臃肿又复杂,如果用lambda表达式就很简单了:

    faculties.sort((f1, f2) -> f1.getName().compareTo(f2.getName()));
    

    还可以用方法引用可以更简单:

    faculties.sort(Comparator.comparing(Faculty::getName));
    

    下面有各种不同的方法来写lambda表达式:

    Runnable noArguments = () -> System.out.println("Hello World");	
    ActionListener oneArgument = event -> System.out.println("button clicked");
    Runnable multiStatement = () -> {
    System.out.print("Hello");
    System.out.println(" World");
    };
    BinaryOperator<Long> add = (x, y) -> x + y;
    BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
    

    lambda表达式可以接受一个或者多个参数,lambda表达式可以转换成一个接口不能转化成类,转化成接口要保证接口中只有一个可以复写的方法。不然会报错。

    Streams

    Stream可以让我们在更高级别的抽象上写集合的处理代码,stream有一系列的方法来让我们使用。

    现在有一需求,需要筛选出来自伦敦的艺术家,通常我们会这么写:

    int count = 0;
    for (Artist artist : allArtists) {
    	if (artist.isFrom("London")) { 
    		count++;
    	} 
    }
    

    用stream可以这么写:

    long count = allArtists.stream()
    						.filter(artist -> artist.isFrom("London"))
    						.count();
    

    这样看起来就清晰很多,而且效率也是差不多的,用stream不会循环两遍的,stream的方法分为eager和lazy两种,比如下面这段代码:

    allArtists.stream()
          .filter(artist -> {
              System.out.println(artist.getName());
    			 return artist.isFrom("London"); 
    		});
    

    它不会打印任何东西,filter就是一个lazy的方法,那怎么区分lazy和eager方法呢?如果返回的还是一个stream那么就是一个lazy的方法,如果返回的不是stream就是eager方法。例如上面的count()方法返回的是int,那么就会被立刻执行。

    collect(toList())

    List<String> collected = Stream.of("a", "b", "c")
                                       .collect(Collectors.toList());
        assertEquals(Arrays.asList("a", "b", "c"), collected);
    

    collect(toList())把一个Stream转换成List。

    map

    List<String> collected = Stream.of("a", "b", "hello")
                                   .map(string -> string.toUpperCase())
                                   .collect(toList());
    

    map可以把一种类型的stream数据转换成另一种。

    filter

    List<String> beginningWithNumbers
      = Stream.of("a", "1abc", "abc1")
    		  .filter(value -> isDigit(value.charAt(0)))
    		  .collect(toList());
    

    filter可以把filter内返回为true的的数据过滤出来。

    flatMap

    List<Integer> together = Stream.of(asList(1, 2), asList(3, 4))
                                   .flatMap(numbers -> numbers.stream())
                                   .collect(toList());
    

    看上面的图更好理解一点,flatmap可以把stream的元素转换成stream,然后再把这些stream合成一个新的stream,而map只能把stream中的元素从一种类型转换成另一种类型。

    max and min

    List<Track> tracks = asList(new Track("Bakai", 524),
    							new Track("Violets for Your Furs", 378),
    							new Track("Time Was", 451));
    Track shortestTrack = tracks.stream()
                                .min(Comparator.comparing(track -> track.getLength()))
                                .get();
    

    在stream中找到最大值和最小值。

    reduce

    int count = Stream.of(1, 2, 3)
    				  .reduce(0, (acc, element) -> acc + element);
    

    reduce会把第一个参数作为初始的acc,遍历stream的元素作为element,然后每次计算的结果会作为下次的acc.上面的代码可以拓展为:

    BinaryOperator<Integer> accumulator = (acc, element) -> acc + element; int count = accumulator.apply(
                        accumulator.apply(
                            accumulator.apply(0, 1),
    2), 3);
    
    综合

    把上面的各种方法结合起来用一下:

    Set<String> origins = album.getMusicians()
                                   .filter(artist -> artist.getName().startsWith("The"))
                                   .map(artist -> artist.getNationality())
                                   .collect(toSet());
    
    1. 过滤出名字以The为开头的艺术家
    2. 取出艺术家的国籍
    3. 得到一个艺术家的国籍的Set(去重)

    再看一个例子:

    public Set<String> findLongTracks(List<Album> albums) { return albums.stream()
                     .flatMap(album -> album.getTracks())
                     .filter(track -> track.getLength() > 60)
                     .map(track -> track.getName())
                     .collect(toSet());
    }
    
    1. 把所有专辑的曲目取出来合并成一个stream
    2. 过滤出大于60秒的曲目
    3. 把曲目的名字取出来的
    4. 转换成一个Set

    方法引用

    artist -> artist.getName()
    Artist::getName
    
    (name, nationality) -> new Artist(name, nationality)
    Artist::new
    

    方法引用是一种更简洁的写法,而且比lambda表达式更容易理解。

    对数据进行分类

    一种分类方法是:partiioningBy

    public Map<Boolean, List<Artist>> bandsAndSolo(Stream<Artist> artists) { 
    	return artists.collect(partitioningBy(artist -> artist.isSolo()));
    }
    

    有时候我们想根据某个值对数据进行分类就可以用partitionBy,方法的返回的boolean值作为key,满足条件的值合并成list作为value。如果不想得到list还以在后面再加一个collector。

    public Map<Boolean, Integer> bandsAndSolo(Stream<Artist> artists) { 
    	return artists.collect(partitioningBy(artist -> artist.isSolo(), list -> list.size()));
    }
    

    这样改一下就是把满足条件的值的数量作为value。

    另一种是:groupingBy

        public Map<Boolean, List<String>> bandsAndSolo(Stream<String> artists) {
            return artists.collect(partitioningBy(artist -> artist.isSolo(), artist.size()));
        }
    

    不同点在于partitioningBy只能把boolean作为key,而且永远只有两个entry,效率要高一点。而groupingBy可以把任意对象作为key。

    总结

    Java8加入的lambda表达式和stream库,可以让我们很方便的操作集合,聚合数据等等,熟练使用的话能很好提高编程效率,同时也提高了代码的可读性。当然它也有缺点,比如不方便debug,有一定的学习成本。

  • 相关阅读:
    Python中的类(上)
    Django REST Framework API Guide 07
    Django REST Framework API Guide 06
    Django REST Framework API Guide 05
    Django REST Framework API Guide 04
    Django REST Framework API Guide 03
    Django REST Framework API Guide 02
    Django REST Framework API Guide 01
    Django 详解 信号Signal
    Django 详解 中间件Middleware
  • 原文地址:https://www.cnblogs.com/ekoeko/p/9610718.html
Copyright © 2011-2022 走看看