读书笔记-《ON JAVA 中文版》-摘要14[第十四章 流式编程]

news2024/11/19 16:42:57

文章目录

  • 第十四章 流式编程
    • 1. 流支持
    • 2. 流创建
      • 2.1 流创建
      • 2.2 随机数流
      • 2.3 int 类型的范围
      • 2.4 generate()
      • 2.5 iterate()
      • 2.6 流的建造者模式
      • 2.7 Arrays
      • 2.8 正则表达式
    • 3. 中间操作
      • 3.1 跟踪和调试
      • 3.2 流元素排序
      • 3.3 移除元素
      • 3.4 应用函数到元素
      • 3.5 在 map() 中组合流
    • 4. Optional 类
      • 4.1 便利函数
      • 4.2 创建 Optional
      • 4.3 Optional 对象操作
      • 4.4 Optional 流
    • 5. 终端操作
      • 5.1 数组
      • 5.2 循环
      • 5.3 集合
      • 5.4 组合
      • 5.5 匹配
      • 5.6 查找
      • 5.7 信息
      • 5.8 数字流信息
    • 6. 本章小结

第十四章 流式编程

集合优化了对象的存储,而流和对象的处理有关。

利用流,我们无需迭代集合中的元素,就可以提取和操作它们

流的一个核心好处是,它使得程序更加短小并且更易理解。当 Lambda 表达式和方法引用(method references)和流一起使用的时候会让人感觉自成一体。流使得 Java 8 更具吸引力。

—PS:我们存储在 list set 中的对象可以通过流进行操作

import java.util.Random;

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

输出:

6
10
13
16
17
18
19

—PS: 这就是流式编程,非常的简洁明了

声明式编程(Declarative programming)是一种:声明要做什么,而非怎么做的编程风。

—PS:例如上面代码的,要去重(distinct)、排序(sorted)等

看下命令式编程:

import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;

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

输出:

[7, 8, 9, 11, 13, 15, 18]

—PS:这就是我们的写法,哈哈

流是懒加载的,这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。

1. 流支持

如何将一个全新的流的概念融入到现有类库中呢?

特别是涉及集合类接口的部分,直接向接口添加新方法会破坏所有老的接口实现类。

Java 8 采用的解决方案是:在接口中添加被 default ( 默认 )修饰的方法。通过这种方案,设计者们可以将流(stream)方法平滑地嵌入到现有类中。

流操作的类型有三种:

  • 创建流
  • 修改流元素(中间操作, Intermediate Operations)
  • 消费流元素(终端操作, Terminal Operations)。最后一种类型通常意味着收集流元素(通常是到集合中)。

2. 流创建

2.1 流创建

通过 ==Stream.of()==很容易地将一组元素转化成为流。

import java.util.stream.Stream;

public class StreamOf {
    public static void main(String[] args) {
        Stream.of("It's ", "a ", "wonderful ", "day ", "for ", "pie!")
                .forEach(System.out::print);
        System.out.println();
        Stream.of(2.3, 5.6, 7.5)
                .forEach(System.out::println);
    }
}

输出:

It's a wonderful day for pie!
2.3
5.6
7.5

集合都可以通过调用 stream() 方法来产生一个流。

public class Bubble {
    public final int i;

    public Bubble(int n) {
        i = n;
    }

    @Override
    public String toString() {
        return "Bubble(" + i + ")";
    }

    private static int count = 0;

    public static Bubble bubbler() {
        return new Bubble(count++);
    }
}
import java.util.*;

public class CollectionToStream {
    public static void main(String[] args) {
        List<Bubble> bubbles = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));
        System.out.println(bubbles.stream().mapToInt(b -> b.i).sum());

        Set<String> w = new HashSet<>(Arrays.asList("It's a wonderful day for pie!".split(" ")));
        w.stream().map(x -> x + " ").forEach(System.out::print);
        System.out.println();

        Map<String, Double> m = new HashMap<>();
        m.put("pi", 3.14159);
        m.put("e", 2.718);
        m.put("phi", 1.618);

        m.entrySet().stream()
                .map(e -> e.getKey() + ":" + e.getValue())
                .forEach(System.out::println);
    }
}

输出:

6
a pie! It's for wonderful day 
phi:1.618
e:2.718
pi:3.14159

2.2 随机数流

Random 类被一组生成流的方法增强了。代码示例:

import java.util.Random;
import java.util.stream.Stream;

public class RandomGenerators {
    public static <T> void show(Stream<T> stream) {
        stream.limit(4).forEach(System.out::println);
        System.out.println("分割------");
    }

    public static void main(String[] args) {
        Random rand = new Random(47);
        show(rand.ints().boxed());
        show(rand.doubles().boxed());

        // 控制上限和下限:
        show(rand.ints(5, 10).boxed());
        show(rand.doubles(4.6, 12.3).boxed());

        // 控制流大小:
        show(rand.ints(2).boxed());
        show(rand.doubles(2).boxed());

        // 控制流的大小和界限
        show(rand.ints(3, 1, 10).boxed());
        show(rand.longs(3,10,20).boxed());
    }
}

输出:

-1172028779
1717241110
-2014573909
229403722
分割------
0.16020656493302599
0.18847866977771732
0.5166020801268457
0.2678662084200585
分割------
6
9
9
8
分割------
10.78860969619508
10.46791267489988
8.684307950878356
8.677884777694413
分割------
2092435409
1072754767
分割------
0.8027943240652707
0.02351516188266134
分割------
1
8
8
分割------
16
19
18
分割------

2.3 int 类型的范围

IntStream 类提供了 range() 方法用于生成整型序列的流。编写循环时,这个方法会更加便利:

import static java.util.stream.IntStream.range;

public class Ranges {
    public static void main(String[] args) {
        int result = 0;
        // 传统方法:
        for (int i = 10; i < 20; i++) {
            result += i;
        }
        System.out.println("传统方法:" + result);

        // for-in 循环:
        result = 0;
        for (int i : range(10, 20).toArray()) {
            result += i;
        }
        System.out.println("for-in 循环:" + result);

        // 使用流:
        System.out.println("使用流:" + range(10,20).sum());
    }
}

输出:

传统方法:145
for-in 循环:145
使用流:145

2.4 generate()

import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Generator implements Supplier<String> {
    Random rand = new Random(47);
    char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();

    @Override
    public String get() {
        System.out.println("执行get()");
        return "" + letters[rand.nextInt(letters.length)];
    }

    public static void main(String[] args) {
        String word = Stream.generate(new Generator())
                            .limit(30)
                            .collect(Collectors.joining());
        System.out.println(word);
    }
}

