深度分析java8的新特性lambda和stream流看完你学会了吗

43次阅读

共计 9320 个字符,预计需要花费 24 分钟才能阅读完成。

1. lambda 表达式

1.1 什么是 lambda

以 java 为例,能够对一个 java 变量赋一个值,比方 int a = 1,而对于一个办法,一块代码也是赋予给一个变量的,对于这块代码,或者说被赋给变量的函数,就是一个 lambda 表达式

// 为变量赋值
int a = 1;

// 将代码块赋值给变量
var = public void fun(int x){x+1;}

// 能够简化
var = (x)->x+1;

1.2 java 为什么要引入 lambda

lambda 是为函数式编程服务的
编程语言共性之 —— 什么是函数式编程?
函数式编程是一种编程范式,也就是如何编写程序的方法论,次要思维是把运算过程尽量编写成一系列嵌套的函数调用,FP 强调“everything is lambda”,并且强调在逻辑解决中不变性的重要性

OOP 强调“everything is object”,以及 object 之间的消息传递。通过消息传递扭转每个 Object 的外部状态,然而很多状况代码的编写实际上是用不到对象的,比方,对一组数据做加工,先查问,而后聚合,聚合后排序,再 join,再排序,再聚合,再转换(map)失去最终的后果。这个过程,用 FP 的函数就很天然

result = func1(func2(func3...funcN(x))))

java 为了在原先 oop 的思维上减少函数式编程的应用,在 java8 上减少了 lambda 函数的新个性

除此之外,lambda 表达式的引入还使得代码更为简洁,能够防止生成过多的污染环境的无用实现类(上面说)

1.3 如何应用 lambda 表达式

lambda 表达式的引入能够防止生成过多的污染环境的实现类;
lambda 表达式能够被赋值给一个变量,那么这个变量的类型是什么?
在 java 中,所有的 Lambda 的类型都是一个接口,而 Lambda 表达式自身,须要是这个接口的实现,这个接口须要具备三个特色,具备这些特色的接口叫做函数式接口

函数式接口只有一个形象办法
default 办法为默认实现,不计入形象办法
如果接口申明了一个笼罩 java.lang.Object 的全局办法之一的形象办法,那么它不会计入接口的形象办法数量中,因为接口的任何实现都将具备 java.lang.Object 或其余中央的实现
如何应用 lambda 表达式
比方 Comparator 接口就是一个函数式接口,所以他能够应用 lambda 表达式,在之前应用 comparator 对一个 list 排序是上面这样的

List<Integer> list = new ArrayList<>();
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {return o1-o2;}
});

能够看到下面理论真正有用的是 return o1 – o2,下面的代码应用 lambda 表达式写如下

Collections.sort(list, ((o1, o2) -> o1-o2));

Lambda 表达式的根底语法:Lambda 操作符 -> 将 Lambda 表达式拆分成两局部:
左侧:Lambda 表达式的参数列表;
右侧:Lambda 表达式中所需执行的性能,即 Lambda 体;

语法格局一:无参数,无返回值
() -> System.out.println("Hello Lambda!");

语法格局二:有一个参数,并且无返回值
(x) -> System.out.println(x)

语法格局三:若只有一个参数,小括号能够省略不写
x -> System.out.println(x)

语法格局四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator<Integer> com = (x, y) -> {System.out.println("函数式接口");
return Integer.compare(x, y);
};

语法格局五:若 Lambda 体中只有一条语句,return 和 大括号都能够省略不写
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

1.4 lambda 表达式办法援用,结构器援用和数组援用

办法援用
若 Lambda 体中的性能,曾经有办法提供了实现,能够应用办法援用

对象的援用 :: 实例办法名
类名 :: 静态方法名
类名 :: 实例办法名

①办法援用所援用的办法的参数列表与返回值类型,须要与函数式接口中形象办法的参数列表和返回值类型保持一致!
②若 Lambda 的参数列表的第一个参数,是实例办法的调用者,第二个参数 (或无参) 是实例办法的参数时,格局:ClassName::MethodName

// 对象的援用 :: 实例办法名
@Test
public void test1(){
        // 之前咱们是这样写的
    Employee emp = new Employee(101, "张三", 18, 9999);
    Supplier<String> sup = () -> emp.getName();
    System.out.println(sup.get());  

    System.out.println("----------------------------------");   
        // 当初咱们是这样写的
    Supplier<String> sup2 = emp::getName;
    System.out.println(sup2.get());
}

// 类名 :: 静态方法名
@Test
public void test2(){Comparator<Integer> com = (x, y) -> Integer.compare(x, y);  
    System.out.println("-------------------------------------");     
    Comparator<Integer> com2 = Integer::compare;
}

// 类名 :: 实例办法名
@Test
public void test3(){BiPredicate<String, String> bp = (x, y) -> x.equals(y);
    System.out.println(bp.test("abcde", "abcde"));
        
    System.out.println("-----------------------------------------");
        
    BiPredicate<String, String> bp2 = String::equals;
    System.out.println(bp2.test("abc", "abc"));

}

结构器援用

对于 person 类,有两个结构器

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

