1 Lamdba优于匿名内部类

(1)DEMO1

  • 匿名内部类:过时
  1. Collections.sort(words, new Comparator<String>() {
  2. public int compare(String s1, String s2) {
  3. return Integer.compare(s1.length(), s2.length());
  4. }
  5. });
  • 上述使用了策略模式,Comparator接口为排序的抽象策略,匿名内部类为具体实现策略,但是匿名内部类的实现过于冗长。

  • 在java8中,如果一个接口只有一个方法,那么这个接口可以看作一个函数接口,功能接口的实现类可以通过lambda来实现,lambda与匿名内部类类似,但是更加简洁。

  • lamdba:常规

  1. Collections.sort(words,(s1, s2) -> Integer.compare(s1.length(), s2.length()));
  • 参数为String类型,返回值为int类型,编译器是如何知道的呢?

  • 编译器使用称为类型推断的过程从上下文中推导出这些类型,但是编译器不是万能的,有时候仍然需要显式设定。

  • lamdba:方法引用

  1. Collections.sort(words, comparingInt(String::length));
  2. words.sort(comparingInt(String::length));

(2)DEMO2

  • 常量类:enum with function object
  1. public enum Operation {
  2. PLUS("+") {
  3. public double apply(double x, double y) { return x + y; }
  4. },
  5. MINUS("-") {
  6. public double apply(double x, double y) { return x - y; }
  7. },
  8. TIMES("*") {
  9. public double apply(double x, double y) { return x * y; }
  10. },
  11. DIVIDE("/") {
  12. public double apply(double x, double y) { return x / y; }
  13. };
  14. private final String symbol;
  15. Operation(String symbol) { this.symbol = symbol; }
  16. @Override public String toString() { return symbol; }
  17. public abstract double apply(double x, double y);
  18. }
  • lambda:enum with function object
  1. public enum Operation {
  2. PLUS ("+", (x, y) -> x + y),
  3. MINUS ("-", (x, y) -> x - y),
  4. TIMES ("*", (x, y) -> x * y),
  5. DIVIDE("/", (x, y) -> x / y);
  6. private final String symbol;
  7. private final DoubleBinaryOperator op;
  8. Operation(String symbol, DoubleBinaryOperator op) {
  9. this.symbol = symbol;
  10. this.op = op;
  11. }
  12. @Override public String toString() { return symbol; }
  13. public double apply(double x, double y) {
  14. return op.applyAsDouble(x, y);
  15. }
  16. }
  • lambda中的不要超过三行。
  • lambda中无法访问枚举的实例成员。
  • lambda无法创建抽象类的实例,但匿名内部类可以。
  • lambda无法获取到对自身的引用。
  • 如果需要反序列化一个函数接口,如:Comparator,我们需要使用私有静态内部类。

2 lambda中优先使用方法引用

(1)DEMO

  1. // lambda代码块
  2. map.merge(key, 1, (count, incr) -> count + incr);
  3. // 方法引用
  4. map.merge(key, 1, Integer::sum);

(2)类型

方法引用类型 例子 Lambda等效方案
Static Integer::parseInt str -> Integer.parseInt(str)
Bound Instant.now()::isAfter Instant then = Instant.now();
t -> then.isAfter(t)
Unbound String::toLowerCase str -> str.toLowerCase()
Class Constructor TreeMap::new () -> new TreeMap
Array Constructor int[]::new len -> new int[len]
  • 如果方法引用更加简洁和清晰,请使用方法引用,反之使用Lambda表达式。

3 优先使用标准功能接口