输出:

执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
执行get()
YNZBRNYGCFOWZNTCQRGSEGZMMJMROE

如果要创建包含相同对象的流,只需要传递一个生成那些对象的 lambda 到 generate() 中:

import java.util.stream.Stream;

public class Duplicator {
    public static void main(String[] args) {
        Stream.generate(() -> "duplicate")
                .limit(3)
                .forEach(System.out::println);
    }
}

输出:

duplicate
duplicate
duplicate

由于 bubbler() 与 Supplier 是接口兼容的,我们可以将其方法引用直接传递给 Stream. generate() :

import java.util.stream.Stream;

public class Bubbles {
    public static void main(String[] args) {
        Stream.generate(Bubble::bubbler)
                .limit(5)
                .forEach(System.out::println);
    }
}

输出:

Bubble(0)
Bubble(1)
Bubble(2)
Bubble(3)
Bubble(4)

—PS:Stream.generate() 方法返回一个无限连续的无序流,其中每个元素由提供的供应商(Supplier)生成,该方法用于生成常量流和随机元素流,常配合 limit() 一起使用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JtF2tta1-1681527051564)(14-1.png)]

2.5 iterate()

Stream. iterate() 以种子(第一个参数)开头,并将其传给方法(第二个参数)。方法的结果将添加到流,并存储作为第一个参数用于下次调用 iterate() ,依次类推。

import java.util.stream.Stream;

public class Fibonacci {
    int x = 1;

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

    public static void main(String[] args) {
        new Fibonacci().numbers()
                .skip(20) // 过滤前 20 个
                .limit(10) // 然后取 10 个
                .forEach(System.out::println);
    }
}

输出:

6765
10946
17711
28657
46368
75025
121393
196418
317811
514229

—PS:Stream. iterate() 返回一个无限流

2.6 流的建造者模式

在建造者设计模式(也称构造器模式)中,首先创建一个 builder 对象,传递给它多个构造器信息,最后执行“构造”。Stream 库提供了这样的 Builder 。

import java.util.stream.Stream;

public class StreamBuilder {
    public static void main(String[] args) {
        Stream.Builder<String> builder = Stream.builder();
        builder.add("a"); // 放入元素
        builder.add("b");
        builder.add("c");
        builder.add("d");
        builder.add("e");
        Stream<String> build = builder.build(); // build() 返回一个流
        build.map(s -> s.toUpperCase())
             .forEach(System.out::print);
    }
}

输出:

ABCDE

只要你不调用 build() 方法,就可以继续向 builder 对象中 add 元素。

2.7 Arrays

Arrays 类中含有一个名为 stream() 的静态方法用于把数组转换成为流。

import java.util.Arrays;

public class ArraysStream {
    public static void main(String[] args) {
        Arrays.stream(new int[]{1, 3, 6, 8})
              .forEach(System.out::println);
    }
}

输出:

1
3
6
8

stream() 同样可以产生 IntStreamLongStreamDoubleStream

import java.util.Arrays;
import java.util.stream.DoubleStream;

public class ArraysStream2 {
    public static void main(String[] args) {
        // PS:stream() 同样可以产生 IntStream,LongStream 和 DoubleStream
        DoubleStream doubleStream = Arrays.stream(new double[]{3.2, 4.6, 7.8});
        doubleStream.forEach(System.out::println);

        // PS:选择一个子域,前包后不包
        Arrays.stream(new int[]{1, 3, 5, 7, 15, 28, 37}, 3, 6)
                .forEach(n -> System.out.format("%d ", n));
    }
}

输出:

3.2
4.6
7.8
7 15 28 

2.8 正则表达式

Java 8 在 java.util.regex.Pattern 中增加了一个新的方法 splitAsStream() 。这个方法可以根据传入的公式将字符序列转化为流。但是有一个限制,输入只能是 CharSequence,因此不能将流作为 splitAsStream() 的参数。

—PS:CharSequence 是一个接口,表示char值的一个可读序列。此接口对许多不同种类的char序列提供统一的自读访问。此接口不修改equals 和 hashCode 方法的常规协定,因此,通常未定义比较实现 CharSequence 的两个对象的结果。他有几个实现类:CharBuffer、String、StringBuffer、StringBuilder。

创建 Cheese.dat

// streams/Cheese.dat
Not much of a cheese shop really, is it?
Finest in the district, sir.
And what leads you to that conclusion?
Well, it's so clean.
It's certainly uncontaminated by cheese.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FileToWordsRegexp {
    private String all;

    public FileToWordsRegexp(String filePath) throws IOException {
        all = Files.lines(Paths.get(filePath))
                .skip(1) // 跳过第一行
                .collect(Collectors.joining(" "));
    }

    public Stream<String> stream() {
        return Pattern.compile("[ .,?]+").splitAsStream(all);
    }

    public static void main(String[] args) throws IOException {
        FileToWordsRegexp fw = new FileToWordsRegexp("D:\\demo\\src\\main\\java\\com\\liu\\Cheese.dat");
        fw.stream().limit(7)
                   .map(w->w+" ")
                   .forEach(System.out::print);
        System.out.println("------");
        fw.stream().skip(7)
                   .limit(2)
                   .map(w -> w+" ")
                   .forEach(System.out::print);
    }
}

输出:

Not much of a cheese shop really ------
is it

3. 中间操作

中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作。

3.1 跟踪和调试

peek() 操作的目的是帮助调试。它允许你无修改地查看流中的元素。

import java.util.ArrayList;
import java.util.List;

public class Peeking {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        list.stream().peek(System.out::print)
                .map(String::toLowerCase)
                .peek(System.out::print)
                .map(String::toUpperCase)
                .forEach(System.out::print);
    }
}

输出:

aaAbbBccC

因为 peek() 符合无返回值的 Consumer 函数式接口,所以我们只能观察,无法使用不同的元素来替换流中的对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QAQtN3JZ-1681527051565)(14-2.png)]

3.2 流元素排序

除了sorted() 的默认比较器实现,其实它还有另一种形式的实现: 传入一个 Comparator 参数

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class SortedComparator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("f");
        list.add("c");
        list.add("y");
        list.add("e");

        list.stream()
                .map(w -> w + " ")
                .sorted(Comparator.reverseOrder())
                .forEach(System.out::print);
    }
}

输出:

y f e c a

3.3 移除元素

  • distinct() :在 Randoms.java 类中的 distinct() 可用于消除流中的重复元素。相比创建一个 Set 集合,该方法的工作量要少得多。

  • filter(Predicate) :过滤操作会保留与传递进去的过滤器函数计算结果为 true 元素。

