3分钟看完Java 8——史上最强Java 8新特性总结之第二篇 Stream API
目录
· 概况
· 构建流
· 由值创建流
· 由数组创建流
· 由文件生成流
· 并行流
概况
1. Stream API:以声明性方式处理数据集合,即说明想要完成什么(比如筛选热量低的菜肴)而不是说明如何实现一个操作(利用循环和if条件等控制流语句)。
2. Stream API特点
a) 流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。这让可实现延迟和短路优化。
b) 内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。
3. Stream(流):从支持数据处理操作的源生成的元素序列(A sequence of elements from a source that supports data processing operations)。
a) 元素序列:与集合类似,流也提供了一个接口(java.util.stream.Stream),可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如ArrayList、LinkedList);但流的目的在于表达计算,比如filter、sorted和map。
b) 源:流会使用一个提供数据的源,如集合、数组或输入/输出。注意,从有序集合生成流时会保留原有的顺序。
c) 数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可并行执行。
4. 流操作分类
a) 中间操作(Intermediate Operations):可以连接起来的流操作,并不会生成任何结果。
b) 终端操作(Terminal Operations):关闭流的操作,处理流水线以返回结果。
c) 常用中间操作
操作 |
返回类型 |
操作参数 |
函数描述符 |
filter |
Stream<T> |
Predicate<T> |
T -> boolean |
map |
Stream<R> |
Function<T, R> |
T -> R |
limit |
Stream<T> |
||
sorted |
Stream<T> |
Comparator<T> |
(T, T) -> R |
distinct |
Stream<T> |
d) 常用终端操作
操作 |
目的 |
forEach |
消费流中的每个元素并对其应用Lambda。这一操作返回void。 |
count |
返回流中元素的个数。这一操作返回long。 |
collect |
把流归约成一个集合,比如List、Map甚至是Integer。 |
5. 举例
a) Dish.java(后续举例将多次使用到该类)
- public class Dish {
- private final String name;
- private final boolean vegetarian;
- private final int calories;
- private final Type type;
- public enum Type {MEAT, FISH, OTHER}
- public Dish(String name, boolean vegetarian, int calories, Type type) {
- this.name = name;
- this.vegetarian = vegetarian;
- this.calories = calories;
- this.type = type;
- }
- public String getName() {
- return name;
- }
- public boolean isVegetarian() {
- return vegetarian;
- }
- public int getCalories() {
- return calories;
- }
- public Type getType() {
- return type;
- }
- @Override
- public String toString() {
- return name;
- }
- }
b) DishUtils.java(后续举例将多次使用到该类)
- import java.util.Arrays;
- import java.util.List;
- public class DishUtils {
- public static List<Dish> makeMenu() {
- return Arrays.asList(
- new Dish("pork", false, 800, Dish.Type.MEAT),
- new Dish("beef", false, 700, Dish.Type.MEAT),
- new Dish("chicken", false, 400, Dish.Type.MEAT),
- new Dish("french fries", true, 530, Dish.Type.OTHER),
- new Dish("rice", true, 350, Dish.Type.OTHER),
- new Dish("season fruit", true, 120, Dish.Type.OTHER),
- new Dish("pizza", true, 550, Dish.Type.OTHER),
- new Dish("prawns", false, 300, Dish.Type.FISH),
- new Dish("salmon", false, 450, Dish.Type.FISH));
- }
- public static <T> void printList(List<T> list) {
- for (T i : list) {
- System.out.println(i);
- }
- }
- }
c) Test.java
- import java.util.List;
- import static java.util.stream.Collectors.toList;
- public class Test {
- public static void main(String[] args) {
- List<String> names = DishUtils.makeMenu().stream() // 获取流
- .filter(d -> d.getCalories() > 300) // 中间操作,选出高热量菜
- .map(Dish::getName) // 中间操作,获取菜名
- .limit(3) // 中间操作,选出前三
- .collect(toList()); // 终端操作,将结果保存在List中
- DishUtils.printList(names);
- DishUtils.makeMenu().stream()
- .filter(d -> d.getCalories() > 300)
- .map(Dish::getName)
- .limit(3)
- .forEach(System.out::println); // 遍历并打印
- }
- }
d) 示意图
筛选(Filtering)
1. 筛选相关方法
a) filter()方法:使用Predicate筛选流中元素。
b) distinct()方法:调用流中元素的hashCode()和equals()方法去重元素。
2. 举例
- import java.util.Arrays;
- import java.util.List;
- import static java.util.stream.Collectors.toList;
- // filter()方法
- List<Dish> vegetarianMenu = DishUtils.makeMenu().stream()
- .filter(Dish::isVegetarian)
- .collect(toList());
- DishUtils.printList(vegetarianMenu);
- System.out.println("-----");
- // 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);
切片(Slicing)
1. 切片相关方法
a) limit()方法:返回一个不超过给定长度的流。
b) skip()方法:返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。
2. 举例
- import java.util.List;
- import static java.util.stream.Collectors.toList;
- // limit()方法
- List<Dish> dishes1 = DishUtils.makeMenu().stream()
- .filter(d -> d.getCalories() > 300)
- .limit(3)
- .collect(toList());
- DishUtils.printList(dishes1);
- System.out.println("-----");
- // skip()方法
- List<Dish> dishes2 = DishUtils.makeMenu().stream()
- .filter(d -> d.getCalories() > 300)
- .skip(2)
- .collect(toList());
- DishUtils.printList(dishes2);
映射(Mapping)
1. 映射相关方法
a) map()方法:接受一个函数作为参数,该函数用于将每个元素映射成一个新的元素。
b) flatMap()方法:接受一个函数作为参数,该函数用于将每个数组元素映射成新的扁平化流。
c) 注意:map()、flatMap()方法都不会修改原元素。
2. 举例
- import java.util.Arrays;
- import java.util.List;
- import static java.util.stream.Collectors.toList;
- // map()方法
- List<Integer> dishNameLengths = DishUtils.makeMenu().stream()
- .map(Dish::getName)
- .map(String::length)
- .collect(toList());
- DishUtils.printList(dishNameLengths);
- System.out.println("-----");
- // flatMap()方法
- String[] arrayOfWords = {"Goodbye", "World"};
- Arrays.stream(arrayOfWords)
- .map(w -> w.split("")) // 将每个单词转换为由其字母构成的数组
- .flatMap(Arrays::stream) // 将各个生成流扁平化为单个流
- .distinct() // 去重
- .forEach(System.out::println);
匹配(Matching)
1. 匹配相关方法
a) anyMatch()方法:检查流中是否有一个元素能匹配给定的Predicate。
b) allMatch()方法:检查流中是否所有元素能匹配给定的Predicate。
c) noneMatch()方法:检查流中是否所有元素都不匹配给定的Predicate。
2. 举例
- // anyMatch()方法
- if (DishUtils.makeMenu().stream().anyMatch(Dish::isVegetarian)) {
- System.out.println("The menu is (somewhat) vegetarian friendly!!");
- }
- // allMatch()方法
- boolean isHealthy1 = DishUtils.makeMenu().stream()
- .allMatch(d -> d.getCalories() < 1000);
- System.out.println(isHealthy1);
- // noneMatch()方法
- boolean isHealthy2 = DishUtils.makeMenu().stream()
- .noneMatch(d -> d.getCalories() >= 1000);
- System.out.println(isHealthy2);
查找(Finding)
1. 查找相关方法
a) findAny()方法:返回当前流中的任意元素,返回类型为java.util.Optional(Java 8用于解决NullPointerException的新类)。
b) findFirst()方法:与findAny()方法类似,区别在于返回第一个元素。
2. 举例
- import java.util.Arrays;
- import java.util.List;
- import java.util.Optional;
- // findAny()方法
- Optional<Dish> dish = DishUtils.makeMenu().stream()
- .filter(Dish::isVegetarian)
- .findAny();
- System.out.println(dish.get()); // french fries
- // 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(); //
- System.out.println(firstSquareDivisibleByThree.get());
归约(Reducing)
1. 归约相关方法
a) reduce()方法:把一个流中的元素组合起来,也叫折叠(fold)。
i. 如果指定初始值,则直接返回归约结果值。
ii. 如果不指定初始值,则返回Optional。
2. 举例
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Optional;
- List<Integer> numbers = new ArrayList<>();
- for (int n = 1; n <= 100; n++) {
- numbers.add(n);
- }
- // 元素求和
- int sum1 = numbers.stream().reduce(0, (a, b) -> a + b); // 指定初始值0
- System.out.println(sum1);
- Optional<Integer> sum2 = numbers.stream().reduce((a, b) -> a + b); // 不指定初始值0
- System.out.println(sum2);
- int sum3 = numbers.stream().reduce(0, Integer::sum); // 方法引用
- System.out.println(sum3);
- // 最大值
- Optional<Integer> max1 = numbers.stream().reduce((a, b) -> a < b ? b : a); // Lambda表达式
- System.out.println(max1);
- Optional<Integer> max2 = numbers.stream().reduce(Integer::max); // 方法引用
- System.out.println(max2);
- // 统计个数
- int count1 = DishUtils.makeMenu().stream()
- .map(d -> 1)
- .reduce(0, (a, b) -> a + b); // MapReduce编程模型,更易并行化
- System.out.println(count1);
- long count2 = DishUtils.makeMenu().stream().count();
- System.out.println(count2);
排序(Sorting)
1. 排序相关方法
a) sorted()方法:根据指定的java.util.Comparator规则排序。
2. 举例
- import static java.util.Comparator.comparing;
- DishUtils.makeMenu().stream()
- .sorted(comparing(Dish::getCalories))
- .forEach(System.out::println);
数值流(Numeric streams)
原始类型流(Primitive stream)
1. 使用目的:避免自动装箱带来的开销。
2. 相关方法
a) mapToInt():将流转换为原始类型流IntStream。
b) mapToDouble():将流转换为原始类型流DoubleStream。
c) mapToLong():将流转换为原始类型流LongStream。
d) boxed():将原始类型流转换为对象流。
3. Optional的原始类型版本:OptionalInt、OptionalDouble和OptionalLong。
4. 举例
- import java.util.OptionalInt;
- import java.util.stream.IntStream;
- import java.util.stream.Stream;
- // 映射到数值流
- int calories = DishUtils.makeMenu().stream() // 返回Stream<Dish>
- .mapToInt(Dish::getCalories) // 返回IntStream
- .sum();
- System.out.println(calories);
- // 转换回对象流
- IntStream intStream = DishUtils.makeMenu().stream().mapToInt(Dish::getCalories); // 将Stream 转换为数值流
- Stream<Integer> stream = intStream.boxed(); // 将数值流转换为Stream
- // OptionalInt
- OptionalInt maxCalories = DishUtils.makeMenu().stream()
- .mapToInt(Dish::getCalories)
- .max();
- int max = maxCalories.orElse(1); // 如果没有最大值的话,显式提供一个默认最大值
- System.out.println(max);
数值范围(Numeric ranges)
1. 数值范围相关方法
a) range()方法:生成起始值到结束值范围的数值,不包含结束值。
b) rangeClosed()方法:生成起始值到结束值范围的数值,包含结束值。
2. 举例
- import java.util.stream.IntStream;
- IntStream.range(1, 5).forEach(System.out::println); // 1~4
- IntStream.rangeClosed(1, 5).forEach(System.out::println); // 1~5
构建流
由值创建流
1. 举例
a) Stream.of()方法
- import java.util.stream.Stream;
- Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
- stream.map(String::toUpperCase).forEach(System.out::println);
b) 空流
- import java.util.stream.Stream;
- Stream<String> emptyStream = Stream.empty();
由数组创建流
1. 举例
- int[] numbers = {2, 3, 5, 7, 11, 13};
- int sum = Arrays.stream(numbers).sum();
- System.out.println(sum); //
由文件生成流
1. 举例
- try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
- long uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
- .distinct()
- .count();
- System.out.println(uniqueWords);
- } catch (IOException e) {
- e.printStackTrace();
- }
由函数生成流(创建无限流)
1. 无限流:没有固定大小的流。
2. 相关方法
a) Stream.iterate()方法:生成无限流,其初始值为第1个参数,下一个值由第2个参数的Lambda表达式生成。
b) Stream.generate()方法:生成无限流,其值由参数的Lambda表达式生成。
3. 注意:一般,应该使用limit(n)对无限流加以限制,以避免生成无穷多个值。
4. 举例
- Stream.iterate(0, n -> n + 2)
- .limit(5)
- .forEach(System.out::println); // 0 2 4 6 8
- Stream.generate(Math::random)
- .limit(5)
- .forEach(System.out::println);
collect()高级用法
归约和汇总(Reducing and summarizing)
1. 举例
a) 按元素某字段查找最大值
- import java.util.Comparator;
- import java.util.Optional;
- import static java.util.stream.Collectors.maxBy;
- Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
- Optional<Dish> mostCalorieDish = DishUtils.makeMenu().stream()
- .collect(maxBy(dishCaloriesComparator));
- System.out.println(mostCalorieDish);
b) 按元素某字段求和
- import static java.util.stream.Collectors.summingInt;
- int totalCalories = DishUtils.makeMenu().stream().collect(summingInt(Dish::getCalories));
- System.out.println(totalCalories);
c) 按元素某字段求平均值
- import static java.util.stream.Collectors.averagingInt;
- double avgCalories = DishUtils.makeMenu().stream().collect(averagingInt(Dish::getCalories));
- System.out.println(avgCalories);
d) 连接字符串
- import static java.util.stream.Collectors.joining;
- String shortMenu = DishUtils.makeMenu().stream().map(Dish::getName).collect(joining(", "));
- System.out.println(shortMenu);
e) 广义归约
- // 所有热量求和
- import static java.util.stream.Collectors.reducing;
- // i.e.
- // int totalCalories = DishUtils.makeMenu().stream()
- // .mapToInt(Dish::getCalories) // 转换函数
- // .reduce(0, Integer::sum); // 初始值、累积函数
- int totalCalories = DishUtils.makeMenu().stream()
- .collect(reducing(
- 0, // 初始值
- Dish::getCalories, // 转换函数
- Integer::sum)); // 累积函数
- System.out.println(totalCalories);
分组(Grouping)
1. 分组:类似SQL语句的group by,区别在于这里的分组可聚合(即SQL的聚合函数),也可不聚合。
2. 举例
a) 简单分组
- Map<Dish.Type, List<Dish>> dishesByType = DishUtils.makeMenu().stream()
- .collect(groupingBy(Dish::getType));
- System.out.println(dishesByType); // {FISH=[prawns, salmon], MEAT=[pork, beef, chicken], OTHER=[french fries, rice, season fruit, pizza]}
b) 复杂分组
- import static java.util.stream.Collectors.groupingBy;
- public enum CaloricLevel {DIET, NORMAL, FAT}
- Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = DishUtils.makeMenu().stream().collect(
- groupingBy(dish -> {
- if (dish.getCalories() <= 400) return CaloricLevel.DIET;
- else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
- else return CaloricLevel.FAT;
- }));
- System.out.println(dishesByCaloricLevel); // {NORMAL=[beef, french fries, pizza, salmon], DIET=[chicken, rice, season fruit, prawns], FAT=[pork]}
c) 多级分组
- import static java.util.stream.Collectors.groupingBy;
- public enum CaloricLevel {DIET, NORMAL, FAT}
- Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = DishUtils.makeMenu().stream().collect(
- groupingBy(Dish::getType, // 一级分类函数
- groupingBy(dish -> { // 二级分类函数
- if (dish.getCalories() <= 400) return CaloricLevel.DIET;
- else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
- else return CaloricLevel.FAT;
- })
- )
- );
- System.out.println(dishesByTypeCaloricLevel);
- // {FISH={NORMAL=[salmon], DIET=[prawns]}, MEAT={NORMAL=[beef], DIET=[chicken], FAT=[pork]}, OTHER={NORMAL=[french fries, pizza], DIET=[rice, season fruit]}}
d) 分组聚合
- import static java.util.Comparator.comparingInt;
- import static java.util.stream.Collectors.groupingBy;
- import static java.util.stream.Collectors.counting;
- Map<Dish.Type, Long> typesCount = DishUtils.makeMenu().stream()
- .collect(groupingBy(Dish::getType, counting()));
- System.out.println(typesCount); // {FISH=2, MEAT=3, OTHER=4}
- Map<Dish.Type, Optional<Dish>> mostCaloricByType1 = DishUtils.makeMenu().stream()
- .collect(groupingBy(Dish::getType, maxBy(comparingInt(Dish::getCalories))));
- System.out.println(mostCaloricByType1); // {FISH=Optional[salmon], MEAT=Optional[pork], OTHER=Optional[pizza]}
- Map<Dish.Type, Dish> mostCaloricByType2 = DishUtils.makeMenu().stream()
- .collect(groupingBy(Dish::getType, // 分类函数
- collectingAndThen(
- maxBy(comparingInt(Dish::getCalories)), // 包装后的收集器
- Optional::get))); // 转换函数
- System.out.println(mostCaloricByType2); // {FISH=salmon, MEAT=pork, OTHER=pizza}
分区(Partitioning)
1. 分区:分区是分组的特殊情况,即根据Predicate<T>分组为true和false两组,因此分组后的Map的Key是Boolean类型。
2. 举例
- import java.util.List;
- import java.util.Map;
- import java.util.Optional;
- import static java.util.Comparator.comparingInt;
- import static java.util.stream.Collectors.*;
- Map<Boolean, List<Dish>> partitionedMenu = DishUtils.makeMenu().stream()
- .collect(partitioningBy(Dish::isVegetarian));
- System.out.println(partitionedMenu);
- // {false=[pork, beef, chicken, prawns, salmon], true=[french fries, rice, season fruit, pizza]}
- Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType = DishUtils.makeMenu().stream()
- .collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));
- System.out.println(vegetarianDishesByType);
- // {false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]}, true={OTHER=[french fries, rice, season fruit, pizza]}}
- Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = DishUtils.makeMenu().stream()
- .collect(partitioningBy(Dish::isVegetarian, collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get)));
- System.out.println(mostCaloricPartitionedByVegetarian);
- // {false=pork, true=pizza}
并行流
1. 并行流:一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
2. 并行流相关方法
a) parallel()方法:将顺序流转换为并行流。
b) sequential()方法:将并行流转换为顺序流。
c) 以上两方法并没有对流本身有任何实际的变化,只是在内部设了一个boolean标志,表示让调用parallel()/sequential()之后进行的所有操作都并行/顺序执行。
3. 并行流原理:并行流内部默认使用ForkJoinPool,其默认的线程数为CPU核数(通过Runtime.getRuntime().availableProcessors()获取),同时支持通过系统属性设置(全局),比如:
- System.setProperty('java.util.concurrent.ForkJoinPool.common.parallelism','12');
4. 何时并行流更有效?
a) 实测:在待运行的特定机器上,分别用顺序流和并行流做基准测试性能。
b) 注意装/拆箱:自动装箱和拆箱会大大降低性能,应避免。
c) 某些操作性能并行流比顺序流差:比如limit()和findFirst(),因为在并行流上执行代价较大。
d) 计算流操作流水线的总成本:设N是要处理的元素的总数,Q是一个元素通过流水线的大致处理成本,则N*Q就是这个对成本的一个粗略的定性估计。Q值较高就意味着使用并行流时性能好的可能性比较大。
e) 数据量较小时并行流比顺序流性能差:因为并行化会有额外开销。
f) 流背后的数据结构是否易于分解:见下表。
数据结构 |
可分解性 |
ArrayList |
极佳 |
LinkedList |
差 |
IntStream.range |
极佳 |
Stream.iterate |
差 |
HashSet |
好 |
TreeSet |
好 |
g) 流自身特点、流水线的中间操作修改流的方式,都可能会改变分解过程的性能:比如未执行筛选操作时,流被分成大小差不多的几部分,此时并行执行效率很高;但执行筛选操作后,可能导致这几部分大小相差较大,此时并行执行效率就较低。
h) 终端操作合并步骤的代价:如果该步骤代价很大,那么合并每个子流产生的部分结果所付出的代价就可能会超出通过并行流得到的性能提升。
5. 举例
- // 顺序流
- long sum1 = Stream.iterate(1L, i -> i + 1)
- .limit(8)
- .reduce(0L, Long::sum);
- System.out.println(sum1);
- // 并行流
- long sum2 = Stream.iterate(1L, i -> i + 1)
- .limit(8)
- .parallel()
- .reduce(0L, Long::sum);
- System.out.println(sum2);
作者:netoxi
出处:http://www.cnblogs.com/netoxi
本文版权归作者和博客园共有,欢迎转载,未经同意须保留此段声明,且在文章页面明显位置给出原文连接。欢迎指正与交流。
3分钟看完Java 8——史上最强Java 8新特性总结之第二篇 Stream API的更多相关文章
- 3分钟看完Java 8——史上最强Java 8新特性总结之第四篇 其他新特性
目录 · 默认方法和静态方法 · 初步理解 · 应用模式 · 优先级问题 · Optional · CompletableFuture · 基本用法 · CompletableFuture与Strea ...
- 3分钟看完Java 8——史上最强Java 8新特性总结之第三篇 函数式编程技巧
目录 · 改写设计模式 · 策略模式(Strategy Pattern) · 模板方法模式(Template Method Pattern) · 观察者模式(Observer Pattern) · 责 ...
- 3分钟看完Java 8——史上最强Java 8新特性总结之第一篇 函数式编程基础
目录 · 行为参数化 · Lambda表达式 · 概况 · 函数式接口 · 类型推断 · 使用外层变量 · 方法引用 · 复合Lambda表达式 行为参数化 1. 理解函数式编程要先理解行为参数化. ...
- 史上最强Java NIO入门:担心从入门到放弃的,请读这篇!
本文原题“<NIO 入门>,作者为“Gregory M. Travis”,他是<JDK 1.4 Tutorial>等书籍的作者. 1.引言 Java NIO是Java 1.4版 ...
- 金九银十,史上最强 Java 面试题整理。
以下会重新整理所有 Java 系列面试题答案.及各大互联网公司的面试经验,会从以下几个方面汇总,本文会长期更新. Java 面试篇 史上最全 Java 面试题,带全部答案 史上最全 69 道 Spri ...
- Java 8新特性之旅:使用Stream API处理集合
在这篇“Java 8新特性教程”系列文章中,我们会深入解释,并通过代码来展示,如何通过流来遍历集合,如何从集合和数组来创建流,以及怎么聚合流的值. 在之前的文章“遍历.过滤.处理集合及使用Lambda ...
- Java 8新特性(二):Stream API
本篇文章继续介绍Java 8的另一个新特性--Stream API.新增的Stream API与InputStream和OutputStream是完全不同的概念,Stream API是对Java中集合 ...
- 【Java8新特性】关于Java8的Stream API,看这一篇就够了!!
写在前面 Java8中有两大最为重要的改变.第一个是 Lambda 表达式:另外一个则是 Stream API(java.util.stream.*) ,那什么是Stream API呢?Java8中的 ...
- 史上最强Java开发环境搭建
在项目产品开发中,开发环境搭建是软件开发的首要阶段,也是必须阶段,只有开发环境搭建好了,方可进行开发,良好的开发环境搭建,为后续的开发工作带来极大便利. 对于大公司来说,软件开发环境搭建工作一般是由运 ...
随机推荐
- PyCharm 安装教程(Windows)
python教程 http://www.runoob.com/python3/python3-basic-syntax.html PyCharm 是一款功能强大的 Python 编辑器,具有跨平台性, ...
- .net读取excel数据到DataSet中
Dim objExcelFile As Excel.Application Dim objWorkBook As Excel.Workbook Dim objSheet As Excel.Worksh ...
- Spring流行的十大理由
Spring大概是每个JAVA程序员都听过的框架,但是它为什么能这么流行? 听到咕泡学院的Tom老师的公开课,下面是他总结的阿里为什么选择Spring的十大理由,我觉得这也是Spring能流行的原因: ...
- Windows批处理命令学习中遇到的坑--持续更新中
再次拾起windows批处理命令,下边将一些遇到的小问题写出来,希望可以帮到大家 1.set命令:set主要的作用是为变量赋值,类似于编程语言中的var i = Value:但是在使用的过程中一定要注 ...
- Note on Preliminary Introduction to Distributed System
今天读了几篇分布式相关的内容,记录一下.非经典论文,非系统化阅读,非严谨思考和总结.主要的着眼点在于分布式存储:好处是,跨越单台物理机器的计算和存储能力的限制,防止单点故障(single point ...
- JavaScriptDOM
DOM简介 1.HTML DOM:网页被加载时,浏览器会创建文档对象模型 2.DOM操作HTML:改变HTML的元素.属性.CSS样式.对所有事件作出反应 DOM操作HTML 1.改变HTML输出流 ...
- python学习笔记-调用接口
requests模块 1.get请求: url = 'http://api.nnzhp.cn/api/user/stu_info' data = {'stu_name':'小黑'} #请求数据 req ...
- Python之配置日志的几种方式(logging模块)
原文:https://blog.csdn.net/WZ18810463869/article/details/81147167 作为开发者,我们可以通过以下3种方式来配置logging: 1)使用Py ...
- 抄一篇maven的备忘
标注下来源:http://www.trinea.cn/android/maven/ 由浅入深,主要介绍maven的用途.核心概念(Pom.Repositories.Artifact.Build Lif ...
- Android学习笔记(1):常用按钮点击事件处理方式
1.从布局文件获取对应的控件然后对其添加点击监听器. Button loginBtn; @Override protected void onCreate(Bundle savedInstanceSt ...