Java8流式编程笔记(1)--流创建

237 阅读5分钟

Java8流式编程(1)--流创建

一. 简介

流式编程在Java8中是一个全新的内容,结合前面的函数式编程,在处理集合对象时能让我们编写更加短小且更易理解的代码。

从最初的for循环迭代器,到Java5提供的foreach循环(基于迭代器的遍历方式,也是Java8之前我们使用地最多的循环遍历方式),去下标的书写方式简化了我们对集合、数组的遍历操作。这类方式被称为外部迭代

而Java8中的流是支持一系列与特定存储机制无关的顺序和并行聚集操作的元素序列。利用流,我们无需迭代集合中的元素,就可以提取和操作它们,这些管道通常被组合在一起,在流上形式一条操作管道。流式编程采用内部迭代

A sequence of elements supporting sequential and parallel aggregate operations.

二. 学习方法

阅读《OnJava8》第十四章 流式编程,部分细节结合具体的JDK文档进行理解。

三. 相关概念

1. 声明式编程(Declarative Programming)

声明式编程的主要思想是声明要做什么,但不指定具体要怎么做。

代码如下(随机展示5至20中不重复的整数并进行排序):

public static void main(String[] args) {
    new Random(47)
        .ints(5, 20)
        .distinct()
        .limit(7)
        .sorted()
        .forEach(System.out::println);
}

2. 命令式编程(Imperative Programming)

命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么。

代码如下:

public static void main(String[] args) {
    Random rand = new Random(47);
    SortedSet<Integer> rints = new TreeSet<>();
    while(rints.size() < 7) {
        int r = rand.nextInt(20);
        if(r < 5) continue;
        rints.add(r);
    }
    System.out.println(rints);
}

对比:命令式编程的形式更难以理解,你必须研究程序的真正意图,而声明式编程的风格去掉实现的细节,让流程看起来更加清晰,可读性更强。

四. 关键点

1. 流创建

使用Stream类提供的各类方法可以很方便完成一些操作,各种创建方式如下:

  • Stream#of(T... values):将一组元素转化为流,相比于写成数组或集合再通过foreach循环来遍历的方式方便很多
public static void main(String[] args) {
    Stream.of("伞兵一号", "卢本伟", "准备就绪.")
                    .forEach(System.out::print);
}
  • java.util.Collection#stream():从当前集合获取一个拥有当前集合中所有元素的流对象。
public static void main(String[] args) {
 Arrays.asList("lbw", " ", "nb")
         .stream()
         .forEach(System.out::print);

 System.out.println("-----------");

 Map<String, String> map = new HashMap<>();
 map.put("who", "我是谁");
 map.put("where", "我在哪儿");
 map.put("what", "我在干嘛");
 // 直接对Map进行遍历
 map.forEach((key, value) -> System.out.println(key + ":" + value));
 System.out.println("===========");
 // 传统的方式, 对EntrySet进行遍历
 map.entrySet()
         .stream()
         .map(entry -> entry.getKey() + ":" + entry.getValue())
         .forEach(System.out::println);
}
  • 随机数流: Random类提供的ints()/longs()/doubles()可以生成int/long/double类型和流(IntStream/LongStream/DoubleStream),再通过boxed()方法将其转换为对应的包装类型。

    // 三参数方法, 指定流的大小和界限
    new Random(47).ints(3, 3, 9).boxed()
        .limit(4)
            .forEach(System.out::println);
    
  • IntStream#range():用于生成指定范围整型序列的流。编写循环时,这个方法会更加方便,但遗憾的是它不支持指定步长。

public static void main(String[] args) {
    // 传统方式
    int result = 0;
    for (int i = 1; i < 101; i++) {
   		result += i;
    }
    System.out.println(result);
    // 使用流
    System.out.println(IntStream.range(1, 101).sum());
}
  • Stream#generate():通过该静态方法可以把任意Supplier函数式接口用于生成T类型的流。