import java.util.Random;

public class Prime {
    public static void main(String[] args) {
        Random rand = new Random(47);
        rand.ints(10, 20)
                .limit(20)
                .distinct()
                .filter(n -> n > 15)
                .forEach(System.out::println);
    }
}

输出:

18
19
17

3.4 应用函数到元素

  • map(Function) :将函数操作应用在输入流的元素中,并将返回值传递到输出流中。

  • mapToInt(ToIntFunction) :操作同上,但结果是 IntStream

  • mapToLong(ToLongFunction) :操作同上,但结果是 LongStream

  • mapToDouble(ToDoubleFunction) :操作同上,但结果是 DoubleStream

import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Stream;

public class FunctionMap {
    static String[] elements = {"12", "", "23", "45"};

    static Stream<String> testStream() {
        return Arrays.stream(elements);
    }

    static void test(String descr, Function<String, String> func) {
        System.out.println(" ---( " + descr + " )---");
        testStream()
                .map(func)
                .forEach(System.out::println);
    }

    public static void main(String[] args) {
        // 每个元素加个 []
        test("add brackets", s -> "[" + s + "]");

        // 每个元素转为 int 后加1
        test("Increment", s -> {
            try {
                return Integer.parseInt(s) + 1 + "";
            } catch (NumberFormatException e) {
                return s;
            }
        });

        // 将元素中的 2 替换为 9
        test("Replace", s -> s.replace("2", "9"));

        // 取元素的最后一个字符
        test("Take last digit", s -> s.length() > 0 ? s.charAt(s.length() - 1) + "" : s);

    }
}

输出:

 ---( add brackets )---
[12]
[]
[23]
[45]
 ---( Increment )---
13

24
46
 ---( Replace )---
19

93
45
 ---( Take last digit )---
2

3
5

在以上例子中, map() 将一个字符串映射为另一个字符串,但是我们完全可以产生和接收类型完全不同的类型,从而改变流的数据类型。下面代码示例:

import java.util.stream.Stream;

class FunctionMap3 {
    public static void main(String[] args) {
        Stream.of("5", "7", "9")
                .mapToInt(Integer::parseInt)
                .forEach(n -> System.out.format("%d ", n));
        System.out.println();
        Stream.of("17", "19", "23")
                .mapToLong(Long::parseLong)
                .forEach(n -> System.out.format("%d ", n));
        System.out.println();
        Stream.of("17", "1.9", ".23")
                .mapToDouble(Double::parseDouble)
                .forEach(n -> System.out.format("%f ", n));
    }
}

输出:

5 7 9 
17 19 23 
17.000000 1.900000 0.230000 

3.5 在 map() 中组合流

