一、筛选和切片

1.用谓词筛选

Streams接口支持filter方法。该操作会接受一个谓词(一个返回
boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。例如筛选出所有素菜,创建一张素食菜单:

List<Dish> vegetarianMenu = menu.stream() 
 .filter(Dish::isVegetarian) 
 .collect(toList());

2.筛选各异的元素

流还支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的
hashCode和equals方法实现)的流。例如,以下代码会筛选出列表中所有的偶数,并确保没有
重复。

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); 
numbers.stream() 
 .filter(i -> i % 2 == 0) 
 .distinct() 
 .forEach(System.out::println);
hashcode( )和equals( )
1.Java中的hashCode()的作用
hashCode()的作用是为了提高在散列结构存储中查找的效率,在线性表中没有作用;只有每个对象的 hash 码尽可能不同才能保证散列的存取性能,事实上 Object 类提供的默认实现确实保证每个对象的 hash 码不同(在对象的内存地址基础上经过特定算法返回一个 hash 码)。在 Java 有些集合类(HashSet)中要想保证元素不重复可以在每增加一个元素就通过对象的 equals 方法比较一次,那么当元素很多时后添加到集合中的元素比较的次数就非常多了,也就是说如果集合中现在已经有 3000 个元素则第 3001 个元素加入集合时就要调用 3000 次 equals 方法,这显然会大大降低效率,于是 Java 采用了哈希表的原理,这样当集合要添加新的元素时会先调用这个元素的 hashCode 方法就一下子能定位到它应该放置的物理位置上(实际可能并不是),如果这个位置上没有元素则它就可以直接存储在这个位置上而不用再进行任何比较了,如果这个位置上已经有元素了则就调用它的 equals 方法与新元素进行比较,相同的话就不存,不相同就散列其它的地址,这样一来实际调用 equals 方法的次数就大大降低了,几乎只需要一两次,而 hashCode 的值对于每个对象实例来说是一个固定值。
2.Java中重写equals()方法时尽量要重写hashCode()方法的原因
当 equals 方法被重写时通常有必要重写 hashCode 方法来维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码,如果不这样做的话就会违反 hashCode 方法的常规约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括 HashMap、HashSet、Hashtable 等

引申:HashMap实现原理及源码分析
注意:哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式

3.截短流

流支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。比如,你可以建立一个List,选出热量超过300卡路里的头三道菜:

List<Dish> dishes = menu.stream() 
 .filter(d -> d.getCalories() > 300) 
 .limit(3) 
 .collect(toList()); 

请注意limit也可以用在无序流上,比如源是一个Set。这种情况下,limit的结果不会以任何顺序排列。

4.跳过元素

流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。

List<Dish> dishes = menu.stream() 
 .filter(d -> d.getCalories() > 300) 
 .skip(2) 
 .collect(toList());

二、映射

1.对流中每一个元素应用函数

提取流中菜肴的名称:

List<String> dishNames = menu.stream() 
  .map(Dish::getName) 
  .collect(toList());

2.流的扁平化

List<String> uniqueCharacters = 
 words.stream() 
 .map(w -> w.split("")) 
 .flatMap(Arrays::stream) 
 .distinct() 
 .collect(Collectors.toList()); 

使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。

三、查找和匹配

1.检查谓词是否至少匹配一个元素

anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。比如,你可以用它来看看菜单里面是否有素食可选择:

if(menu.stream().anyMatch(Dish::isVegetarian)){ 
 System.out.println("The menu is (somewhat) vegetarian friendly!!"); 
}
anyMatch方法返回一个boolean,是一个终端操作

2.检查谓词是否匹配所有元素

allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词

boolean isHealthy = menu.stream() 
 .allMatch(d -> d.getCalories() < 1000);

noneMatch和allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。比如,
你可以用noneMatch重写前面的例子:

boolean isHealthy = menu.stream() 
 .noneMatch(d -> d.getCalories() >= 1000);
anyMatch、allMatch和noneMatch这三个操作都用到了我们所谓的短路

3.查找元素

findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用。比如,你可能想
找到一道素食菜肴。你可以结合使用filter和findAny方法来实现这个查询:

Optional<Dish> dish = 
 menu.stream() 
 .filter(Dish::isVegetarian) 
 .findAny();

Optional简介
Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面的代码中,findAny可能什么元素都没找到。Java 8的库设计人员引入了Optional<T>,这样就不用返回众所周知容易出问题的null了。
Optional里面几种可以迫使你显式地检查值是否存在或处理值不存在的情形的方法也不错。

  • isPresent()将在Optional包含值的时候返回true, 否则返回false。
  • ifPresent(Consumer<T> block)会在值存在的时候执行给定的代码块。
  • T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
  • T orElse(T other)会在值存在时返回值,否则返回一个默认值。