当初有一个工厂接口用来生成 person 类

// Person 工厂
interface PersonFactory<P extends Person> {P create(String firstName, String lastName);
}

咱们能够通过 :: 关键字来援用 Person 类的结构器,来代替手动去实现这个工厂接口:

// 间接援用 Person 结构器
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

Person::new 这段代码,可能间接援用 Person 类的结构器。而后 Java 编译器可能依据上下文选中正确的结构器去实现 PersonFactory.create 办法

2.1 什么是 Stream

Java 8 引入了全新的 Stream API,这里的 Stream 和 I / O 流不同,Java 8 中的 Stream 是对汇合(Collection)对象性能的加强,它专一于对汇合对象进行各种十分便当、高效的聚合操作,或者大批量数据操作,Stream API 借助于同样新呈现的 Lambda 表达式,极大的进步编程效率和程序可读性

Stream 就如同一个迭代器(Iterator),单向,不可往返,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从背后流过,一去不复返

List<String> myList =
    Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList
    .stream() // 创立流
    .filter(s -> s.startsWith("c")) // 执行过滤,过滤出以 c 为前缀的字符串
    .map(String::toUpperCase) // 转换成大写
    .sorted() // 排序
    .forEach(System.out::println); // for 循环打印

①:两头操作会再次返回一个流,所以,咱们能够链接多个两头操作,留神这里是不必加分号的。上图中的 filter 过滤,map 对象转换,sorted 排序,就属于两头操作。②:终端操作是对流操作的一个完结动作,个别返回 void 或者一个非流的后果。上图中的 forEach 循环 就是一个终止操作

下面是 Stream 的简略实用,能够看出它也是函数式编程,更多的表白了业务逻辑

2.2 罕用 api

创立 Stream

1. Arrays.stream()

当在日常编程中面对的是一个数组,能够应用 Arrays.stream()办法来应用 Stream

Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
long count = Arrays.stream(array).filter(i->i>20).count();

2. Stream.of()

当面对数组时除了能够应用 Arrays.stream()办法外,还能够应用 Stream 将须要的数组转成 Stream。这个办法岂但反对传入数组,将数组转成 Stream,也反对传入多个参数,将参数最终转成 Stream

Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
long count = Stream.of(array).filter(i->i>20).count();
long sum = Stream.of(12,77,59,3,654).filter(i->i>20).mapToInt(Integer::intValue).sum();
System.out.println("count:"+count+",sum:"+sum);

3. Collection.stream()

这个就是最常见的 Stream 了。因为 Collection 是 Java 中汇合接口的父接口,Java 中的汇合都继承或实现了此接口。所以 Java 中的汇合都能够应用此办法来创立一个 Stream

List<Integer> numbers = new ArrayList<>();
numbers.add(3);
numbers.add(4);
numbers.add(8);
numbers.add(16);   
numbers.stream().forEach(number->{System.out.println(number);
});

4.filter

这是一个 Stream 的过滤转换,此办法会生成一个新的流,其中蕴含合乎某个特定条件的所有元素,filter 承受一个函数作为参数,该函数用 Lambda 表达式示意

List<Integer> integerList = Lists.newArrayList();
integerList.add(15);
integerList.add(32);
integerList.add(5);
integerList.add(232);
integerList.add(56);
List<Integer> after = integerList.stream()
                    .filter(i->i>50)
                    .collect(Collectors.toList());
System.out.println(after);//232,56

5.map

map 办法指对一个流中的值进行某种模式的转换。须要传递给它一个转换的函数作为参数

List<Integer> integerList = Lists.newArrayList();
integerList.add(15);
integerList.add(32);
integerList.add(5);
integerList.add(232);
integerList.add(56);
// 将 Integer 类型转换成 String 类型
List<String> afterString = integerList.stream()
                .map(i->String.valueOf(i)).collect(Collectors.toList());

6.flatMap

将多个 Stream 连接成一个 Stream,这时候不是用新值取代 Stream 的值,与 map 有所区别,这是从新生成一个 Stream 对象取而代之

List<String> words = new ArrayList<String>();
words.add("your");
words.add("name");


public static Stream<Character> characterStream(String s){List<Character> result = new ArrayList<>();  
    for (char c : s.toCharArray()) 
        result.add(c);
    return result.stream();}
  
Stream<Stream<Character>> result = words.map(w -> characterStream(w));  
//[['y', 'o', 'u', 'r'], ['n', 'a', 'm', 'e']]
Stream<Character> letters = words.flatMap(w -> characterStream(w));
//['y', 'o', 'u', 'r', 'n', 'a', 'm', 'e'] 

7.limit 办法和 skip 办法

limit(n)办法会返回一个蕴含 n 个元素的新的流(若总长小于 n 则返回原始流)
skip(n)办法正好相同,它会抛弃掉后面的 n 个元素
用 limit 和 skip 办法一起应用就能够实现日常的分页性能:

List<Integer> pageList = myList.stream()
                  .skip(pageNumber*pageSize)
                  .limit(pageSize).collect(Collectors.toList());

8.distinct 办法和 sorted 办法

distinct 办法会依据原始流中的元素返回一个具备雷同程序、去除了反复元素的流,这个操作显然是须要记住之前读取的元素。