flatMap() 做了两件事:将产生流的函数应用在每个元素上(与 map() 所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。

  • flatMap(Function) :当 Function 产生流时使用。

  • flatMapToInt(Function) :当 Function 产生 IntStream 时使用。

  • flatMapToLong(Function) :当 Function 产生 LongStream 时使用。

  • flatMapToDouble(Function) :当 Function 产生 DoubleStream 时使用。

import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamOfRandoms {
    public static void main(String[] args) {
        Random rand = new Random(47);
        Stream.of(1, 2, 3)
                .flatMapToInt(i -> IntStream.concat(
                        rand.ints(0, 100).limit(i)
                        , IntStream.of(-1)
                )).forEach(n -> System.out.format("%d ", n));
    }
}

输出:

58 -1 55 93 -1 61 61 29 -1 

concat(),它以参数顺序组合两个流。

4. Optional 类

如果流中的某个元素不存在,会引起一些异常。

Optional 可以避免这个问题。一些标准流操作返回 Optional 对象,因为它们并不能保证预期结果一定存在。包括:

  • findFirst() 返回一个包含第一个元素的 Optional 对象,如果流为空则返回 Optional.empty

  • findAny() 返回包含任意元素的 Optional 对象,如果流为空则返回 Optional.empty

  • max() 和 min() 返回一个包含最大值或者最小值的 Optional 对象,如果流为空则返回 Optional.empty

  • reduce() 不再以 identity 形式开头,而是将其返回值包装在 Optional 中。

  • 对于数字流 IntStreamLongStreamDoubleStreamaverage() 会将结果包装在 Optional 以防止流为空。

Stream.empty() 可以创建一个空流。

import java.util.Optional;
import java.util.stream.Stream;

public class OptionalBasics {
    static void test(Optional<String> optString){
        // PS: isPresent() 检查其中是否包含元素
        if (optString.isPresent()) {
            // PS:使用 get() 获取
            System.out.println(optString.get());
        }else {
            System.out.println("Nothing inside!");
        }
    }

    public static void main(String[] args) {
        test(Stream.of("Epithets").findFirst());

        // PS:没有上下文时,创建空流需要加类型 Stream.<String>empty()
        test(Stream.<String>empty().findFirst());
    }
}

输出:

Epithets
Nothing inside!

4.1 便利函数

有许多便利函数可以解包 Optional ,这简化了上述“对所包含的对象的检查和执行操作”的过程:

  • ifPresent(Consumer) :当值存在时调用 Consumer,否则什么也不做。

  • orElse(otherObject) :如果值存在则直接返回,否则生成 otherObject

  • orElseGet(Supplier) :如果值存在则直接返回,否则使用 Supplier 函数生成一个可替代对象。

  • orElseThrow(Supplier) :如果值存在直接返回,否则使用 Supplier 函数生成一个异常。

import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class Optionals {
    static void basics(Optional<String> optString) {
        if (optString.isPresent()) {
            System.out.println(optString.get());
        } else {
            System.out.println("Nothing inside!");
        }
    }

    static void ifPresent(Optional<String> optString) {
        optString.ifPresent(System.out::println);
    }

    static void orElse(Optional<String> optString) {
        System.out.println(optString.orElse("Nada"));
        ;
    }

    static void orElseGet(Optional<String> optString) {
        System.out.println(optString.orElseGet(() -> "Generated"));
        ;
    }

    static void orElseThrow(Optional<String> optString) {
        try {
            System.out.println(optString.orElseThrow(() -> new Exception("Supplied")));
        } catch (Exception e) {
            System.out.println("Caught " + e);
        }
    }

    static void test(String testName, Consumer<Optional<String>> cos) {
        System.out.println(" === " + testName + " === ");
        cos.accept(Stream.of("Epithets").findFirst());
        cos.accept(Stream.<String>empty().findFirst());
    }

    public static void main(String[] args) {
        test("basics", Optionals::basics);
        test("ifPresent", Optionals::ifPresent);
        test("orElse", Optionals::orElse);
        test("orElseGet", Optionals::orElseGet);
        test("orElseThrow", Optionals::orElseThrow);
    }
}

输出:

 === basics === 
Epithets
Nothing inside!
 === ifPresent === 
Epithets
 === orElse === 
Epithets
Nada
 === orElseGet === 
Epithets
Generated
 === orElseThrow === 
Epithets
Caught java.lang.Exception: Supplied

4.2 创建 Optional

当我们在自己的代码中加入 Optional 时,可以使用下面 3 个静态方法:

  • empty() :生成一个空 Optional

  • of(value) :将一个非空值包装到 Optional 里。

  • ofNullable(value) :针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中。

import java.util.Optional;

public class CreatingOptionals {
    static void test(String testName, Optional<String> opt) {
        System.out.println(" === " + testName + " === ");
        System.out.println(opt.orElse("Null"));
    }

    public static void main(String[] args) {
        test("empty", Optional.empty());
        test("of", Optional.of("Howdy"));
        try {
            test("of", Optional.of(null));
        } catch (Exception e) {
            System.out.println(e);
        }
        test("ofNullable", Optional.ofNullable("Hi"));
        test("ofNullable", Optional.ofNullable(null));
    }
}

输出:

 === empty === 
Null
 === of === 
Howdy
java.lang.NullPointerException
 === ofNullable === 
Hi
 === ofNullable === 
Null

我们不能通过传递 null 到 of() 来创建 Optional 对象。最安全的方法是, 使用 ofNullable() 来优雅地处理 null

4.3 Optional 对象操作

当我们的流管道生成了 Optional 对象,下面 3 个方法可使得 Optional 的后续能做更多的操作:

  • filter(Predicate) :将 Predicate 应用于 Optional 中的内容并返回结果。当 Optional 不满足 Predicate 时返回空。如果 Optional 为空,则直接返回。

  • map(Function) :如果 Optional 不为空,应用 FunctionOptional 中的内容,并返回结果。否则直接返回 Optional.empty

  • flatMap(Function) :同 map() ,但是 flatMap() 不会在最后进行任何包装。

以上方法都不适用于数值型 Optional。一般来说,流的 filter() 会在 Predicate 返回 false 时移除流元素。而 Optional.filter() 在失败时不会删除 Optional,而是将其保留下来,并转化为空。

import java.util.Arrays;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class OptionalFilter {
    static String[] elements = {"Foo", "", "Bar", "Baz", "Bingo"};

    static Stream<String> testStream() {
        return Arrays.stream(elements);
    }

    static void test(String descr, Predicate<String> pred) {
        System.out.println(" ---( " + descr + " )---");
        for (int i = 0; i <= elements.length; i++) {
            System.out.format("第%d次循环", i + 1);
            System.out.println();
            System.out.println(testStream().skip(i)
                    .findFirst() // PS:获取 Optional
                    .filter(pred)); // PS:这里的 filter() 是 Optional 的,不是流的
        }
    }

    public static void main(String[] args) {
        test("true", str -> true);
        test("false", str -> false);
        test("str != \"\"", str -> str != "");
        test("str.length() == 3", str -> str.length() == 3);
        test("startsWith(\"B\")", str -> str.startsWith("B"));
    }
}

输出:

 ---( true )---
第1次循环
Optional[Foo]
第2次循环
Optional[]
第3次循环
Optional[Bar]
第4次循环
Optional[Baz]
第5次循环
Optional[Bingo]
第6次循环
Optional.empty
 ---( false )---
第1次循环
Optional.empty
第2次循环
Optional.empty
第3次循环
Optional.empty
第4次循环
Optional.empty
第5次循环
Optional.empty
第6次循环
Optional.empty
 ---( str != "" )---
第1次循环
Optional[Foo]
第2次循环
Optional.empty
第3次循环
Optional[Bar]
第4次循环
Optional[Baz]
第5次循环
Optional[Bingo]
第6次循环
Optional.empty
 ---( str.length() == 3 )---
第1次循环
Optional[Foo]
第2次循环
Optional.empty
第3次循环
Optional[Bar]
第4次循环
Optional[Baz]
第5次循环
Optional.empty
第6次循环
Optional.empty
 ---( startsWith("B") )---
第1次循环
Optional.empty
第2次循环
Optional.empty
第3次循环
Optional[Bar]
第4次循环
Optional[Baz]
第5次循环
Optional[Bingo]
第6次循环
Optional.empty

注意,不同于普通 for 循环,这里的索引值范围并不是 i < elements.length , 而是 i <= elements.length 。所以最后一个元素实际上超出了流,这将自动成为 Optional.empty

—PS:Optional.empty 是因为 Optional 重写了 toString()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-khjeloOR-1681527051565)(14-3.png)]

同 map() 一样 , Optional.map() 应用于函数。它仅在 Optional 不为空时才应用映射函数,并将 Optional 的内容提取到映射函数。代码示例:

import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Stream;

public class OptionalMap {
    static String[] elements = {"12", "", "23", "45"};

    static Stream<String> testStream() {
        return Arrays.stream(elements);
    }

    static void test(String descr, Function<String, String> func) {
        System.out.println(" ---( " + descr + " )---");
        for (int i = 0; i <= elements.length; i++) {
            System.out.println(testStream().skip(i)
                    .findFirst() // PS:获取 Optional
                    .map(func)); // PS:这里的 map() 是 Optional 的,不是流的
        }
    }

    public static void main(String[] args) {
        test("Add brackets", s -> "[" + s + "]");
        test("Increment", s -> {
            try {
                return Integer.parseInt(s) + 1 + "";
            } catch (NumberFormatException e) {
                return s;
            }
        });
        test("Replace", s -> s.replace("2", "9"));
        test("Take last digit", s -> s.length() > 0 ?
                s.charAt(s.length() - 1) + "" : s);
    }
}

输出:

 ---( Add brackets )---
Optional[[12]]
Optional[[]]
Optional[[23]]
Optional[[45]]
Optional.empty
 ---( Increment )---
Optional[13]
Optional[]
Optional[24]
Optional[46]
Optional.empty
 ---( Replace )---
Optional[19]
Optional[]
Optional[93]
Optional[45]
Optional.empty
 ---( Take last digit )---
Optional[2]
Optional[]
Optional[3]
Optional[5]
Optional.empty

Optional 的 flatMap() 应用于已生成 Optional 的映射函数,所以 flatMap() 不会像 map() 那样将结果封装在 Optional 中。代码示例:

import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

public class OptionalFlatMap {
    static String[] elements = {"12", "", "23", "45"};

