第十四章 流式编程

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

流式编程采用内部迭代

流是懒加载的。

流支持

Java 8 采用的解决方案是:在接口中添加被 default(默认)修饰的方法。通过这种方案,设计者们可以将流式(stream)方法平滑地嵌入到现有类中。流方法预置的操作几乎已满足了我们平常所有的需求。流操作的类型有三种:创建流修改流元素(中间操作, Intermediate Operations),消费流元素(终端操作, Terminal Operations)。最后一种类型通常意味着收集流元素(通常是到集合中)。

流创建

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

Stream.of(new Bubble(1), new Bubble(2), new Bubble(3)).forEach(System.out::println);
Stream.of("It's ", "a ", "wonderful ", "day ", "for ", "pie!").forEach(System.out::print);
Stream.of(3.14159, 2.718, 1.618).forEach(System.out::println);

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

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

随机数流

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.longs().boxed());
show(rand.doubles().boxed());
// Control the lower and upper bounds:
show(rand.ints(10, 20).boxed());
show(rand.longs(50, 100).boxed());
show(rand.doubles(20, 30).boxed());
// Control the stream size:
show(rand.ints(2).boxed());
show(rand.longs(2).boxed());
show(rand.doubles(2).boxed());
// Control the stream size and bounds:
show(rand.ints(3, 3, 9).boxed());
show(rand.longs(3, 12, 22).boxed());
show(rand.doubles(3, 11.5, 12.3).boxed());
}
}

int 类型的范围

IntStream.range():

System.out.println(IntStream.range(10, 20).sum());
public static void repeat(int n, Runnable action) {
IntStream.range(0, n).forEach(i -> action.run());
}

generate()

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

public class Generator implements Supplier<String> {
Random rand = new Random(47);
char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); @Override
public String 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);
}
}
Stream.generate(() -> "duplicate").limit(3).forEach(System.out::println);

iterate()

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

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) // Don't use the first 20
.limit(10) // Then take 10 of them
.forEach(System.out::println);
}
}

流的建造者模式

public class FileToWordsBuilder {
Stream.Builder<String> builder = Stream.builder(); public FileToWordsBuilder(String filePath) throws Exception {
Files.lines(Paths.get(filePath)).skip(1) // Skip the comment line at the beginning
.forEach(line -> {
for (String w : line.split("[ .?,]+"))
builder.add(w);
});
} Stream<String> stream() {
return builder.build();
} public static void main(String[] args) throws Exception {
new FileToWordsBuilder("Cheese.dat").stream().limit(7).map(w -> w + " ").forEach(System.out::print);
}
}

Arrays

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

public class Machine2 {
public static void main(String[] args) {
Arrays.stream(new Operations[] { () -> Operations.show("Bing"), () -> Operations.show("Crack"),
() -> Operations.show("Twist"), () -> Operations.show("Pop") }).forEach(Operations::execute);
}
}
public class ArrayStreams {
public static void main(String[] args) {
Arrays.stream(new double[] { 3.14159, 2.718, 1.618 }).forEach(n -> System.out.format("%f ", n));
System.out.println();
Arrays.stream(new int[] { 1, 3, 5 }).forEach(n -> System.out.format("%d ", n));
System.out.println();
Arrays.stream(new long[] { 11, 22, 44, 66 }).forEach(n -> System.out.format("%d ", n));
System.out.println();
// Select a subrange:
Arrays.stream(new int[] { 1, 3, 5, 7, 15, 28, 37 }, 3, 6).forEach(n -> System.out.format("%d ", n));
}
}

正则表达式

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

public class FileToWordsRegexp {
private String all; public FileToWordsRegexp(String filePath) throws Exception {
all = Files.lines(Paths.get(filePath)).skip(1) // First (comment) line
.collect(Collectors.joining(" "));
} public Stream<String> stream() {
return Pattern.compile("[ .,?]+").splitAsStream(all);
} public static void main(String[] args) throws Exception {
FileToWordsRegexp fw = new FileToWordsRegexp("Cheese.dat");
fw.stream().limit(7).map(w -> w + " ").forEach(System.out::print);
fw.stream().skip(7).limit(2).map(w -> w + " ").forEach(System.out::print);
}
}