(1)模板方法模式

  • 由于Lambda的存在,通过子类重写基本方法以专门化超类的行为的方式有点过时。
  • 替代方案:提供一个静态工厂或者构造器,它们接收一个函数对象来实现相同的效果。
  • 一般来说,我们将编写更多以函数对象作为参数的构造函数和方法。
  1. // 模板方法
  2. abstract class A {
  3. public void print() {
  4. System.out.println("A");
  5. doSubThing();
  6. }
  7. abstract void doSubThing();
  8. }
  9. class B extends A {
  10. @Override
  11. void doSubThing() {
  12. System.out.println("B");
  13. }
  14. }
  15. // lambda
  16. class A {
  17. private Supplier<String> supplier;
  18. public A(Supplier<String> supplier) {
  19. this.supplier = supplier;
  20. }
  21. public void print() {
  22. System.out.println("A");
  23. System.out.println(supplier.get());
  24. }
  25. }
  26. public static void main(String[] args) {
  27. A a = new A(() -> "B");
  28. a.print();
  29. }

(2)标准函数接口

接口 函数签名 例子
UnaryOperator<T> T apply(T t) String::toLowerCase
BinaryOperator<T> T apply(T t1, T t2) BigInteger::add
Predicate<T> boolean test(T t) Collection::isEmpty
Function<T> R apply(T t) Arrays::asList
Supplier<T> T get() Instant::now
Consumer<T> void accept(T t) System.out::println
  • 优先使用标准函数接口,这能够缩小概念表面积,从而降低学习成本。
  • 但是如果所有标准函数接口都不能很好表示时,请
  • 上述的六种接口拥有许多变种,如:int、long和double,甚至是int->long等等。
  • 其实大多数变种都使用了基础类型,而不是包装类型,基础类型的运算更快,节省内存空间,不要使用包装类去替换它们。
  • 其实我们并不会去记忆所有变种,变种随着JDK升级可能会增加或减少,只需要在使用时去翻翻java.util.function包是否有需要的接口即可。

具体请参考:JAVA8的java.util.function包

(3)Comparator接口

  • Comparator接口与ToIntBiFunction接口的结构相同,但是仍然不要用ToIntBiFunction去替代Comparator
  • 因为Comparator的名称含义十分清晰,它在jdk中已经广泛使用了,而且Comparator提供了许多有用默认方法。

(4)@FunctionalInterface

  • @FunctionalInterface注解能够帮助开发者检查这个接口是否只有一个抽象方法,如果不只一个将无法编译。
  • @FunctionalInterface目的:将某个接口标志为函数接口且提供编译时检查。

4 明智地使用Stream

4.1 概念

  • 流:无限或有限的数据元素序列。

  • 管道:对流中的元素进行多级计算。

  • 流的源:集合、数组、文件、正则表达式或模式匹配器、伪随机数生成器或其他流。

  • 管道操作:由源流后跟着零个或多个中间操作和一个终止操作。

  • 中间操作:某种转换流的方式,如:元素映射或元素过滤等。

  • 终止操作:执行最终计算,如:流装入容器中或是消费掉。

4.2 补充

  • 流管道只包含中间操作时是惰性的:当一个流没有最终操作时,流管道是什么都不做的。
  • 流管道的API被设计成链式编码风格。

4.3 流的使用时机