    static Stream<String> testStream() {
        return Arrays.stream(elements);
    }

    static void test(String descr, Function<String, Optional<String>> func) {
        System.out.println(" ---( " + descr + " )---");
        for (int i = 0; i <= elements.length; i++) {
            System.out.println(testStream()
                    .skip(i)
                    .findFirst()
                    .flatMap(func));
        }
    }

    public static void main(String[] args) {
        test("Add brackets", s -> Optional.of("[" + s + "]"));
        test("Increment", s -> {
            try {
                return Optional.of(Integer.parseInt(s) + 1 + "");
            } catch (NumberFormatException e) {
                return Optional.of(s);
            }
        });
        test("Replace", s -> Optional.of(s.replace("2", "9")));
        test("Take last digit", s -> Optional.of(s.length() > 0 ?
                s.charAt(s.length() - 1) + ""
                : s));
    }
}

输出:

 ---( Add brackets )---
Optional[[12]]
Optional[[]]
Optional[[23]]
Optional[[45]]
Optional.empty
 ---( Increment )---
Optional[13]
Optional[]
Optional[24]
Optional[46]
Optional.empty
 ---( Replace )---
Optional[19]
Optional[]
Optional[93]
Optional[45]
Optional.empty
 ---( Take last digit )---
Optional[2]
Optional[]
Optional[3]
Optional[5]
Optional.empty

Optional.flatMap() 是为那些自己已经生成 Optional 的函数而设计的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lUTm11uM-1681527051566)(14-4.png)]

4.4 Optional 流

import java.util.Optional;
import java.util.Random;
import java.util.stream.Stream;

public class Signal {
    private final String msg;

    public Signal(String msg) {
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    @Override
    public String toString() {
        return "Signal(" + msg + ")";
    }

    static Random rand = new Random(47);

    public static Signal morse() {
        switch (rand.nextInt(4)) {
            case 1:
                return new Signal("dot");
            case 2:
                return new Signal("dash");
            default:
                return null;
        }
    }

    public static Stream<Optional<Signal>> stream() {
        return Stream.generate(Signal::morse)
                .map(signal -> Optional.ofNullable(signal));
    }
}
import java.util.Optional;

public class StreamOfOptionals {
    public static void main(String[] args) {
        Signal.stream()
                .limit(10)
                .forEach(System.out::println);
        System.out.println(" ---");
        Signal.stream()
                .limit(10)
                .filter(Optional::isPresent) // 将空值过滤掉
                .map(Optional::get) // 获取元素的值
                .forEach(System.out::println);
    }
}

输出:

Optional[Signal(dash)]
Optional[Signal(dot)]
Optional[Signal(dash)]
Optional.empty
Optional.empty
Optional[Signal(dash)]
Optional.empty
Optional[Signal(dot)]
Optional[Signal(dash)]
Optional[Signal(dash)]
 ---
Signal(dot)
Signal(dot)
Signal(dash)
Signal(dash)

5. 终端操作

以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作总是我们在流管道中所做的最后一件事

5.1 数组

  • toArray() :将流转换成适当类型的数组。

  • toArray(generator) :在特殊情况下,生成自定义类型的数组。

import java.util.Arrays;
import java.util.Random;
import java.util.stream.IntStream;

public class RandInts {
    private static int[] rints = new Random(47)
            .ints(0, 1000)
            .limit(100)
            .toArray();

    public static IntStream rands() {
        return Arrays.stream(rints);
    }
}

5.2 循环

  • forEach(Consumer) 常见如 System.out::println 作为 Consumer 函数。

  • forEachOrdered(Consumer) : 保证 forEach 按照原始流顺序操作。

public class ForEach {
    static final int SZ = 14;

    public static void main(String[] args) {
        RandInts.rands().limit(SZ)
                .forEach(n -> System.out.format("%d ", n));
        System.out.println();
        RandInts.rands().limit(SZ)
                .parallel() // 并发操作,生成的流是无序的
                .forEach(n -> System.out.format("%d ", n));
        System.out.println();
        RandInts.rands().limit(SZ)
                .parallel()
                .forEachOrdered(n -> System.out.format("%d ", n));
    }
}

输出:

258 555 693 861 961 429 868 200 522 207 288 128 551 589 
551 589 555 693 429 288 861 200 522 128 868 207 961 258 
258 555 693 861 961 429 868 200 522 207 288 128 551 589 

在最后一个流中,同时使用了 parallel() 和 forEachOrdered() 来强制保持原始流顺序。因此,对非并行流使用 forEachOrdered() 是没有任何影响的

5.3 集合

  • collect(Collector) :使用 Collector 收集流元素到结果集合中。

  • collect(Supplier, BiConsumer, BiConsumer) :同上,第一个参数 Supplier 创建了一个新结果集合,第二个参数 BiConsumer 将下一个元素包含到结果中,第三个参数 BiConsumer 用于将两个值组合起来。

通过将集合的构造函数引用传递给 Collectors.toCollection() ,从而构建任何类型的集合。

import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class TreeSetOfWords {
    public static void main(String[] args) {
        TreeSet<String> collect = Stream.of("a", "c", "f", "e", "d", "b")
                .collect(Collectors.toCollection(TreeSet::new));
        System.out.println(collect);
    }
}

输出:

[a, b, c, d, e, f]

也可以在流中生成 Map。代码示例:

import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 常规类
 */
class Pair {
    public final Character c;
    public final Integer i;

    Pair(Character c, Integer i) {
        this.c = c;
        this.i = i;
    }

    public Character getC() {
        return c;
    }

    public Integer getI() {
        return i;
    }

    @Override
    public String toString() {
        return "Pair(" + c + ", " + i + ")";
    }
}

/**
 * 创建了随机生成的 Pair 对象流
 */
class RandomPair {
    Random rand = new Random(47);
    Iterator<Character> capChars = rand.ints(65, 91)
            .mapToObj(i -> (char) i)
            .iterator();

    public Stream<Pair> stream() {
        return rand.ints(100, 1000)
                .mapToObj(i -> new Pair(capChars.next(), i));
    }
}

public class MapCollector {
    public static void main(String[] args) {
        Map<Integer, Character> collect = new RandomPair().stream()
                .limit(8)
                .collect(Collectors.toMap(Pair::getI, Pair::getC));
        System.out.println(collect);
    }
}

输出:

{688=W, 309=C, 293=B, 761=N, 858=N, 668=G, 622=F, 751=N}

5.4 组合

  • reduce(BinaryOperator) :使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional

  • reduce(identity, BinaryOperator) :功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果。