public static void main(String[] args) {
    int seed = 47;
    Random random = new Random(seed);
    char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();

    final String upperCharacters =
                            Stream.generate(() -> {
                                final int index = random.nextInt(letters.length);
                                return String.valueOf(letters[index]);
                            })
                                .limit(30)
                                .collect(Collectors.joining());
    System.out.println(upperCharacters);
}
  • Stream#iterate():

    方法结果将添加到流,并存储作为作为第一个参数用于下次调用iterate(),依此类推。

    注意: 流中的第一个元素必定为seed. The first element (position 0) in the Stream will be the provided seed.

示例: 根据其特性生成一个斐波那契数列

public class IterateFibonacci {
    private int x = 1;

    private Stream<Integer> numbers() {
        return Stream.iterate(0, i -> {
            int result = i + x;
            x = i;
            return result;
        });
    }

    public static void main(String[] args) {
        new IterateFibonacci().numbers()
                .limit(10)
                .forEach(System.out::println);
    }
}
  • Stream.Builder.build():

    流的构造器模式,通过Stream.Builder对象可以传递给它多个构造器信息,最后执行"构造".

    在调用build()后再调用add()会产生异常。

示例: String转Character数组&向流中添加26个随机字符

public class StreamBuilder {
    private Random random = new Random();

    public Stream.Builder<Character> builder(){
        // 从字符串转成Character数组
        final Character[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars()
                .mapToObj(c -> (char) c)
                .toArray(Character[]::new);
        // 向builder中添加26个随机字母
        Stream.Builder<Character> builder = Stream.builder();
        Arrays.stream(letters)
                .map(array -> letters[random.nextInt(letters.length)])
                .forEach(character -> builder.add(character));
        return builder;
    }

    public static void main(String[] args) {
        new StreamBuilder()
                .builder()
                .build()
                .forEach(System.out::println);
    }
}
  • Arrays#stream():

    Arrays中包含一个stream()静态方法, 用于将引用类型数组转换成流。 同时该方法可以根据int/double/long基本类型数组产生IntStream/LongStream/DoubleStream注意: 除这三个基本类型外, 其他基本类型数组需要先自行转换为包装类数组.

    示例:

    public static void main(String[] args) {
        // 通过引用类型数组生成对应的流对象Stream<T>
        Arrays.stream(new String[]{"AAA", "BBB", "CCC", "DDD"})
            .forEach(System.out::println);
        System.out.println("------------");
    
        // 通过三种基本类型数组中的int类型生成对应的流对象IntStream
        Arrays.stream(new int[]{1, 3, 5})
            .forEach(System.out::println);
        System.out.println("------------");
    
        // 不支持的基本类型数组转换, 需要先手动转换为包装类数组
        float[] source = {1.0f, 3.0f, 4.0f};
        Float[] target = new Float[source.length];
        for (int i = 0; i < source.length; i++) {
            float v = source[i];
            target[i] = v;
        }
        Arrays.stream(target)
            .forEach(System.out::println);
        System.out.println("------------");
    }
    
  • 正则表达式创建:java.util.regex.Pattern#splitAsStream()

    该方法可以根据传入的正则表达式将给定的字符序列(CharSequence)转换为流

    public static void main(String[] args) {
        String text =
            "aaa bbb      ccc,,,,,, ddd eee??? fff.";
        // 去掉重复的分隔元素
        String regex = "[ ,.?]+";
        Pattern.compile(regex).splitAsStream(text)
            .forEach(System.out::println);
    }
    

五. 总结

1. JAVA8中创建流方式的很灵活,可以根据需要选择对应的创建方式,但iterate()方法暂时没想到会有什么用处。
2. Stream对基本类型支持得不完整(书上的猜测是因为其他基本类型使用频率比较低的原因), Java8中只有int(IntStream)/long(LongStream)/double(DoubleStream)三种基本类型拥有对应的Stream,可以通过Arrays.stream()重载的基本类型参数快速转换为Stream.而其他基本类型就得通过循环等方式自行为包装类型才能调用Arrays.stream()的泛型参数重载方法进行转换.