(1)不要滥用流
  1. // 普通方式
  2. // 读取文件中的单词,检查单词的字母,相同字母的单词收集在一起
  3. public class Anagrams {
  4. public static void main(String[] args) throws IOException {
  5. File dictionary = new File(args[0]);
  6. int minGroupSize = Integer.parseInt(args[1]);
  7. Map<String, Set<String>> groups = new HashMap<>();
  8. try (Scanner s = new Scanner(dictionary)) {
  9. while (s.hasNext()) {
  10. String word = s.next();
  11. groups.computeIfAbsent(alphabetize(word), (unused) -> new TreeSet<>()).add(word);
  12. }
  13. }
  14. for (Set<String> group : groups.values())
  15. if (group.size() >= minGroupSize)
  16. System.out.println(group.size() + ": " + group);
  17. }
  18. private static String alphabetize(String s) {
  19. char[] a = s.toCharArray();
  20. Arrays.sort(a);
  21. return new String(a);
  22. }
  23. }
  24. // 过度使用流:虽然很简洁,但是对流不了解的开发人员可能无法理解。
  25. // 打个比方,有些动漫是只有死宅才看的:永生之酒。
  26. public static void main(String[] args) throws IOException {
  27. Path dictionary = Paths.get(args[0]);
  28. int minGroupSize = Integer.parseInt(args[1]);
  29. try (Stream<String> words = Files.lines(dictionary)) {
  30. words.collect(
  31. groupingBy(word -> word.chars().sorted()
  32. .collect(StringBuilder::new,
  33. (sb, c) -> sb.append((char) c),
  34. StringBuilder::append).toString())
  35. )
  36. .values().stream()
  37. .filter(group -> group.size() >= minGroupSize)
  38. .map(group -> group.size() + ": " + group)
  39. .forEach(System.out::println);
  40. }
  41. }
  42. // 合适使用流方式
  43. // 有的动漫是大家都看的:龙珠。对动漫不需要太了解也能够接收。
  44. public static void main(String[] args) throws IOException {
  45. Path dictionary = Paths.get(args[0]);
  46. int minGroupSize = Integer.parseInt(args[1]);
  47. try (Stream<String> words = Files.lines(dictionary)) {
  48. words.collect(groupingBy(word -> alphabetize(word)))
  49. .values().stream()
  50. .filter(group -> group.size() >= minGroupSize)
  51. .forEach(group -> System.out.println(group.size() + ": " + group));
  52. }
  53. }
  • 字母排序方法抽取出来增加程序的可读性。
  • lambda中参数的命名尤为重要,好的命名能够提升可读性。
  • 也许大家都希望使用lambda来消灭循环,但实际是不可取的(元素少时lambda存在性能问题)。
(2)代码块与lambda
  • Stream的缺点

    • 代码块能够读取或修改范围内的局部变量,lambda只能操作final变量和当前范围的局部变量。
    • 代码块中能够return、抛出异常、跳出循环或是跳过循环,lambda中都无法做到。
  • Stream的优势
    • map:统一转换元素类型
    • filter:过滤序列
    • min、compute:计算最小值、合并序列等
    • reduce:累计序列
    • grouping:分组
(3)流无法做到同时在多级阶段访问相应的元素
  • 通过操作反转来获取上一个流元素
  1. public static void main(String[] args) {
  2. primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
  3. // (1-1/50)=98%代表isProbablePrime只有当98%几率为素数才返回true。
  4. .filter(mersenne -> mersenne.isProbablePrime(50))
  5. .limit(20)
  6. // mp.bitLength等于p值,反向运算来获取上一个流的值。
  7. .forEach(mp -> System.out.println(mp.bitLength() + ": " + mp));
  8. }
  9. static Stream<BigInteger> primes() {
  10. return Stream.iterate(TWO, BigInteger::nextProbablePrime);
  11. }
(4)笛卡尔积
  1. private static List<Card> newDeck() {
  2. List<Card> result = new ArrayList<>();
  3. for (Suit suit : Suit.values())
  4. for (Rank rank : Rank.values())
  5. result.add(new Card(suit, rank));
  6. return result;
  7. }
  8. // flatMap 用于展平一个序列,如:List<String> -> String.
  9. private static List<Card> newDeck() {
  10. return Stream.of(Suit.values())
  11. .flatMap(suit ->
  12. Stream.of(Rank.values())
  13. .map(rank -> new Card(suit, rank)))
  14. .collect(toList());
  15. }

5 优先选择流中无副作用的功能

(1)概述

  • 为了得到stream的表现力、速度和并行度,我们必须遵守范式和使用API。
  • stream范式最重要的部分:计算 -> 转换 ,每个转换(中间或终止操作)都是纯函数。
  • 纯函数应该都是无副作用的(不依赖任何可变状态,不更新任何状态)。

(2)Collectors的基本方法

名称 作用
toCollection toList toSet 流转换为集合
toMap 流转换为Map
partitioningBy groupingBy groupingByConcurrent 分组
minBy maxBy 最值
counting 计数
summingInt averagingInt 和 平均值
joining mapping 合并 映射