  • reduce(identity, BiFunction, BinaryOperator) :更复杂的使用形式(暂不介绍),这里把它包含在内,因为它可以提高效率。通常,我们可以显式地组合 map() 和 reduce() 来更简单的表达它。

import java.util.Random;
import java.util.stream.Stream;

class Frobnitz {
    int size;

    Frobnitz(int sz) {
        size = sz;
    }

    @Override
    public String toString() {
        return "Frobnitz(" + size + ")";
    }

    static final int BOUND = 100;
    static Random rand = new Random(47);

    // 生成对象的方法
    static Frobnitz supply() {
        return new Frobnitz(rand.nextInt(BOUND));
    }
}

public class Reduce {
    public static void main(String[] args) {
        // Frobnitz 包含了一个名为 supply() 的生成器;
        // 因为这个方法对于 Supplier<Frobnitz> 是签名兼 容的,
        // 我们可以将其方法引用传递给 Stream.generate() (这种签名兼容性被称作结构一致性)。
        Stream.generate(Frobnitz::supply)
                .limit(10)
                .peek(System.out::println)
                // 第一个参数 fr0 是上一次调用 reduce() 的结果。而第二个参数 fr1 是从流 传递过来的值。
                .reduce((fr0, fr1) -> fr0.size > 50 ? fr0 : fr1)
                // 无“初始值”的 reduce() 方法返回值是 Optional 类型。
                .ifPresent(System.out::println);
    }
}

输出:

Frobnitz(58)
Frobnitz(55)
Frobnitz(93)
Frobnitz(61)
Frobnitz(61)
Frobnitz(29)
Frobnitz(68)
Frobnitz(0)
Frobnitz(22)
Frobnitz(7)
Frobnitz(58)

当取得第一个长度小于 50 的 Frobnitz ,只要得到结果就会忽略其他。

—PS:只管匹配到的第一个,所以输出结果中有 Frobnitz(29) 等 50 以下的

5.5 匹配

  • allMatch(Predicate) :如果流的每个元素根据提供的 Predicate 都返回 true 时,结果返回为 true。在第一个 false 时,则停止执行计算。

  • anyMatch(Predicate) :如果流中的任意一个元素根据提供的 Predicate 返回 true 时,结果返回为 true。在第一个 false 是停止执行计算。

  • noneMatch(Predicate) :如果流的每个元素根据提供的 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。

import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import java.util.stream.Stream;

interface Matcher extends BiPredicate<Stream<Integer>, Predicate<Integer>> {
}

public class Matching {
    static void show(Matcher match, int val) {
        // IntStream.rangeClosed() 生成 int 类型的流
        Stream<Integer> integerStream = IntStream.rangeClosed(1, 9)
                .boxed() // 将 int 流 包装成 Integer 流
                .peek(n -> System.out.format("%d ", n));

        boolean test = match.test(integerStream, n -> n < val);
        System.out.println(test);
    }

    public static void main(String[] args) {
        show(Stream::allMatch, 10); // 是否都小于10
        show(Stream::allMatch, 4);
        show(Stream::anyMatch, 2); // 任意一个小于2
        show(Stream::anyMatch, 0);
        show(Stream::noneMatch, 5); // 没有小于5的
        show(Stream::noneMatch, 0);
    }
}

输出:

1 2 3 4 5 6 7 8 9 true
1 2 3 4 false
1 true
1 2 3 4 5 6 7 8 9 false
1 false
1 2 3 4 5 6 7 8 9 true

—PS:稍微捋一下就清楚了,很见名知义了

BiPredicate 是一个二元谓词,它只能接受两个参数且只返回 true 或者 false。 match.test() 的调用会被转换成 Stream::*Match 函数的调用。

peek() 是用于向我们展示测试在短路之前的情况。

5.6 查找

  • findFirst() :返回第一个流元素的 Optional,如果流为空返回 Optional.empty

  • findAny() :返回含有任意流元素的 Optional,如果流为空返回 Optional.empty

import static com.liu.RandInts.rands;

public class SelectElement {
    public static void main(String[] args) {
        System.out.println(rands().findFirst().getAsInt());
        System.out.println(rands().parallel().findFirst().getAsInt());
        System.out.println(rands().findAny().getAsInt());
        System.out.println(rands().parallel().findAny().getAsInt());
    }
}

输出:

258
258
258
242

findFirst() 无论流是否为并行化的,总是会选择流中的第一个元素。对于非并行 流, findAny() 会选择流中的第一个元素(即使从定义上来看是选择任意元素)。

如果必须选择流中最后一个元素,那就使用 reduce() 。代码示例:

import java.util.Optional;
import java.util.OptionalInt;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class LastElement {
    public static void main(String[] args) {
        OptionalInt last = IntStream.range(10, 20)
                .reduce((n1, n2) -> n2);
        System.out.println(last.orElse(-1));

        Optional<String> lastobj = Stream.of("one", "two", "three")
                .reduce((n1, n2) -> n2);
        System.out.println(lastobj.orElse("Nothing there!"));
    }
}

输出:

19
three

—PS:reduce() 第一个参数为 reduce 上次的执行结果,第二个参数为这次处理的流元素

5.7 信息

  • count() :流中的元素个数。

  • max(Comparator) :根据所传入的 Comparator 所决定的“最大”元素。

  • min(Comparator) :根据所传入的 Comparator 所决定的“最小”元素。

import java.util.stream.Stream;

public class Informational {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("adb", "you", "a", "c");
        System.out.println(stream.count());
        stream = Stream.of("adb", "you", "a", "c");
        System.out.println(stream.min(String.CASE_INSENSITIVE_ORDER).orElse("NONE"));
        stream = Stream.of("adb", "you", "a", "c");
        System.out.println(stream.max(String.CASE_INSENSITIVE_ORDER).orElse("NONE"));
    }
}

输出:

4
a
you

min() 和 max() 的返回类型为 Optional,这需要我们使用 orElse() 来解包。

5.8 数字流信息

  • average() :求取流元素平均值。

  • max() 和 min() :数值流操作无需 Comparator

  • sum() :对所有流元素进行求和。

  • summaryStatistics() :生成可能有用的数据。目前并不太清楚这个方法存在的必要性,因为我们其实可以用更直接的方法获得需要的数据。

import static com.liu.RandInts.rands;

public class NumericStreamInfo {
    public static void main(String[] args) {
        System.out.println(rands().average().getAsDouble());
        System.out.println(rands().max().getAsInt());
        System.out.println(rands().min().getAsInt());
        System.out.println(rands().sum());
        System.out.println(rands().summaryStatistics());
    }
}

