Java8之使用Stream流

第四章 引入流(stream)

1、基础概念




java流处理filter java流使用_java


1)流和集合
  • 流是按需加载的,就像流媒体视频一样,是按需下载播放的
  • 集合是急切创建的,所有都放在内存


java流处理filter java流使用_java_02


2)流只能遍历一次
  • 遍历完之后,我们就说这个流已经被消费掉了。
  • 例如,以下代码会抛出一个异常,说流已被消 费掉了:


java流处理filter java流使用_stream_03


集合和流的另一个关键区别在于它们遍历数据的方式

3)外部迭代和内部迭代
  • 外部迭代(用for-each)


java流处理filter java流使用_java_04


  • 流:内部迭代


java流处理filter java流使用_java流处理filter_05


List<String> names = menu.stream()
                         .map(Dish::getName)
                         .collect(toList());
  • 帮助理解区别
  • 以下是for-each迭代

你:“索菲亚,我们把玩具收起来吧。地上还有玩具吗?”
索菲亚:“有,球。”
你:“好,把球放进盒子里。还有吗?”

索菲亚:“有,那是我的娃娃。”
你:“好,把娃娃放进盒子里。还有吗?”
索菲亚:“有,有我的书。”
你:“好,把书放进盒子里。还有吗?”
索菲亚:“没了,没有了。”
你:“好,我们收好啦。”

  • 以下是流迭代

你:“索菲亚,我们把玩具收起来吧。把你身边的玩具都给我”
索菲亚:“好的。”
你:“好,我们收好啦。”

4)流操作


java流处理filter java流使用_stream_06


  • filter、map和limit可以连成一条流水线;
  • collect触发流水线执行并关闭它。关闭流的操作称为终端操作。


java流处理filter java流使用_java流处理filter_07


第五章 使用流

1、筛选与切片

1)谓词筛选-filter
  • Streams接口支持filter方法(你现在应该很熟悉了)。该操作会接受一个谓词(一个返回 boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。
  • 例如
List<Dish> vegetarianMenu = 
menu.stream().filter(Dish::isVegetarian).collect(toList());
2)筛选各异的元素(其实就是去重)-distinct
  • 例如-筛选出列表中所有的偶数,并确保没有 重复
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
         .filter(i -> i % 2 == 0)
         .distinct()
         .forEach(System.out::println);
3)截短流(其实就类似于mysql的分页)-limit
  • 例如,选出了符合谓词的头三个元素, 然后就立即返回了结果。
List<Dish> dishes = menu.stream()
                         .filter(d -> d.getCalories() > 300)
                         .limit(3)
                         .collect(toList());
4)跳过元素-skip- 和limit互补
  • skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一 个空流。请注意,limit(n)和skip(n)是互补的!
  • 例如,下面的代码将跳过超过300卡路里的头 两道菜,并返回剩下的
List<Dish> dishes = menu.stream()
                         .filter(d -> d.getCalories() > 300)
                         .skip(2)
                         .collect(toList());

2、映射

1)流的扁平化-flatMap
  • 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所 有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流
List<String> uniqueCharacters =
     words.stream()
             .map(w -> w.split(""))
             .flatMap(Arrays::stream)
             .distinct()
             .collect(Collectors.toList());
  • 和map对比图


java流处理filter java流使用_java_08


  • flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接 起来成为一个流。

3、查找和匹配

1)anyMatch-流中是否有一个元素能匹配给定的谓词
if(menu.stream().anyMatch(Dish::isVegetarian)){
	 System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
2)allMatch-看流中的元素是否都能匹配给定的谓词
3)noneMatch-和allMatch相对,即都不能匹配
4)findAny-返回当前流中的任意元素
  • 它可以与其他流操作结合使用。
  • 可以结合使用filter和findAny方法来实现查询
Optional<Dish> dish =
                     menu.stream()
                     .filter(Dish::isVegetarian)
                     .findAny();
  • Optional返回值

 isPresent()将在Optional包含值的时候返回true, 否则返回false。

 ifPresent(Consumer block)会在值存在的时候执行给定的代码块。它让你传递一个接收T类型参数,并返回void的Lambda 表达式。

 T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。

 T orElse(T other)会在值存在时返回值,否则返回一个默认值。


java流处理filter java流使用_java流处理filter_09


5)findFirst-查找第一个元素
  • 给定一个数字列表,下面的代码能找出第一个平方 能被3整除的数:
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

4、归约-reduce

1)元素求和
  • Java7 写法
int sum = 0;
for (int x : numbers) {
 sum += x;
}
  • java8写法
private static void useReduce() {
        logger.info("[useReduce]用法");
        List<Integer> nums = Arrays.asList(1,2,3,6);
        //int sum = nums.parallelStream().reduce(0,(a,b) -> a + b);
  			int sum = nums.parallelStream().reduce(0,Integer::sum);
        int product = nums.parallelStream().reduce(1,(a,b) -> a * b);
        System.out.println(sum);
        System.out.println(product);
    }


java流处理filter java流使用_lambda_10


2) 最大值和最小值
Optional<Integer> max = numbers.stream().reduce(Integer::max); 
Optional<Integer> min = numbers.stream().reduce(Integer::min);


java流处理filter java流使用_stream_11


3) 算出集合中有多少元素
Integer count = nums.parallelStream()
                .map(d -> 1)
                .reduce(0, Integer::sum);
        System.out.println(count);
  • 可以把流中每个元素都映射成数字1,然后用reduce求和
4) 总结


java流处理filter java流使用_stream_12


5、付诸实践

  • 练习