(3)示例

  1. // 不遵守范式,forEach应该只用于呈现流执行的计算结果
  2. Map<String, Long> freq = new HashMap<>();
  3. try (Stream<String> words = new Scanner(file).tokens()) {
  4. words.forEach(word -> freq.merge(word.toLowerCase(), 1L, Long::sum));
  5. }
  6. // 正确地使用流来初始化频率表
  7. Map<String, Long> freq;
  8. try (Stream<String> words = new Scanner(file).tokens()) {
  9. freq = words.collect(groupingBy(String::toLowerCase, counting()));
  10. }
  11. // 按照频次获取前十个元素
  12. List<String> topTen = freq.keySet().stream()
  13. .sorted(comparing(freq::get).reversed())
  14. .limit(10)
  15. .collect(toList());
  16. // groupingByConcurrent返回并发Map
  17. ConcurrentHashMap<String, Long> freq;
  18. try (Stream<String> words = new Scanner(file).tokens()) {
  19. freq = words.collect(groupingByConcurrent(String::toLowerCase, counting()));
  20. }

(4)分组

  • Collector<T, ?, Map<Boolean, List>> partitioningBy(Predicate<? super T> predicate):true和false分成两组。
  • Collector<T, ?, Map<K, List>> groupingBy(Function<? super T, ? extends K> classifier):按照key值分组。
  1. List<String> words = new ArrayList<>();
  2. words.add("1");
  3. words.add("1");
  4. words.add("2");
  5. words.add("3");
  6. Map<Boolean, List<String>> map = words.stream().collect(
  7. partitioningBy(s -> s.equals("1"))
  8. );
  9. System.out.println(map); // {false=[2, 3], true=[1, 1]}
  10. Map<String, List<String>> map = words.stream().collect(
  11. groupingBy(String::toLowerCase)
  12. );
  13. System.out.println(map); // {1=[1, 1], 2=[2], 3=[3]}

(5)List转Map

  1. // List转Map的正确实现
  2. Map<Integer, Data> collect = words.stream().collect(toMap(Data::getId, e -> e));
  3. // key值重复时,获取销量最大的Album
  4. Map<Artist, Album> topHits = albums.collect(
  5. toMap(Album::artist, a->a,maxBy(comparing(Album::sales)))
  6. );
  7. // 后访问的覆盖先访问的
  8. Map<Artist, Album> topHits = albums.collect(
  9. toMap(Album::artist, a->a,(v1, v2) -> v2)
  10. );
  11. // 指定返回Map的类型
  12. HashMap<Artist, Album> topHits = albums.collect(
  13. toMap(Album::artist, a->a,(v1, v2) -> v2,HashMap::new)
  14. );

(6)List->字符串

  1. // joining
  2. List<String> words = new ArrayList<>();
  3. words.add("2");
  4. words.add("1");
  5. words.add("1");
  6. words.add("3");
  7. String join1 = words.stream().collect(joining());
  8. String join2 = words.stream().collect(joining(","));
  9. String join3 = words.stream().collect(joining(",","[","]"));
  10. System.out.println(join1); // 2113
  11. System.out.println(join2); // 2,1,1,3
  12. System.out.println(join3); //[2,1,1,3]
  13. // mapping和map类似
  14. List<String> words = new ArrayList<>();
  15. words.add("2");
  16. words.add("1");
  17. words.add("1");
  18. words.add("3");
  19. List<Integer> list1 = words.stream().collect(mapping(e -> Integer.valueOf(e), toList()));
  20. List<Integer> list2 = words.stream().map(e -> Integer.valueOf(e)).collect(toList());
  21. System.out.println(list1); // [2, 1, 1, 3]
  22. System.out.println(list2); // [2, 1, 1, 3]