List<Integer> myTestList = Lists.newArrayList();
myTestList.add(10);
myTestList.add(39);
myTestList.add(10);
myTestList.add(78);
myTestList.add(10);
List<Integer> distinctList = myTestList.stream()
                        .distinct().collect(Collectors.toList());
System.out.println("distinctList:"+distinctList);
运行后果:distinctList:[10, 39, 78]

sorted 办法是须要遍历整个流的,并在产生任何元素之前对它进行排序。因为有可能排序后汇合的第一个元素会在未排序汇合的最初一位。

List<Integer> myTestList = Lists.newArrayList();
myTestList.add(39);
myTestList.add(78);
myTestList.add(10);
myTestList.add(22);
myTestList.add(56);
List<Integer> sortList = myTestList.stream()
                .sorted(Integer::compareTo).collect(Collectors.toList());
System.out.println("sortList:"+sortList);
运行后果:sortList:[10, 22, 39, 56, 78]

9.Collect

collect 在流中生成列表,map,等罕用的数据结构

将一个流收集到一个 List 中,只须要这样写就能够。List<Integer> thereList = hereList.stream().collect(Collectors.toList());

收集到 Set 中能够这样用
Set<Integer> thereSet = hereList.stream().collect(Collectors.toSet());

收集到 Set 时,管制 Set 的类型,能够这样。TreeSet<Integer> treeSet = hereList.stream()
                    .collect(Collectors.toCollection(TreeSet::new));

10. 聚合操作

聚合是指将流汇聚为一个值,以便在程序中应用。聚合办法都是终止操作,聚合办法包含 sum,count,max,min

long sum = Stream.of(12,77,59,3,654).filter(i->i>20).mapToInt(Integer::intValue).sum();
findFirst 办法返回非空集合中的第一个值,它通常与 filter 办法联合起来应用
Integer first = hearList.stream().filter(i->i>100).findFirst().get();

findAny 办法能够在汇合中只有找到任何一个所匹配的元素,就返回,此办法在对流并行执行时非常无效
Integer anyItem = hearList.parallelStream().filter(i->i>100).findAny().get();

11. 分组

对具备雷同个性的值进行分组是一个很常见的性能

将一个 Room 对象汇合依照高度分组。List<Room> roomList = Lists.newArrayList(new Room(11,23,56),
new Room(11,84,48),
new Room(22,46,112),
new Room(22,75,62),
new Room(22,56,75),
new Room(33,92,224));

Map<Integer,List<Room>> groupMap = roomList.stream().collect(Collectors.groupingBy(Room::getHigh));
System.out.println("groupMap:"+groupMap);

2.3 Stream 流的解决程序

Stream 流的两头操作具备提早性,当且仅当存在终端操作时,两头操作才会被执行

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {System.out.println("filter:" + s);
        return true;
    });

执行此代码段时,不会打印任何内容,对下面的代码增加 forEach 终端操作,就有打印内容了

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {System.out.println("filter:" + s);
        return true;
    })
    .forEach(s -> System.out.println("forEach:" + s));
filter:  d2
forEach: d2
filter:  a2
forEach: a2
filter:  b1
forEach: b1
filter:  b3
forEach: b3
filter:  c
forEach: c

然而能够看到输入后果并不是先将所有 filter 操作的打印语句打印进去;事实上,输入的后果却是随着链条垂直挪动的,比如说,当 Stream 开始解决 d2 元素时,它实际上会在执行完 filter 操作后,再执行 forEach 操作,接着才会解决第二个元素

起因是出于性能的思考。这样设计能够缩小对每个元素的理论操作数,比方上面操作

Stream.of("d2", "a2", "b1", "b3", "c")
    .map(s -> {System.out.println("map:" + s);
        return s.toUpperCase(); // 转大写})
    .anyMatch(s -> {System.out.println("anyMatch:" + s);
        return s.startsWith("A"); // 过滤出以 A 为前缀的元素
    });

// map:      d2
// anyMatch: D2
// map:      a2
// anyMatch: A2

终端操作 anyMatch()示意任何一个元素以 A 为前缀,返回为 true,就进行循环。所以它会从 d2 开始匹配,接着循环到 a2 的时候,返回为 true,于是进行循环。

因为数据流的链式调用是垂直执行的,map 这里只须要执行两次。绝对于程度执行来说,map 会执行尽可能少的次数,而不是把所有元素都 map 转换一遍

stream --> filter --> map --> sorted --> collect

2.4 并行流

和迭代器不同的是,Stream 能够并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当应用串行形式去遍历时,每个 item 读完后再读下一个 item;
Stream 具备平行解决能力,解决的过程会分而治之,也就是将一个大工作切分成多个小工作,这示意每个工作都是一个操作

//parallel 办法能够将任意的串行流转换为一个并行流
Stream.of(roomList).parallel();
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.parallelStream()
       .forEach(out::println);  
// 展现程序不肯定会是 1、2、3、4、5、6、7、8、9,而可能是任意的程序

最初

大家看完有什么不懂的欢送在下方留言探讨

正文完
 0