中间操作

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

跟踪和调试

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

class Peeking {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat").skip(21).limit(4).map(w -> w + " ").peek(System.out::print)
.map(String::toUpperCase).peek(System.out::print).map(String::toLowerCase).forEach(System.out::print);
}
}

流元素排序

public class SortedComparator {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat").skip(10).limit(10).sorted(Comparator.reverseOrder()).map(w -> w + " ")
.forEach(System.out::print);
}
}

sorted() 预设了一些默认的比较器。这里我们使用的是反转“自然排序”。当然你也可以把 Lambda 函数作为参数传递给 sorted()

移除元素

  • distinct():在 Randoms.java 类中的 distinct() 可用于消除流中的重复元素。相比创建一个 Set 集合,该方法的工作量要少得多。
  • filter(Predicate):过滤操作会保留与传递进去的过滤器函数计算结果为 true 元素。
public class Prime {
public static boolean isPrime(long n) {
return rangeClosed(2, (long) Math.sqrt(n)).noneMatch(i -> n % i == 0);
} public LongStream numbers() {
return iterate(2, i -> i + 1).filter(Prime::isPrime);
} public static void main(String[] args) {
new Prime().numbers().limit(10).forEach(n -> System.out.format("%d ", n));
System.out.println();
new Prime().numbers().skip(90).limit(10).forEach(n -> System.out.format("%d ", n));
}
}

应用函数到元素

  • map(Function):将函数操作应用在输入流的元素中,并将返回值传递到输出流中。
  • mapToInt(ToIntFunction):操作同上,但结果是 IntStream
  • mapToLong(ToLongFunction):操作同上,但结果是 LongStream
  • mapToDouble(ToDoubleFunction):操作同上,但结果是 DoubleStream

使用 map() 映射多种函数到一个字符串流中:

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 + "]"); 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);
}
}

map() 将一个字符串映射为另一个字符串,但是我们完全可以产生和接收类型完全不同的类型,从而改变流的数据类型。

class Numbered {
final int n; Numbered(int n) {
this.n = n;
} @Override
public String toString() {
return "Numbered(" + n + ")";
}
} class FunctionMap2 {
public static void main(String[] args) {
Stream.of(1, 5, 7, 9, 11, 13).map(Numbered::new).forEach(System.out::println);
}
}

如果使用 Function 返回的结果是数值类型的一种,我们必须使用合适的 mapTo数值类型 进行替代。

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));
}
}

在 map() 中组合流

  • flatMap():获取流产生( stream-producing)函数,并将其应用于新到的元素(如 map() 所做的),然后获取每一个流并将其“扁平”为元素。所以它的输出只是元素。
  • flatMap(Function):当 Function 产生流时使用。
  • flatMapToInt(Function):当 Function 产生 IntStream 时使用。
  • flatMapToLong(Function):当 Function 产生 LongStream 时使用。
  • flatMapToDouble(Function):当 Function 产生 DoubleStream 时使用。
public class StreamOfRandoms {
static Random rand = new Random(47); public static void main(String[] args) {
Stream.of(1, 2, 3, 4, 5).flatMapToInt(i -> IntStream.concat(rand.ints(0, 100).limit(i), IntStream.of(-1)))
.forEach(n -> System.out.format("%d ", n));
}
}

Optional类

Optional 可以实现这样的功能。

可作为流元素的持有者,即使查看的元素不存在也能友好的提示我们(也就是说,没有异常)。