输出:

507.94
998
8
50794
IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, max=998}

6. 本章小结

流式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转。

自我学习总结:

  1. Java 8 通过 default ( 默认 )修饰的方法,将流嵌入到现有类中
  2. 创建流的方式:
1)Stream.of() 一组元素转化成为流
2)集合的 stream() 产生流
3)Random 的 ints()产生 int 流,一般配合 boxed()
4)Stream.generate() 产生一个无限连续的无序流,入参为类的生成器,一般配合 limit()
5)Stream.iterate() 以种子(第一个参数)开头,并将其传给方法(第二个参数)。方法的结果将添加到流,并存储作为第一个参数用于下次调用 iterate() ,依次类推,产生一个无限流
6)Arrays.stream() 将数组转为流
7)Stream.empty() 可以创建一个空流
  1. 中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作,包含如下:
方法名说明
peek(Consumer)允许无修改地查看流中的元素,
常帮助调试
sorted()排序,有默认比较器实现,也
可传入一个 Comparator 参数
distinct()消除流中的重复元素
filter(Predicate)过滤掉流中不符合过滤器的元素
map(Function)将函数操作应用在输入流的元素中,
并将返回值传递到输出流中
flatMap(Function)将产生流的函数应用在每个元素上
(与 map() 所做的相同),
然后将每个流都扁平化为元素,
因而最终产生的仅仅是元素。
concat()以参数顺序组合两个流
parallel()获取并行流
  1. 标准流操作返回 Optional 对象,因为它们并不能保证预期结果一定存在。包括:

    findFirst() 返回一个包含第一个元素的 Optional 对象,如果流为空则返回 Optional.empty

    findAny() 返回包含任意元素的 Optional 对象,如果流为空则返回 Optional.empty

    max() 和 min() 返回一个包含最大值或者最小值的 Optional 对象,如果流为空则返回 Optional.empty

    reduce() 不再以 identity 形式开头,而是将其返回值包装在 Optional

    对于数字流 IntStreamLongStreamDoubleStreamaverage() 会将结果包装在 Optional 以防止流为空

  2. 创建 Optional 有3个静态方法:

empty() :生成一个空 Optional

of(value) :将一个非空值包装到 Optional 里。

ofNullable(value) :针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中。

  1. Optional 的方法有:
方法名说明
isPresent()检查 Optional 是否包含元素
get()获取 Optional 中的元素
ifPresent(Consumer)当值存在时调用 Consumer
否则什么也不做
orElse(otherObject)如果值存在则直接返回,
否则生成 otherObject
orElseGet(Supplier)如果值存在则直接返回,
否则使用 Supplier 函数生成一个可替代对象
orElseThrow(Supplier)如果值存在直接返回,
否则使用 Supplier 函数生成一个异常
filter(Predicate)Predicate 应用于 Optional 中的内容并返回结果。
Optional 不满足 Predicate 时返回空。
如果 Optional 为空,则直接返回
map(Function)如果 Optional 不为空,
应用 FunctionOptional 中的内容,并返回结果。
否则直接返回 Optional.empty
flatMap(Function)同 map() ,但是不会将结果封装在 Optional
  1. 流的 filter() 会在 Predicate 返回 false 时移除流元素。而 Optional.filter() 在失败时不会删除 Optional,而是将其保留下来,并转化为空。
  2. 流的终端操作,即在流管道中所做的最后一件事,会停止流,并得到最终结果。有以下方法:
方法名说明
toArray()将流转换成适当类型的数组
toArray(generator)在特殊情况下,
生成自定义类型的数组
collect(Collector)使用 Collector 收集流元素到结果集合中。
通过将集合的构造函数引用
传递给 Collectors.toCollection() ,
从而构建任何类型的集合。
可以在流中生成 Map ,即 Collectors.toMap()
collect(Supplier, BiConsumer, BiConsumer)同上,
第一个参数 Supplier 创建了一个新结果集合,
第二个参数 BiConsumer 将下一个元素包含到结果中,
第三个参数 BiConsumer 用于将两个值组合起来
reduce(BinaryOperator)使用 BinaryOperator
组合所有流中的元素。
因为流可能为空,其返回值为 Optional
reduce(identity, BinaryOperator)功能同上,
但是使用 identity 作为其组合的初始值。
因此如果流为空,identity 就是结果
reduce(identity, BiFunction, BinaryOperator)功能同上,太复杂,暂不介绍
allMatch(Predicate)如果流的每个元素
根据提供的 Predicate 都返回 true 时,
结果返回为 true。
在第一个 false 时,则停止执行计算
anyMatch(Predicate)如果流中的任意一个元素
根据提供的 Predicate 返回 true 时,
结果返回为 true。
在第一个 false 是停止执行计算
noneMatch(Predicate)如果流的每个元素
根据提供的 Predicate 都返回 false 时,
结果返回为 true。
在第一个 true 时停止执行计算
findFirst()返回第一个流元素的 Optional
如果流为空返回 Optional.empty
findAny()返回含有任意流元素的 Optional
如果流为空返回 Optional.empty
对于非并行流会返回流的第一个元素
count()统计流中的元素个数
max(Comparator)根据所传入的 Comparator
返回所决定的“最大”元素
min(Comparator)根据所传入的 Comparator
返回所决定的“最小”元素
average()求取数字流元素平均值
max() 和 min()返回数字流中的最大、最小值,
无需 Comparator
sum()求取数字流元素的和
summaryStatistics()生成数字流可能有用的数据
  1. 流式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转
    在这里插入图片描述
    (图网,侵删)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/444698.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑文件恢复怎么做?3个方法快速恢复文件!

案例&#xff1a;电脑文件恢复怎么操作&#xff1f; 【我的电脑已经好久没用了&#xff0c;最近因为需要查看一些相关的文件才用到电脑&#xff0c;但是我打开后发现里面很多重要的文件都不见了&#xff0c;请问电脑文件应该怎么恢复呢&#xff1f;感谢回答&#xff01;】 电…

AIGC从入门到精通

一键起飞 # 提前安装好python 3.10.9 ​git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui cd stable-diffusion-webui ./webui.sh -f --api --listen --enable-insecure-extension-access 非常详细&#xff01;6000字详解AI绘画文生图干货、技巧&#xf…

【多线程】线程的状态

1. 等待一个线程 join 有一天张三与小美在家里吃饭&#xff0c;张三早早的把饭吃完了&#xff0c;对小美说&#xff0c;等你把饭吃完了&#xff0c;我就去洗碗了&#xff01; 此时张三就要等待小美吃完饭才能去洗碗&#xff0c;就好比线程 A 要等待线程 B 执行完&#xff0c;线…