(7)计算

  1. List<String> words = new ArrayList<>();
  2. words.add("2");
  3. words.add("1");
  4. words.add("3");
  5. // 求和
  6. Integer sum1 = words.stream().collect(summingInt(value -> Integer.valueOf(value)));
  7. Integer sum2 = words.stream().mapToInt(value -> Integer.valueOf(value)).sum();
  8. // 平均值
  9. Double avg = words.stream().collect(averagingInt(value -> Integer.valueOf(value)));
  10. // 最大值
  11. String max1 = words.stream().max(comparing(Integer::valueOf)).get();
  12. String max2 = words.stream().collect(maxBy(comparing(Integer::valueOf))).get();
  13. // 总结值
  14. IntSummaryStatistics summary = words.stream().collect(summarizingInt(Integer::valueOf));
  15. System.out.println(summary.getAverage());
  16. System.out.println(summary.getSum());
  17. System.out.println(summary.getCount());
  18. System.out.println(summary.getMax());
  19. System.out.println(summary.getMin());

6 优先选择集合作为返回值

(1)stream的iterator

  1. // need to cast
  2. for (ProcessHandle ph : (Iterable<ProcessHandle>)ProcessHandle.allProcesses()::iterator){
  3. ...
  4. }
  5. // Adapter from Stream<E> to Iterable<E>
  6. public static <E> Iterable<E> iterableOf(Stream<E> stream) {
  7. return stream::iterator;
  8. }
  9. for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())) {
  10. // Process the process
  11. }

(2)spliterator

  1. // spliterator用于并行迭代
  2. public static <E> Stream<E> streamOf(Iterable<E> iterable) {
  3. return StreamSupport.stream(iterable.spliterator(), false);
  4. }
  5. // 例子:并行计算1+2+...+10000
  6. public static void main(String[] args) throws InterruptedException {
  7. List<String> words = new ArrayList<>();
  8. for (int i = 1; i <= 10000; i++) {
  9. words.add(i + "");
  10. }
  11. final AtomicInteger atomicInteger = new AtomicInteger(0);
  12. int count = 10;
  13. CountDownLatch latch = new CountDownLatch(count);
  14. final List<Spliterator<String>> splitList = split(words, count);
  15. for (int i = 0; i < count; i++) {
  16. int finalI = i;
  17. new Thread(() -> {
  18. try {
  19. splitList.get(finalI)
  20. .forEachRemaining(s -> atomicInteger.getAndAdd(Integer.valueOf(s)));
  21. } finally {
  22. latch.countDown();
  23. }
  24. }, "Thread:" + i).start();
  25. }
  26. latch.await();
  27. System.out.println(atomicInteger.get());
  28. }
  29. public static <T> List<Spliterator<T>> split(List<T> list, int size) {
  30. List<Spliterator<T>> returnList = new ArrayList<>();
  31. returnList.add(list.spliterator());
  32. if (size > 1) spliterator(returnList, 2, size);
  33. return returnList;
  34. }
  35. private static <T> void spliterator(List<Spliterator<T>> returnList, int i, int size) {
  36. int j = i / 2 - 1;
  37. returnList.add(returnList.get(j).trySplit());
  38. if (size == i) return;
  39. spliterator(returnList, i + 1, size);
  40. }

(3)原则

  • Collection是Iterable的子类型,具有stream方法,因此提供迭代和流访问,因此Collection或适当的子类型通常是返回方法的最佳返回类型。
  • 如果返回的序列小到足够放到内存中,则最好返回一个标准集合实现。