首先确保准流操作返回 Optional 对象,因为它们并不能保证预期结果一定存在。它们包括:

  • findFirst() 返回一个包含第一个元素的 Optional 对象,如果流为空则返回 Optional.empty
  • findAny() 返回包含任意元素的 Optional 对象,如果流为空则返回 Optional.empty
  • max()min() 返回一个包含最大值或者最小值的 Optional 对象,如果流为空则返回 Optional.empty
  • reduce() 不再以 identity 形式开头,而是将其返回值包装在 Optional 中。(identity 对象成为其他形式的 reduce() 的默认结果,因此不存在空结果的风险)
  • 对于数字流 IntStream、LongStream 和 DoubleStream,average() 会将结果包装在 Optional 以防止流为空。
class OptionalsFromEmptyStreams {
public static void main(String[] args) {
System.out.println(Stream.<String>empty().findFirst());
System.out.println(Stream.<String>empty().findAny());
System.out.println(Stream.<String>empty().max(String.CASE_INSENSITIVE_ORDER));
System.out.println(Stream.<String>empty().min(String.CASE_INSENSITIVE_ORDER));
System.out.println(Stream.<String>empty().reduce((s1, s2) -> s1 + s2));
System.out.println(IntStream.empty().average());
}
}

当你接收到 Optional 对象时,应首先调用 isPresent() 检查其中是否包含元素。如果存在,可使用 get() 获取。

class OptionalBasics {
static void test(Optional<String> optString) {
if (optString.isPresent())
System.out.println(optString.get());
else
System.out.println("Nothing inside!");
} public static void main(String[] args) {
test(Stream.of("Epithets").findFirst());
test(Stream.<String>empty().findFirst());
}
}

便利函数

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

  • ifPresent(Consumer):当值存在时调用 Consumer,否则什么也不做。
  • orElse(otherObject):如果值存在则直接返回,否则生成 otherObject。
  • orElseGet(Supplier):如果值存在直接生成对象,否则使用 Supplier 函数生成一个可替代对象。
  • orElseThrow(Supplier):如果值存在直接生成对象,否则使用 Supplier 函数生成一个异常。
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);
}
}

创建 Optional

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

  • empty():生成一个空 Optional。
  • of(value):将一个非空值包装到 Optional 里。
  • ofNullable(value):针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中。
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));
}
}

Optional 对象操作

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

  • filter(Predicate):将 Predicate 应用于 Optional 中的内容并返回结果。当 Optional 不满足 Predicate 时返回空。如果 Optional 为空,则直接返回。
  • map(Function):如果 Optional 不为空,应用 Function 于 Optional 中的内容,并返回结果。否则直接返回 Optional.empty。
  • flatMap(Function):同 map(),但是提供的映射函数将结果包装在 Optional 对象中,因此 flatMap() 不会在最后进行任何包装。

一般来说,流的 filter() 会在 Predicate 返回 false 时删除流元素。而 Optional.filter() 在失败时不会删除 Optional,而是将其保留下来,并转化为空。

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

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

Optional 流

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));
}
}
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);
}
}

终端操作

这些操作获取一个流并产生一个最终结果;它们不会像后端流提供任何东西。因此,终端操作总是你在管道中做的最后一件事情。

转化成数组(Convert to an Array)

  • toArray():将流转换成适当类型的数组。
  • toArray(generator):在特殊情况下,生成器用于分配你自己的数组存储。
public class RandInts {
private static int[] rints = new Random(47).ints(0, 1000).limit(100).toArray(); public static IntStream rands() {
return Arrays.stream(rints);
}
}

对每个元素应用最终操作(Apply a Final Operation to Every Element)

  • forEach(Consumer):你已经看到很多次 System.out::println 作为 Consumer 函数。
  • forEachOrdered(Consumer): 这个形式保证了 forEach 的操作顺序是原始流顺序。
public class ForEach {
static final int SZ = 1000; public static void main(String[] args) {
rands().limit(SZ).forEach(n -> System.out.format("%d ", n));
System.out.println();
rands().limit(SZ).parallel().forEach(n -> System.out.format("%d ", n));
System.out.println();
rands().limit(SZ).parallel().forEachOrdered(n -> System.out.format("%d ", n));
rands().limit(SZ).parallel().forEachOrdered(n -> {
System.out.println(Thread.currentThread().getName() + " -=- " + n);
});
}
}