Ansys Lumerical | 光子集成电路之PN 耗尽型移相器仿真工作流

01 说明 本文旨在介绍Ansys Lumerical针对有源光子集成电路中PN耗尽型移相器的仿真分析方法。通过FDE和CHARGE求解器模拟并计算移相器的性能指标&#xff08;如电容、有效折射率扰动和损耗等&#xff09;&#xff0c;并创建用于INTERCONNECT的紧凑模型&#xff0c;然后将其表征…

vue_03

文章目录 导航菜单功能的实现在Admin.vue中添加下列代码布局选择点击跳转事件 vuex的安装及配置安装配置新建store和index.js在index.js下写如下代码在main.js中引入store 封装菜单请求工具类新建menus.js编写menus.js文件 解决F5刷新数据丢失问题 导航菜单功能的实现 在Admin…

多项开发任务,如何做好任务分配和管理?

1、确定任务清单 任务精细化分解 需要将任务进行精细化分解&#xff0c;每个子任务时间最好不超过一周&#xff0c;明确子任务的目标、时间点和交付物。 多项开发任务&#xff0c;如何做好任务分配和管理&#xff1f; 2、优先级排序 需要将精细化好的任务&#xff0c;进行优先级…

TFTP+Filezilla文件双向传输(2)-ubuntu(VMware)-win10(host)

TFTP&#xff08;Trivial File Transfer Protocol,简单文件传输协议&#xff09;是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议&#xff0c;提供不复杂、开销不大的文件传输服务。端口号为69。 ftpd-hpa是tftp服务器 tftp-hpa是tftp客服端 inetd的全称…

设计尝试用Microsoft Visual Studio 在网络上架构一个电影院网络订票系统

设计尝试用Microsoft Visual Studio 在网络上架构一个电影院网络订票系统的设计&#xff0c;以使每一位客户不用出门就能看到最新的电影信息和预定电影票。本文从理论和实践两个角度出发&#xff0c;对一个具有数据挖掘功能电影院网络订票系统的设计进行设计与实现分析。随着电…

windows下运行dpdk下的helloworld

打开“本地安全策略”管理单元&#xff0c;在搜索框输入secpol。 打开本地策略->用户权限分配->锁定内存页->添加用户或组->高级->立即查找 输入电脑用户名&#xff0c;选择并添加。点击确定后&#xff0c;重启电脑。 安装内核驱动&#xff0c;下载地址https:…

srm 采购管理系统是如何赋能企业降本的?

近年来&#xff0c;随着全球经济的不断发展&#xff0c;企业的竞争也日趋激烈&#xff0c;企业为适应市场需求&#xff0c;加强产品创新和技术升级&#xff0c;加大产品研发和生产投入&#xff0c;然而在新冠肺炎疫情的影响下&#xff0c;许多企业经营出现困境。在这种情况下&a…

基于时间戳和序列号的mac地址生成策略

基于时间戳 基于时间戳生成MAC地址的具体操作步骤如下&#xff1a; 获取设备的出厂日期和时间。一般情况下&#xff0c;这个信息可以在设备的测试报告或者出厂记录中找到。 将设备的出厂日期和时间转换成时间戳格式。时间戳指的是一个从1970年1月1日00:00:00 UTC开始的秒数。…

自动化数据驱动?最全接口自动化测试yaml数据驱动实战,看这一篇就够了

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 我们在做自动化测…

ROS学习第十九节——TF静态坐标变换

1.坐标msg消息 订阅发布模型中数据载体 msg 是一个重要实现&#xff0c;首先需要了解一下&#xff0c;在坐标转换实现中常用的 msg:geometry_msgs/TransformStamped和geometry_msgs/PointStamped 前者用于传输坐标系相关位置信息&#xff0c;后者用于传输某个坐标系内坐标点的…

js面试题

在全局作用域下声明了一个变量 arr, 它的初始值是一个空数组 第二段代码&#xff0c;循环计数器变量i的初始值为0&#xff0c;循环条件是i的值小于2&#xff0c; 也就是说当i的值为0或者1时&#xff0c; 循环条件才能成立 才能够进入到循环体 当i的值为2时循环条件不成立&…

ConcurrentLinkedQueue

唯一一个使用cas实现的线程安全并发效率高的集合。 一、为什么叫松散队列&#xff1f; 链表是松散的&#xff0c;链表节点并不都是有效的&#xff0c;允许存在无效节点valnull&#xff0c;但是只有最后一个节点才能nextnull 为什么线程安全需要把链表做成松散的。就是因为入队…

蓝精灵协会 (The Smurfs‘ Society) 宣布与著名艺术家展开一系列的合作,打造传奇 PFP 系列

4 月 18 日&#xff0c;The Smurfs Society 将推出第一个由 Smurfs 品牌支持的官方 PFP 系列。该系列建立在链上游戏的基础之上&#xff0c;该游戏聚集了超过 85,000 名玩家&#xff0c;并在设计、创意和与著名艺术家的合作方面设立了新标准。 而最近&#xff0c;蓝精灵官方&am…

2023-04-21 学习记录--C/C++-实现升序降序(选择法)

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、选择法 —— 升序 ⭐️ &#xff08;一&#xff09;、思路 从左到右&#xff0c;除最后一个数 依次作为 “当前数” 与 其右…

STM32F4_模数转换器(ADC)详解

目录 1. ADC是什么 2. ADC主要特性 3. ADC框图 3.1 ADC开关控制 3.2 ADC时钟 3.3 通道选择 3.4 单次转换模式和连续转换模式 3.5 时序图 3.6 模拟看门狗 4 温度传感器 5. ADC中断 6. ADC初始化结构体 6.1 ADC相关实验配置 7. 相关寄存器 7.1 ADC控制寄存器&…

Deep Neural Network for YouTube Recommendation论文精读

这篇论文 Deep Neural Networks for YouTube Recommendations 是google的YouTube团队在推荐系统上DNN方面的尝试&#xff0c;发表在16年9月的RecSys会议。本文所介绍的YouTube的推荐系统主要包括Deep Candidate Generation model和Deep Ranking model两个部分&#xff1a;Deep …

AWS EC2使用过程总结

步骤1&#xff1a;开通AWS账号 需要一个邮箱、一个信用卡账号&#xff1b;有第一年的免费试用&#xff0c;EC2每个月免费试用750小时&#xff1b;注册完成后&#xff0c;得到实例管理平台&#xff1a; 步骤2&#xff1a;开通EC2实例 步骤3&#xff1a;开通网关和安全组&…