(4)DEMO

  • 幂集
  1. // {a,b,c}的幂集为{{},{a},{b},{c},{a,b},{a,c},{b,c},{a,b ,c}}
  2. public class PowerSet {
  3. public static final <E> Collection<Set<E>> of(Set<E> s) {
  4. List<E> src = new ArrayList<>(s);
  5. if (src.size() > 30)
  6. throw new IllegalArgumentException("Set too big " + s);
  7. return new AbstractList<Set<E>>() {
  8. @Override public int size() {
  9. // 如果size > 31将导致溢出int的范围
  10. return 1 << src.size(); // 2^size
  11. }
  12. @Override public boolean contains(Object o) {
  13. return o instanceof Set && src.containsAll((Set)o);
  14. }
  15. @Override public Set<E> get(int index) {
  16. Set<E> result = new HashSet<>();
  17. for (int i = 0; index != 0; i++, index >>= 1)
  18. if ((index & 1) == 1) result.add(src.get(i));
  19. return result;
  20. }
  21. };
  22. }
  23. }
  • 前缀子集、后缀子集
  1. public class SubLists {
  2. public static <E> Stream<List<E>> of(List<E> list) {
  3. return Stream.concat(Stream.of(Collections.emptyList()),
  4. prefixes(list).flatMap(SubLists::suffixes));
  5. }
  6. // (a,b,c) => ((a),(a,b),(a,b,c))
  7. private static <E> Stream<List<E>> prefixes(List<E> list) {
  8. return IntStream.rangeClosed(1, list.size())
  9. .mapToObj(end -> list.subList(0, end));
  10. }
  11. // (a,b,c) => ((a,b,c),(b,c),(c))
  12. private static <E> Stream<List<E>> suffixes(List<E> list) {
  13. return IntStream.range(0, list.size())
  14. .mapToObj(start -> list.subList(start, list.size()));
  15. }
  16. }
  • 所有子列表
  1. // [1,3,2] => [[1], [1, 3], [1, 3, 2], [3], [3, 2], [2]]
  2. public static <E> Stream<List<E>> of(List<E> list) {
  3. return IntStream.range(0, list.size())
  4. .mapToObj(start -> IntStream.rangeClosed(start + 1, list.size())
  5. .mapToObj(end -> list.subList(start, end)))// subList使用闭区间
  6. .flatMap(x -> x);
  7. }

7 谨慎使用并行流

(1)原则

  • ArrayList、HashMap、HsahSet、CouncurrentHashMap、数组、int范围流和long范围流的并行性性能效益最佳。

  • 它们的范围可以确定,而执行任务的抽象为spliterator。

  • 数组存储的元素在内存中相近,数据定位更快。而上面涉及的数据结构基本都基于数组实现。

  • 流的终止操作会影响并行执行的有效性。而流的reduce操作或预先打包(min、max、count和sum)是并行流的最佳实践。

  • 流的中间操作(anyMatch、allMatch和noneMatch)也适合并行操作。

  • 流的collect操作则不适合。

  • 自己实现Stream、Iterable或Collection且希望有良好的并行性能,则需要覆盖spliterator方法。

  • 并行流是基于fork-join池实现的。

  • 当无法写出正确的并行流,将导致异常或者错误的数据。

注:程序的安全性、正确性比性能更重要。

(2)DEMO

  1. // 串行,10^8需要30秒
  2. static long pi(long n) {
  3. return LongStream.rangeClosed(2, n)
  4. .mapToObj(BigInteger::valueOf)
  5. .filter(i -> i.isProbablePrime(50))
  6. .count();
  7. }
  8. // 并行,10^8需要9秒
  9. static long pi(long n) {
  10. return LongStream.rangeClosed(2, n)
  11. .parallel()
  12. .mapToObj(BigInteger::valueOf)
  13. .filter(i -> i.isProbablePrime(50))
  14. .count();
  15. }