收集(Collecting)

  • collect(Collector):使用 Collector 来累计流元素到结果集合中。
  • collect(Supplier, BiConsumer, BiConsumer):同上,但是 Supplier 创建了一个新的结果集合,第一个 BiConsumer 是将下一个元素包含在结果中的函数,而第二个 BiConsumer 是用于将两个值组合起来。

组合所有流元素(Combining All Stream Elements)

  • reduce(BinaryOperator):使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional。
  • reduce(identity, BinaryOperator):功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果。
  • reduce(identity, BiFunction, BinaryOperator):这个形式更为复杂(所以我们不会介绍它),在这里被提到是因为它使用起来会更有效。通常,你可以显示的组合 map() 和 reduce() 来更简单的表达这一点。

匹配(Matching)

  • allMatch(Predicate) :如果流的每个元素根据提供的 Predicate 都返回 true 时,结果返回为 true。这个操作将会在第一个 false 之后短路;也就是不会在发生 false 之后继续执行计算。
  • anyMatch(Predicate):如果流中的一个元素根据提供的 Predicate 返回 true 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。
  • noneMatch(Predicate):如果流的每个元素根据提供的 Predicate 都返回 false 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。
interface Matcher extends BiPredicate<Stream<Integer>, Predicate<Integer>> {
} public class Matching {
static void show(Matcher match, int val) {
System.out.println(
match.test(IntStream.rangeClosed(1, 9).boxed().peek(n -> System.out.format("%d ", n)), n -> n < val));
} public static void main(String[] args) {
show(Stream::allMatch, 10);
show(Stream::allMatch, 4);
show(Stream::anyMatch, 2);
show(Stream::anyMatch, 0);
show(Stream::noneMatch, 5);
show(Stream::noneMatch, 0);
}
}

选择元素

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

信息(Informational)

  • count():流中的元素个数。
  • max(Comparator):根据所传入的 Comparator 所决定的“最大”元素。
  • min(Comparator):根据所传入的 Comparator 所决定的“最小”元素。
public class Informational {
public static void main(String[] args) throws Exception {
System.out.println(FileToWords.stream("Cheese.dat").count());
System.out.println(FileToWords.stream("Cheese.dat").min(String.CASE_INSENSITIVE_ORDER).orElse("NONE"));
System.out.println(FileToWords.stream("Cheese.dat").max(String.CASE_INSENSITIVE_ORDER).orElse("NONE"));
}
}

数字流信息(Information for Numeric Streams)

  • average() :求取流元素平均值。
  • max()min():因为这些操作在数字流上面,所以不需要 Comparator。
  • sum():对所有流元素进行求和。
  • summaryStatistics():生成可能有用的数据。目前还不太清楚他们为什么觉得有必要这样做,但是你可以直接使用方法产生所有的数据。
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());
}
}