(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。

(2) 交易员都在哪些不同的城市工作过?

(3) 查找所有来自于剑桥的交易员,并按姓名排序。

(4) 返回所有交易员的姓名字符串,按字母顺序排序。

(5) 有没有交易员是在米兰工作的?

(6) 打印生活在剑桥的交易员的所有交易额。

(7) 所有交易中,最高的交易额是多少? (8) 找到交易额最小的交易。

  • Traders和Transactions列表
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario","Milan");
Trader alan = new Trader("Alan","Cambridge");
Trader brian = new Trader("Brian","Cambridge");
List<Transaction> transactions = Arrays.asList(
 new Transaction(brian, 2011, 300),
 new Transaction(raoul, 2012, 1000),
 new Transaction(raoul, 2011, 400),
 new Transaction(mario, 2012, 710),
 new Transaction(mario, 2012, 700),
 new Transaction(alan, 2012, 950)
);
  • Trader和Transaction类
public class Trader{
   private final String name;
   private final String city;
   public Trader(String n, String c){
       this.name = n;
       this.city = c;
   }
   public String getName(){
   		return this.name;
   }
   public String getCity(){
   		return this.city;
   } 
    public String toString(){
   		return "Trader:"+this.name + " in " + this.city;
   }
}
public class Transaction{
 private final Trader trader;
 private final int year;
 private final int value;
 public Transaction(Trader trader, int year, int value){
     this.trader = trader;
     this.year = year;
     this.value = value;
 }
 public Trader getTrader(){
 		return this.trader;
 }
 public int getYear(){
 		return this.year;
 }
 public int getValue(){
 		return this.value;
 }
 public String toString(){
     return "{" + this.trader + ", " +
     "year: "+this.year+", " +
     "value:" + this.value +"}";
     }
}


java流处理filter java流使用_java_13


java流处理filter java流使用_lambda_14


java流处理filter java流使用_python_15


java流处理filter java流使用_java流处理filter_16


java流处理filter java流使用_java_17


java流处理filter java流使用_python_18


java流处理filter java流使用_lambda_19


java流处理filter java流使用_lambda_20


java流处理filter java流使用_stream_21


6、数值流

6.1 原始类型流特化
1、映射到数值流


java流处理filter java流使用_stream_22


这里,mapToInt会从每道菜中提取热量(用一个Integer表示),并返回一个IntStream (而不是一个Stream)。然后你就可以调用IntStream接口中定义的sum方法,对卡 路里求和了!请注意,如果流是空的,sum默认返回0。IntStream还支持其他的方便方法,如 max、min、average等。

2、转换回对象流

一旦有了数值流,你可能会想把它转换回非特化流。例如,IntStream上的操作只能 产生原始整数: IntStream 的 map 操作接受的 Lambda 必须接受 int 并返回 int (一个 IntUnaryOperator)。但是你可能想要生成另一类值,比如Dish。为此,你需要访问Stream 接口中定义的那些更广义的操作。要把原始流转换成一般流(每个int都会装箱成一个 Integer),可以使用boxed方法


java流处理filter java流使用_python_23


3、默认值OptionalInt

求和的那个例子很容易,因为它有一个默认值:0。但是,如果你要计算IntStream中的最 大元素,就得换个法子了,因为0是错误的结果。如何区分没有元素的流和最大值真的是0的流呢? Optional可以用 Integer、String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类 型特化版本:OptionalInt、OptionalDouble和OptionalLong。

  • 要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:
OptionalInt maxCalories = menu.stream()
 .mapToInt(Dish::getCalories)
 .max(); 
//如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:
int max = maxCalories.orElse(1);
6.2 数值范围

假设你想要生成1和100之间的所有数字。Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围: range和rangeClosed。


java流处理filter java流使用_java_24


6.3 数值流应用:勾股数
Stream<int[]> pythagoreanTriples =
                 IntStream.rangeClosed(1, 100).boxed()
                           .flatMap(a ->
                                    IntStream.rangeClosed(a, 100)
                                   					 .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
                                    					.mapToObj(b ->
                                              				new int[]{a, b, (int)Math.sqrt(a * a + b * b)})
       																);

pythagoreanTriples.limit(5)
                  .forEach(t ->
                 					System.out.println(t[0] + ", " + t[1] + ", " + t[2]));

7、构建流

7.1 由值创建流-Stream.of/empty
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println); 

Stream<String> emptyStream = Stream.empty();
7.2 由数组创建流-Arrays.stream
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
//这里stream里面必须放int[]才能调出sum()方法
7.3 由文件生成流java.nio.file.Files
  • 看一个文件中有多少各不相同的词
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) {

        }

你可以使用Files.lines得到一个流,其中的每个元素都是给定文件中的一行。然后,你 可以对line调用split方法将行拆分成单词。应该注意的是,你该如何使用flatMap产生一个扁 平的单词流,而不是给每一行生成一个单词流。最后,把distinct和count方法链接起来,数 数流中有多少各不相同的单词。

7.4 由函数生成流:创建无限流
1、迭代-Stream.iterate
Stream.iterate(0, n -> n + 2)
 .limit(10)
 .forEach(System.out::println);
  • 一般来说,在需要依次生成一系列值的时候应该使用iterate,比如一系列日期:1月31日, 2月1日,依此类推。
  • 测验5.4:斐波纳契元组序列

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55…

Stream.iterate(new int[]{0, 1},
                t -> new int[]{t[1],t[0] + t[1]})
                .limit(10)
                .map(t -> t[0])
                .forEach(System.out::println);
2、生成-Stream.generate
Stream.generate(Math::random)
                .limit(5)
                .forEach(System.out::println);

Q.E.D.