例如,在前面的代码中你需要显式地检查Optional对象中是否存在一道菜可以访问其名称:

menu.stream() 
.filter(Dish::isVegetarian) 
.findAny() 
.ifPresent(d -> System.out.println(d.getName());

4.查找第一个元素

使用findFirst()方法

List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5); 
Optional<Integer> firstSquareDivisibleByThree = 
 someNumbers.stream() 
 .map(x -> x * x) 
 .filter(x -> x % 3 == 0) 
 .findFirst(); // 9

四、归约

1.元素求和(多归一)

  • 有初始值

    int sum = numbers.stream().reduce(0, (a, b) -> a + b); 

    reduce接受两个参数:
    (1) 一个初始值,这里是0;
    (2) 一个BinaryOperator<T>来将两个元素结合起来产生一个新值,这里我们用的是lambda (a, b) -> a + b。

  • 无初始值
    reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象:

    Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b)); 
    为什么它返回一个Optional<Integer>呢?
    考虑流中没有任何元素的情况。reduce操作无返回其和,因为它没有初始值。这就是为什么结果被包裹在一个Optional对象里,以表明和可能不存在。

2.最大值和最小值

eg:

Optional<Integer> max = numbers.stream().reduce(Integer::max);//最大值
Optional<Integer> min = numbers.stream().reduce(Integer::min);//最小值
map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名,因为它很容易并行化。
诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果,这些操作一般都是无状态的;但诸如reduce、sum、max等操作需要内部状态来累积结果,我们把这些操作叫作有状态操作

五、数值流

1.原始类型流特化

1.1 映射到数值流

Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max,以及min、average等。
可以像下面这样用mapToInt对menu中的卡路里求和:

int calories = menu.stream() 
 .mapToInt(Dish::getCalories) 
 .sum();
请注意,如果流是空的,sum默认返回0。

1.2 转换回对象流

要把原始流转换成一般流(每个int都会装箱成一个Integer),可以使用boxed方法,如下所示:

IntStream intStream = menu.stream().mapToInt(Dish::getCalories); 
Stream<Integer> stream = intStream.boxed();

1.3 默认值OptionalInt

求和的那个例子很容易,因为它有一个默认值:0。但是,如果你要计算IntStream中的最大元素,就得换个法子了,因为0是错误的结果。对于三种原始流特化,分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong。

例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:

OptionalInt maxCalories = menu.stream() 
 .mapToInt(Dish::getCalories) 
 .max(); 

现在,如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:

int max = maxCalories.orElse(1);

2.数值范围

假设你想要生成1和100之间的所有数字。Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:
range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但
range是不包含结束值的,而rangeClosed则包含结束值。

IntStream evenNumbers = IntStream.rangeClosed(1, 100) 
 .filter(n -> n % 2 == 0); 
System.out.println(evenNumbers.count());

六、构建流

1.由值创建流

你可以使用静态方法Stream.of,通过显式值创建一个流。它可以接受任意数量的参数。例如,以下代码直接使用Stream.of创建了一个字符串流。然后,你可以将字符串转换为大写,再一个个打印出来:

Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action"); 
stream.map(String::toUpperCase).forEach(System.out::println); 

你可以使用empty得到一个空流,如下所示:

Stream<String> emptyStream = Stream.empty();

2.由数组创建流

int[] numbers = {2, 3, 5, 7, 11, 13}; 
int sum = Arrays.stream(numbers).sum();

3.由文件生成流

Files.lines,它会返回一个由指定文件中的各行构成的字符串流。

用这个方法看看一个文件中有多少各不相同的词:

long uniqueWords = 0; 
try(Stream<String> lines = 
 Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){ 
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
 .distinct() 
 .count(); 
} catch(IOException e){ 
 //如果打开文件时出现异常则加以处理
}

4.由函数生成流:创建无限流

Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。

4.1 迭代

Stream.iterate(0, n -> n + 2) 
 .limit(10) 
 .forEach(System.out::println); 
iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator<t>类型)。这里,我们使用Lambda n -> n + 2,返回的是前一个元素加上2。

4.2 生成

与iterate方法类似,generate方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一Supplier<T>类型的Lambda提供新的值。我们先来
看一个简单的用法:

Stream.generate(Math::random) 
 .limit(5) 
 .forEach(System.out::println); 

这段代码将生成一个流,其中有五个0到1之间的随机双精度数。


小白龙
56 声望6 粉丝