20190827 On Java8 第十四章 流式编程的更多相关文章

  1. CSS3秘笈复习:十三章&十四章&十五章&十六章&十七章

    第十三章 1.在使用浮动时,源代码的顺序非常重要.浮动元素的HTML必须处在要包围它的元素的HTML之前. 2.清楚浮动: (1).在外围div的底部添加一个清除元素:clear属性可以防止元素包围浮 ...

  2. 第十四章——循环神经网络(Recurrent Neural Networks)(第一部分)

    由于本章过长,分为两个部分,这是第一部分. 这几年提到RNN,一般指Recurrent Neural Networks,至于翻译成循环神经网络还是递归神经网络都可以.wiki上面把Recurrent ...

  3. 【C++】《C++ Primer 》第十四章

    第十四章 重载运算与类型转换 一.基本概念 重载运算符是具有特殊名字的函数:由关键字operator和其后要定义的运算符号共同组成.也包含返回类型.参数列表以及函数体. 当一个重载的运算符是成员函数时 ...

  4. 【odoo14】第十四章、CMS网站开发

    第十四章.CMS网站开发** Odoo有一个功能齐全的内容管理系统(CMS).通过拖放功能,你的最终用户可以在几分钟内设计一个页面,但是在Odoo CMS中开发一个新功能或构建块就不是那么简单了.在本 ...

  5. 《Linux命令行与shell脚本编程大全》 第十四章 学习笔记

    第十四章:呈现数据 理解输入与输出 标准文件描述符 文件描述符 缩写 描述 0 STDIN 标准输入 1 STDOUT 标准输出 2 STDERR 标准错误 1.STDIN 代表标准输入.对于终端界面 ...

  6. perl 第十四章 Perl5的包和模块

    第十四章 Perl5的包和模块 by flamephoenix 一.require函数  1.require函数和子程序库  2.用require指定Perl版本二.包  1.包的定义  2.在包间切 ...

  7. Gradle 1.12 翻译——第十四章. 教程 - 杂七杂八

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

  8. C和指针 (pointers on C)——第十四章:预处理器

    第十四章 预处理器 我跳过了先进的指针主题的章节. 太多的技巧,太学科不适合今天的我.但我真的读,读懂.假设谁读了私下能够交流一下.有的小技巧还是非常有意思. 预处理器这一章的内容.大家肯定都用过.什 ...

  9. JavaScript高级程序设计:第十四章

    第十四章 一.表单的基础知识 在HTML中,表单是由<form>元素来表示的,而在javascript中,表单对应的则是HTMLFormElement类型.HTMLFormElement继 ...

随机推荐

  1. 认识一下Qt用到的开发工具

    http://c.biancheng.net/view/3868.html Qt 不是凭空产生的,它是基于现有工具链打造而成的,它所使用的编译器.链接器.调试器等都不是自己的,Qt 官方只是开发了上层 ...

  2. SolrCloud集群

    1 SolrCloud简介 1.1什么是SolrCloud SolrCloud(solr 云)是 Solr 提供的分布式搜索方案,当你需要大规模,容错,分布式索引和检索能力时使用 SolrCloud. ...

  3. 二分图最大匹配(匈牙利算法)简介& Example hdu 1150 Machine Schedule

    二分图匹配(匈牙利算法) 1.一个二分图中的最大匹配数等于这个图中的最小点覆盖数 König定理是一个二分图中很重要的定理,它的意思是,一个二分图中的最大匹配数等于这个图中的最小点覆盖数.如果你还不知 ...

  4. C#的Class的几个修饰符

    none or internal     类只能在当前的工程中访问 Public                    类可以在任何地方访问 abstract or internal abstract ...

  5. STM32 JTAG接口SWD下载接线图

  6. DevExpress v18.2版本亮点——Analytics Dashboard篇(一)

    行业领先的.NET界面控件——DevExpress v18.2版本亮点详解,本文将介绍了DevExpress Analytics Dashboard v18.2 的版本亮点,新版30天免费试用!点击下 ...

  7. 【leetcode】1171. Remove Zero Sum Consecutive Nodes from Linked List

    题目如下: Given the head of a linked list, we repeatedly delete consecutive sequences of nodes that sum ...

  8. ubuntu16.04 下 C# mono开发环境搭建

    本文转自:https://www.cnblogs.com/2186009311CFF/p/9204031.html 前记 之前我一直不看好C#的前景,因为我认为它只能在windows下运行,不兼容,对 ...

  9. CSS3制作太极图以及用JS实现旋转太极图

     太极图可以理解为一个一半黑一半白的半圆,上面放置着两个圆形,一个黑色边框白色芯,一个白色边框黑色芯. 1.实现黑白各半的圆形. .box{ width:200px;height:200px; bor ...

  10. React Native 之FlatList

    1.新建项目 2.因为要用到导航跳转, 所以添加依赖,,这里拷贝这个: "dependencies": { "@types/react": "^16. ...