Effective Java 读书笔记(五):Lambda和Stream的更多相关文章

  1. Effective java读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...

  2. Effective Java读书笔记完结啦

    Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...

  3. Effective Java 读书笔记(一):使用静态工厂方法代替构造器

    这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...

  4. Effective Java 读书笔记之九 并发

    一.访问共享的可变数据时要同步 1.synchronized关键字既然保证访问的可见性也能保证原子性.而volatile修饰符只能保证变量的线程可见性. 2.增量操作符等不是原子性,多线程操作时可能导 ...

  5. Effective Java 读书笔记之七 通用程序设计

    一.将局部变量的作用域最小化 1.在第一次使用变量的地方声明 2.几乎每个变量的声明都应该包含一个初始化表达式:try-catch语句是一个例外 3.使方法小而集中是一个好的策略 二.for-each ...

  6. Effective Java 读书笔记之一 创建和销毁对象

    一.考虑用静态工厂方法代替构造器 这里的静态工厂方法是指类中使用public static 修饰的方法,和设计模式的工厂方法模式没有任何关系.相对于使用共有的构造器来创建对象,静态工厂方法有几大优势: ...

  7. Effective Java 读书笔记(三):类与接口

    1 最小化类和成员的可访问性 (1)封装 封装对组成系统的组件进行解耦,从而允许这些组件独立开发,测试,优化,使用,理解和修改. 封装提高了软件的复用性,因为组件间的耦合度低使得它们不仅在开发环境,而 ...

  8. Effective Java 读书笔记之十 序列化

    一.谨慎地实现Serializable接口 1.一旦一个类被发布,就大大地降低了“改变这个类的实现”的灵活性. 2.仔细设计类的序列化形式而不是接受类的默认虚拟化形式. 3.反序列化机制是一个“隐藏的 ...

  9. Effective Java 读书笔记之八 异常

    一.只针对异常的情况才使用异常 1.类具有状态相关的方法时,可采用状态测试方法和可识别的返回值两个策略. 二.对可恢复的情况使用受检异常,对编程错误使用运行时异常 1.期望调用者能够适当恢复的情况,应 ...

随机推荐

  1. Qt类关系一览表

  2. Kubernetes addons 之 coredns部署

    Kubernetes addons 之 coredns部署 2019.06.04 18:04:35字数 1045阅读 121 DNS 是 Kubernetes 的核心功能之一,通过 kube-dns ...

  3. Java基础 return 退出main方法的示例

        JDK :OpenJDK-11      OS :CentOS 7.6.1810      IDE :Eclipse 2019‑03 typesetting :Markdown   code ...

  4. Kotlin介绍

    Kotlin介绍 转 https://www.jianshu.com/p/d30406daaf25 Google在今年的IO大会上宣布,将Android开发的官方语言更换为Kotlin,作为跟着Goo ...

  5. 测试一下windowsLiveWriter

    一个是看看这个东西能不能发布出博客,还有一个就是准备开始写博客了,所以随便写个作为开始吧,我不想多说什么目标啊,什么的,所以就这一句简单的一句话就够了.

  6. 手机wifi连上Fiddler后无网络问题解决

    早上老板交代一个任务,对一款app抓包分析下接口调用的时延.我的重新打开了一年多前用过的Fiddler(参见win10笔记本用Fiddler对手机App抓包),拿过测试手机开始设置wifi代理地址和端 ...

  7. 【DataBase】H2 DateBase与项目集成

    本例介绍H2与web项目的集成 项目启动H2数据库 1.新建Maven Web项目,参考:[Maven]Eclipse 使用Maven创建Java Web项目 2.引入h2的jar包依赖 <de ...

  8. 【超分辨率】—图像超分辨率(Super-Resolution)技术研究

    一.相关概念 1.分辨率 图像分辨率指图像中存储的信息量,是每英寸图像内有多少个像素点,分辨率的单位为PPI(Pixels Per Inch),通常叫做像素每英寸.一般情况下,图像分辨率越高,图像中包 ...

  9. APT攻击与银行

    APT与传统攻击 APT:团队作战,拥有自研工具集,意图明确,技术高超,拥有政府或商业背景,潜伏期多达数年. 传统攻击:脚本小子,水平参差不齐,短期攻击,直接获取利益为目标. 攻击目标 APT攻击链 ...

  10. stochastic noise and deterministic noise

    在机器学习中,导致overfitting的原因之一是noise,这个noise可以分为两种,即stochastic noise,随机噪声来自数据产生过程,比如测量误差等,和deterministic ...