JDK 新特性篇:JDK 8 新特性详解

2023-11-06

Java8新特性简介

Java 8 (又称为 JDK 1.8) 是 Java 语言开发的一个主要版本。Java 8 是 Oracle 公司于 2014 年 3 月发布,可以看成是自 Java 5 以来最具革命性的版本。Java 8 为 Java 语言、编译器、类库、开发工具与 JVM 带来了大量新特性。

  • 速度更快
  • 代码更少(增加了新的语法 Lambda 表达式)
  • 强大的 Stream API
  • 优化 Fork/Join 框架,更加容易写并行代码
  • 最大化减少空指针异常 Optional
  • 时间 API

......

其中最为核心的是 Lambda 表达式和 Stream API。

哈希结构

Java8 优化了哈希算法,JDK7 之前是数组 + 链表,当两个不同对象经过哈希算法算出的下标相同时,则该数组下标处形成链表,旧的在链表最后面。但是这样产生很长的链表时,恰巧我们需要的对象在最后一个,则需要从头遍历到尾,效率低。

image-20220209144032901

JDK8 变成了数组 + 链表 + 红黑树,当链表的长度超出 8 时,并且整个数组的长度超出 64 时,则链表转为红黑树,即一种二叉树,这样减少了从头遍历到尾的效率低问题,只需要在二叉树的一方遍历,找出对象即可,不需要像全部遍历到最后。

image-20220209144503523

JVM方法区

永久代改为元空间,即最大内存变成电脑的最大内存。

因为永久代的垃圾回收条件苛刻,所以容易导致内存不足,而转为元空间,使用本地内存,极大减少了 OOM 异常,毕竟,现在电脑的内存足以支持 Java 的允许。

首先明确:只有 Hotspot 才有永久代。BEA JRockit、IBMJ9 等来说,是不存在永久代的概念的。原则上如何实现方法区属于虚拟机实现细节,不受《Java 虚拟机规范》管束,并不要求统一。

Hotspot 中方法区的变化:

| 版本 | 变化 | | ---------- | ----------------------------------------------- | | JDK1.6 及以前 | 有永久代,静态变量存储在永久代上 | | JDK1.7 | 有永久代,但已经逐步「去永久代」,字符串常量池,静态变量移除,保存在堆中 | | JDK1.8 | 无永久代,类型信息,字段,方法,常量保存在本地内存的元空间,但字符串常量池、静态变量仍然在堆中 |

为什么永久代要被元空间替代?

随着 Java8 的到来,HotSpot VM 中再也见不到永久代了。但是这并不意味着类的元数据信息也消失了。这些数据被移到了一个与堆不相连的本地内存区域,这个区域叫做元空间(Metaspace)。

由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间,这项改动是很有必要的,原因有:

  • 为永久代设置空间大小是很难确定的

在某些场景下,如果动态加载类过多,容易产生 Perm 区的 OOM。比如某个实际 Web 工程中,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误。

Exception in thread‘dubbo client x.x connector'java.lang.OutOfMemoryError:PermGen space

而元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

因此,默认情况下,元空间的大小仅受本地内存限制。

  • 对永久代进行调优是很困难的

    主要是为了降低 Full GC

有些人认为方法区(如 HotSpot 虚拟机中的元空间或者永久代)是没有垃圾收集行为的,其实不然。《Java 虚拟机规范》对方法区的约束是非常宽松的,提到过可以不要求虚拟机在方法区中实现垃圾收集。事实上也确实有未实现或未能完整实现方法区类型卸载的收集器存在(如 JDK11 时期的 ZGC 收集器就不支持类卸载)。

一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻。但是这部分区域的回收有时又确实是必要的。以前 Sun 公司的 Bug 列表中,曾出现过的若干个严重的 Bug 就是由于低版本的 HotSpot 虚拟机对此区域未完全回收而导致内存泄漏。

方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不在使用的类型。

Lambda表达式

为什么使用Lambda表达式

Lambda 是一个 匿名函数,我们可以把 Lambda 表达式理解为是 一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升。

从匿名类到 Lambda 的转换

// 原来的匿名内部类 public void test1(){    Comparator<String> com = new Comparator<String>(){        @Override        public int compare(String o1, String o2) {            return Integer.compare(o1.length(), o2.length());       }   };    TreeSet<String> ts = new TreeSet<>(com); } ​ // 现在的 Lambda 表达式 public void test2(){    Comparator<String> com = (x, y) -> Integer.compare(x.length(), y.length());    TreeSet<String> ts = new TreeSet<>(com); }

由此我们可以知道,Lambada 表达式替换了匿名内部类,自动实现匿名内部类的唯一接口。

逐步优化代码示例(顺便了解 Stream API)

首先是没有优化的代码,也就是初学者经常使用的代码,获取年龄小于 35 的员工信息、获取公司中工资大于 5000 的员工信息

``` public class TestLambda {    List emps = Arrays.asList( new Employee(101, "张三", 18, 9999.99), new Employee(102, "李四", 59, 6666.66), new Employee(103, "王五", 28, 3333.33), new Employee(104, "赵六", 8, 7777.77), new Employee(105, "田七", 38, 5555.55) );    // 需求:获取公司中年龄小于 35 的员工信息 public List filterEmployeeAge(List emps){ List list = new ArrayList<>();

for (Employee emp : emps) {
  if(emp.getAge() <= 35){
    list.add(emp);
  }
}
return list;

}    // 需求:获取公司中工资大于 5000 的员工信息 public List filterEmployeeSalary(List emps){ List list = new ArrayList<>();

for (Employee emp : emps) {
  if(emp.getSalary() >= 5000){
    list.add(emp);
  }
}
return list;

}

} ```

优化方式一:策略设计模式 + 匿名内部类

首先创建策略设计模式接口

public interface MyPredicate<T> { public boolean test(T t); }

然后利用匿名内部类实现

``` public class TestLambda {    List emps = Arrays.asList( new Employee(101, "张三", 18, 9999.99), new Employee(102, "李四", 59, 6666.66), new Employee(103, "王五", 28, 3333.33), new Employee(104, "赵六", 8, 7777.77), new Employee(105, "田七", 38, 5555.55) );    // 需求:获取公司中年龄小于 35 的员工信息 public void filterEmployeeAge(){ List list = filterEmployee(emps, new MyPredicate () { @Override public boolean test(Employee t) { return t.getId() <= 103; } });

for (Employee employee : list) {
  System.out.println(employee);
}

}    // 需求:获取公司中工资大于 5000 的员工信息    public void filterEmployeeSalary(){ List list = filterEmployee(emps, new MyPredicate () { @Override public boolean test(Employee t) { return t.getSalary() >= 5000; } });

for (Employee employee : list) {
  System.out.println(employee);
}

} } ```

优化方式三:策略设计模式 + Lambda 表达式

``` public class TestLambda {    List emps = Arrays.asList( new Employee(101, "张三", 18, 9999.99), new Employee(102, "李四", 59, 6666.66), new Employee(103, "王五", 28, 3333.33), new Employee(104, "赵六", 8, 7777.77), new Employee(105, "田七", 38, 5555.55) );    // 需求:获取公司中年龄小于 35 的员工信息 public void test6(){ List list = filterEmployee(emps, (e) -> e.getAge() <= 35); list.forEach(System.out::println);

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

List<Employee> list2 = filterEmployee(emps, (e) -> e.getSalary() >= 5000);
list2.forEach(System.out::println);

} } ```

优化方式四:Stream API

注意:不需要策略设计模式

``` public class TestLambda {    List emps = Arrays.asList( new Employee(101, "张三", 18, 9999.99), new Employee(102, "李四", 59, 6666.66), new Employee(103, "王五", 28, 3333.33), new Employee(104, "赵六", 8, 7777.77), new Employee(105, "田七", 38, 5555.55) );    public void test7(){ emps.stream() .filter((e) -> e.getAge() <= 35) .forEach(System.out::println);

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

emps.stream()
  .map(Employee::getName)
  .limit(3)
  .sorted()
  .forEach(System.out::println);

} } ```

Lambda表达式语法

Lambda 表达式在 Java 语言中引入了一个新的语法元素和操作符。这个操作符为 -> , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:

  • 左侧:指定了 Lambda 表达式需要的所有参数
  • 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能

语法格式一:无参,无返回值,Lambda 体只需一条语句

public void test1(){    Runnable r1 = () -> System.out.println("Hello Lambda!"); }

语法格式二:Lambda 需要一个参数,并且无返回值

public void test2(){    Consumer<String> con = (x) -> System.out.println(x);    con.accept("Lambda 表达式"); }

语法格式三:Lambda 只需要一个参数时,参数的小括号可以省略

public void test2(){    Consumer<String> con = x -> System.out.println(x);    con.accept("Lambda 表达式"); }

语法格式四:Lambda 需要两个参数,并且有返回值

public void test3(){    Comparator<Integer> com = (x, y) -> {        System.out.println("函数式接口");        return Integer.compare(x, y);   }; }

语法格式五:当 Lambda 体只有一条语句时,return 与大括号可以省略

public void test4(){    Comparator<Integer> com = (x, y) -> Integer.compare(x, y); }

语法格式六:数据类型可以省略,因为可由编译器推断得出,称为「类型推断」

public void test4(){    // 类型不省略    Comparator<Integer> com = (Integer x, Integer y) -> Integer.compare(x, y);    // 数据类型省略    Comparator<Integer> com = (x, y) -> Integer.compare(x, y); }

类型推断

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的「类型推断」。

函数式接口

什么是函数式接口

只包含一个抽象方法的接口,称为函数式接口。

你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。

我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

自定义函数式接口

使用 @FunctionalInterface 注解

@FunctionalInterface public interface MyFunction {    public String getValue(String str); }

函数式接口中使用泛型

@FunctionalInterface public interface MyFunction2<T, R> { public R getValue(T t1, T t2); }

作为参数传递Lambda表达式

这里面使用了自定义函数式接口的第一个例子。

public String toUpperString(MyFunction mf, String str) {    return mf.getValue(str) }

作为参数传递 Lambda 表达式:

String newStr = toUpperString( (str) -> str.toUpperCase(), "abcdef"); System.out.println(newStr);

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收 Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

四大核心函数式接口

这是 Java 为我们提供的四个函数式接口,所以简单的函数式接口不用自己写。

| 函数式接口 | 参数类型 | 返回类型 | 用途 | | --------------------- | ---- | ------- | ---------------------------------------------------------- | | Consumer<T> 消费型接口 | T | void | 对类型为 T 的对象应用操作,包含方法 void accept(T t); | | Supplier<T> 供给型接口 | 无 | T | 返回类型为 T 的对象,包含方法 T get(); | | Function<T, R>函数型接口 | T | R | 对类型为 T 的对象应用操作,并返回结果。结果是 R 类型的对象。包含方法 R apply(T t); | | Predicate<T> 断言型接口 | T | boolean | 确定类型为 T 的对象是否满足某约束,并返回 boolean 值。包含方法 boolean test(T t); |

消费型接口 Consumer<T>

接口的方法

void accept(T t);

没有返回值,参数一去不复返

public void happy(double money, Consumer<Double> con){    con.accept(money); } ​ public void test1(){    happy(10000, (m) -> System.out.println("消费:" + m + "元")); }

供给型接口 Supplier<T>

接口的方法

T get();

没有参数,返回值是泛型 T,也就是参数进入接口后,回来的还是自己(值可能发生改变)

// 产生指定个数的整数,并放入集合中 public List<Integer> getNumList(int num, Supplier<Integer> sup){    List<Integer> list = new ArrayList<>(); ​    for (int i = 0; i < num; i++) {        Integer n = sup.get();        list.add(n);   } ​    return list; } ​ public void test2(){    List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100)); ​    for (Integer num : numList) {        System.out.println(num);   } }

函数型接口 Function<T, R>

接口的方法

R apply(T t);

参数是 T,返回值是 R,也就是 T 参数进入接口后,处理成 R 的类型并返回

// 用于处理字符串 public String strHandler(String str, Function<String, String> fun){    return fun.apply(str); } ​ public void test3(){    String newStr = strHandler("\t\t\t 四大核心函数式接口   ", (str) -> str.trim());    System.out.println(newStr); ​    String subStr = strHandler("四大核心函数式接口", (str) -> str.substring(2, 5));    System.out.println(subStr); }

断言型接口 Predicate<T>

接口的方法

boolean test(T t);

对 T 参数进行处理后,返回类型是 boolean 值

// 将满足条件的字符串,放入集合中 public List<String> filterStr(List<String> list, Predicate<String> pre){    List<String> strList = new ArrayList<>(); ​    for (String str : list) {        if(pre.test(str)){            strList.add(str);       }   } ​    return strList; } ​ public void test4(){    List<String> list = Arrays.asList("Hello", "kele", "Lambda", "www", "ok");    List<String> strList = filterStr(list, (s) -> s.length() > 3); ​    for (String str : strList) {        System.out.println(str);   } }

其他接口

| 函数式接口 | 参数类型 | 返回类型 | 用途 | | ----------------------------------- | ------ | ------ | --------------------------------------------------------- | | BiFunction<T, U, R> | T,U | R | 对类型为 T,U 参数应用操作,返回 R 类型的 结果。包含方法为 R apply(T t, U u); | | UnaryOperator<T>(Function 子接口) | T | T | 对类型为 T 的对象进行一元运算, 并返回 T 类型的结果。包含方法为 T apply(T t); | | BinaryOperator<T>(BiFunction 子接口) | T,T | T | 对类型为 T 的对象进行二元运算,并返回 T 类型的结果。包含方法为 T apply(T t1, T t2); | | BiConsumer<T, U> | T,U | void | 对类型为 T,U 参数应用操作。包含方法为 void accept(T t, U u) | | ToIntFunction<T> | T | int | 计算 int 值的函数 | | ToLongFunction<T> | T | long | 计算 long 值的函数 | | ToDoubleFunction<T> | T | double | 计算 double 值的函数 | | IntFunction<R> | int | R | 参数为 int 类型的函数 | | LongFunction<R> | long | R | 参数为 long 类型的函数 | | DoubleFunction<R> | double | R | 参数为 double 类型的函数 |

三大引用

方法引用

当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用。

方法引用:使用操作符 :: 将方法名和对象或类的名字分隔开来。如下三种主要使用情况:

  • 对象::实例方法
  • 类::静态方法
  • 类::实例方法

例 1(类::静态方法):

(x) -> System.out.println(x); // 等价于 System.out::println; ​ public void test1(){    Consumer<String> con = (str) -> System.out.println(str);    con.accept("Hello World!"); // 等价于    Consumer<String> con2 = System.out::println;    con2.accept("Hello Java8!"); } public void test4(){    Comparator<Integer> com = (x, y) -> Integer.compare(x, y); // 等价于    Comparator<Integer> com2 = Integer::compare; }

自动读取参数列表,放到 :: 后面的方法里。

例 2(对象::实例方法):

Binaryoperator<Double> bo = (x, y) -> Math.pow(x, y); // 等价于 Binaryoperator<Double> bo = Math::pow; ​ public void test2(){    Employee emp = new Employee(101, "张三", 18, 9999.99); ​    Supplier<String> sup = () -> emp.getName();    System.out.println(sup.get()); // 等价于    Supplier<String> sup2 = emp::getName;    System.out.println(sup2.get()); }

自动读取参数列表,放到 :: 后面的方法里。

例 3(类::实例方法):

compare((x, y) -> x.equals(y), "abcdef", "abcdef" ); // 等价于 compare(String::equals, "abcdef", "abcdef" ); ​ public void test5(){    BiPredicate<String, String> bp = (x, y) -> x.equals(y);    System.out.println(bp.test("abcde", "abcde"));    // 等价于    BiPredicate<String, String> bp2 = String::equals;    System.out.println(bp2.test("abcde", "abcde")); ​    System.out.println("-----------------------------------------"); ​    Function<Employee, String> fun = (e) -> e.show();    System.out.println(fun.apply(new Employee())); // 等价于    Function<Employee, String> fun2 = Employee::show;    System.out.println(fun2.apply(new Employee())); ​ }

:: 前面是类型,后面是实例方法,则自动读取参数列表,第一个参数放到 :: 的前面,其他参数放到 :: 后面的方法里。

注意:

  • 方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致
  • 若 Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式:ClassName::MethodName,如例 3

构造器引用

格式:ClassName::new

与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致。

public void test7(){    Function<String, Employee> fun = (n) -> new Employee(n);    // 等价于    Function<String, Employee> fun = Employee :: new; }

数组引用

格式:ClassName[]::new

public void test8(){    Function<Integer, String[]> fun = (args) -> new String[args];    String[] strs = fun.apply(10);    System.out.println(strs.length);    // 等价于    Function<Integer, Employee[]> fun2 = Employee[] :: new;    Employee[] emps = fun2.apply(20);    System.out.println(emps.length); }

额外 - 遍历集合

利用引用来快速遍历 List、Set、Map 等集合的数据。

public void testCollection(){    //List:    List<Integer> list = Arrays.asList(1, 2, 3);    list.forEach(System.out::println);        // Set:    Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));    set.forEach(System.out::println); ​    // Map:    Map<Object, Object> map = new HashMap<>();    map.put("Tom", 78);    map.put("Jerry", 88);    map.put("Tim", 68);        map.forEach((k,v) -> System.out.println(k + ":" + v)); }

Stream API

了解Stream

Java8 中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。

使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

什么是Stream

Stream 是流,是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。「集合讲的是数据,流讲的是计算」

注意:

  • Stream 自己不会存储元素
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行

操作三个步骤

  • 创建 Stream

    一个数据源(如:集合、数组),获取一个流

  • 中间操作

    一个中间操作链,对数据源的数据进行处理

  • 终止操作(终端操作)

    一个终止操作,执行中间操作链,并产生结果

image-20220209172921727

Stream创建

由集合创建流

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • default Stream stream(): 返回一个串行流(顺序流)
  • default Stream parallelStream(): 返回一个并行流

public void test1(){    // Collection 提供了两个方法 stream() 与 parallelStream()    List<String> list = new ArrayList<>();    Stream<String> stream = list.stream(); // 获取一个顺序流    Stream<String> parallelStream = list.parallelStream(); // 获取一个并行流 }

由数组创建流

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • static Stream stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

public void test1(){   // 通过 Arrays 中的 stream() 获取一个数组流   Integer[] nums = new Integer[10];   Stream<Integer> stream1 = Arrays.stream(nums);   }

由值创建流

可以使用静态方法 Stream.of(),通过显示值 创建一个流。它可以接收任意数量的参数

  • public static Stream of(T... values): 返回一个流

public void test1(){   // 通过 Stream 类中静态方法 of()   Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6); }

由函数创建流:创建无限流

可以使用静态方法 Stream.iterate()Stream.generate(),创建无限流

  • 迭代:public static Stream iterate(final T seed, final UnaryOperator f)
  • 生成:public static Stream generate(Supplier s);

public void test1(){     // 创建无限流   // 迭代   Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10); // 限制 10 个,否则无限创建   stream3.forEach(System.out::println);     // 生成   Stream<Double> stream4 = Stream.generate(Math::random).limit(2); // 限制 2 个   stream4.forEach(System.out::println); }

Stream中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为「惰性求值」。

筛选与切片

| 方法 | 描述 | | ------------------- | ------------------------------------------------------- | | filter(Predicate p) | 接收 Lambda,从流中排除某些元素 | | distinct() | 筛选,通过流所生成元素的 hashCode()equals() 去除重复元素 | | limit(long maxSize) | 截断流,使其元素不超过给定数量 | | skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 |

代码示例:

/** * 筛选与切片 * filter:接收 Lambda , 从流中排除某些元素。 * limit:截断流,使其元素不超过给定数量。 * skip(n):跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 * distinct:筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 */ public class TestStreamaAPI {    List<Employee> emps = Arrays.asList(        new Employee(102, "李四", 59, 6666.66),        new Employee(101, "张三", 18, 9999.99),        new Employee(103, "王五", 28, 3333.33),        new Employee(104, "赵六", 8, 7777.77),        new Employee(104, "赵六", 8, 7777.77),        new Employee(104, "赵六", 8, 7777.77),        new Employee(105, "田七", 38, 5555.55)   ); // 测试 filter    public void testFilter(){        // 所有的中间操作不会做任何的处理        Stream<Employee> stream = emps.stream()           .filter((e) -> {                System.out.println("测试中间操作");                return e.getAge() <= 35;           });        // 只有当做终止操作时,所有的中间操作会一次性的全部执行,称为「惰性求值」        stream.forEach(System.out::println);   } // 测试 limit    public void testLimit(){        emps.stream()           .filter((e) -> {                System.out.println("短路"); // && ||                return e.getSalary() >= 5000;           }).limit(3)           .forEach(System.out::println);   } // 测试 skip    public void testSkip(){        emps.parallelStream() // 并行流           .filter((e) -> e.getSalary() >= 5000)           .skip(2)           .forEach(System.out::println);   } // 测试 distinct    public void testDistinct(){        emps.stream()           .distinct()           .forEach(System.out::println);   } }

映射

| 方法 | 描述 | | ------------------------------- | -------------------------------------------- | | map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素 | | mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream | | mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream | | mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream | | flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |

代码示例:

/** * 映射 *   map:接收 Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 * flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 */ public class TestStreamAPI1 { ​    List<Employee> emps = Arrays.asList(        new Employee(102, "李四", 59, 6666.66),        new Employee(101, "张三", 18, 9999.99),        new Employee(103, "王五", 28, 3333.33),        new Employee(104, "赵六", 8, 7777.77),        new Employee(104, "赵六", 8, 7777.77),        new Employee(104, "赵六", 8, 7777.77),        new Employee(105, "田七", 38, 5555.55)   );    @Test    public void test1(){        Stream<String> str = emps.stream()           .map((e) -> e.getName()); ​        System.out.println("-------------------------------------------"); ​        List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee"); // 字母转大写        Stream<String> stream = strList.stream()           .map(String::toUpperCase); ​        stream.forEach(System.out::println); ​        Stream<Stream<Character>> stream2 = strList.stream()           .map(TestStreamAPI1::filterCharacter); ​        stream2.forEach((sm) -> {            sm.forEach(System.out::println);       }); ​        System.out.println("---------------------------------------------"); ​        Stream<Character> stream3 = strList.stream()           .flatMap(TestStreamAPI1::filterCharacter); ​        stream3.forEach(System.out::println);   } ​    public static Stream<Character> filterCharacter(String str){        List<Character> list = new ArrayList<>(); ​        for (Character ch : str.toCharArray()) {            list.add(ch);       } ​        return list.stream();   } }

如何区分 mapflatMap?利用去重 distinct 来讲解

public void testMap(){    String[] words = new String[]{"Hello", "World"};    List<String[]> a = Arrays.stream(words)       .map(word -> word.split(""))       .distinct()       .collect(Collectors.toList());    a.forEach(System.out::print); // 输出:[Ljava.lang.String;@5e9f23b4[Ljava.lang.String;@4783da3f } public void testFlatMap(){    String[] words = new String[]{"Hello", "World"};    List<String> a = Arrays.stream(words)       .map(word -> word.split(""))       .flatMap(Arrays::stream)       .distinct()       .collect(Collectors.toList());    a.forEach(System.out::print); // 输出:HeloWrd }

数组使用 distinct 去重是比较数组不同下标的元素,如 Hello 和 World 比较,而不是 Hello 和 World 自己内部比较去重。

flatMap 是把 Hello 和 World 分成一个字符串,即一个字符串就是一个流,然后去重,是可以把 l 去掉,而 map 是不同下标元素比较。

使用 flatMap 方法的效果是,各个数组并不是分别映射一个流,而是映射成流的内容,所有使用 map(Array::stream) 时生成的单个流被合并起来,即扁平化为一个流。

两个方法效果如图:

img

img

举例说明

有二箱鸡蛋,每箱 5 个,现在要把鸡蛋加工成煎蛋,然后分给学生。

map 做的事情:把二箱鸡蛋分别加工成煎蛋,还是放成原来的两箱,分给 2 组学生;

flatMap 做的事情:把二箱鸡蛋分别加工成煎蛋,然后放到一起【10 个煎蛋】,分给 10 个学生。

总结

数组去重复时,如果去重复的是不同下标的整体元素,则使用 map,如果去重复的是每个元素自己的内容,则使用 flatMap

排序

| 方法 | 描述 | | ----------------------- | ---------------------- | | sorted() | 产生一个新流,其中按自然顺序排序(从小到大) | | sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序 |

代码示例:

``` /** * sorted():自然排序(从小到大) * sorted(Comparator com):自定义排序 */ public class TestStreamAPI1 { ​    List emps = Arrays.asList(        new Employee(102, "李四", 59, 6666.66),        new Employee(101, "张三", 18, 9999.99),        new Employee(103, "王五", 28, 3333.33),        new Employee(104, "赵六", 8, 7777.77),        new Employee(104, "赵六", 8, 7777.77),        new Employee(104, "赵六", 8, 7777.77),        new Employee(105, "田七", 38, 5555.55)   );    @Test    public void test2(){ emps.stream() .map(Employee::getName) .sorted() .forEach(System.out::println);

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

emps.stream()
  .sorted((x, y) -> {
    if(x.getAge() == y.getAge()){
      return x.getName().compareTo(y.getName());
    }else{
      return Integer.compare(x.getAge(), y.getAge());
    }
  }).forEach(System.out::println);

} } ```

增量

| 方法 | 描述 | | ------------------------------------------------- | ------------------------------------------------------------------------- | | range(int startInclusive, int endExclusive) | 以 1 的增量步从 startInclusive 到 endExclusive 返回顺序的有序 IntStream,不包括endExclusive | | rangeClosed(int startInclusive, int endExclusive) | 以 1 的增量步从 startInclusive 到 endExclusive 返回顺序的有序 IntStream,包括endExclusive |

代码示例:

public class TestStreamAPI1 {    @Test    public void test3(){        IntStream.range(0, 10).forEach( i -> System.out.println(i)); ​   }    @Test    public void test4(){        IntStream.rangeClosed(0, 10).forEach( i -> System.out.println(i));   } }

test3 方法会输出 0 到 9,而 test4 方法会输出 0 到 10。

Stream终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。

查找与匹配

| 方法 | 描述 | | ---------------------- | -------------------------------------------------------------------- | | allMatch(Predicate p) | 检查是否有匹配所有条件的元素 | | anyMatch(Predicate p) | 检查是否有至少匹配一个条件的元素 | | noneMatch(Predicate p) | 检查是否没有匹配所有条件的元素 | | findFirst() | 返回第一个元素 | | findAny() | 返回当前流中的任意元素 | | count() | 返回流中元素总数 | | max(Comparator c) | 返回流中最大值 | | min(Comparator c) | 返回流中最小值 | | forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代,即它帮你把迭代做了) |

代码示例:

``` /** * allMatch:检查是否匹配所有元素 * anyMatch:检查是否至少匹配一个元素 * noneMatch:检查是否没有匹配的元素 * findFirst:返回第一个元素 * findAny:返回当前流中的任意元素 * count:返回流中元素的总个数 * max:返回流中最大值 * min:返回流中最小值 */ public class TestStreamAPI2 {    // BUSY:忙碌,FREE:空闲,VOCATION:工作    List emps = Arrays.asList(        new Employee(102, "李四", 59, 6666.66, Status.BUSY),        new Employee(101, "张三", 18, 9999.99, Status.FREE),        new Employee(103, "王五", 28, 3333.33, Status.VOCATION),        new Employee(104, "赵六", 8, 7777.77, Status.BUSY),        new Employee(104, "赵六", 8, 7777.77, Status.FREE),        new Employee(104, "赵六", 8, 7777.77, Status.FREE),        new Employee(105, "田七", 38, 5555.55, Status.BUSY)   );    // 测试检查 match    @Test    public void test1(){        boolean bl = emps.stream()           .allMatch((e) -> e.getStatus().equals(Status.BUSY)); ​        System.out.println(bl); ​        boolean bl1 = emps.stream()           .anyMatch((e) -> e.getStatus().equals(Status.BUSY)); ​        System.out.println(bl1); ​        boolean bl2 = emps.stream()           .noneMatch((e) -> e.getStatus().equals(Status.BUSY)); ​        System.out.println(bl2);   }    // 测试 findFirst() 和 findAny()    @Test public void test2(){ Optional op = emps.stream() .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())) .findFirst();

System.out.println(op.get());

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

Optional<Employee> op2 = emps.parallelStream()
  .filter((e) -> e.getStatus().equals(Status.FREE))
  .findAny();

System.out.println(op2.get());

}    // 测试 max,min    @Test    public void test3(){ long count = emps.stream() .filter((e) -> e.getStatus().equals(Status.FREE)) .count();

System.out.println(count);

Optional<Double> op = emps.stream()
  .map(Employee::getSalary)
  .max(Double::compare);

System.out.println(op.get());

Optional<Employee> op2 = emps.stream()
  .min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));

System.out.println(op2.get());

} } ```

注意:流进行了终止操作后,则该流不能再次使用。

如下的 max 返回无效,因为 count 已经终止操作。

public void test4(){    Stream<Employee> stream = emps.stream()       .filter((e) -> e.getStatus().equals(Status.FREE)); ​    long count = stream.count(); ​    stream.map(Employee::getSalary)       .max(Double::compare); }

归约

| 方法 | 描述 | | -------------------------------- | ------------------------------------- | | reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 T,初始值是 iden | | reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。 返回 Optional<T> |

注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

代码示例:

``` /** * 归约 *   reduce(T identity, BinaryOperator) / reduce(BinaryOperator):可以将流中元素反复结合起来,得到一个值。 */ public class TestStreamAPI3 {    List emps = Arrays.asList( new Employee(102, "李四", 79, 6666.66, Status.BUSY), new Employee(101, "张三", 18, 9999.99, Status.FREE), new Employee(103, "王五", 28, 3333.33, Status.VOCATION), new Employee(104, "赵六", 8, 7777.77, Status.BUSY), new Employee(104, "赵六", 8, 7777.77, Status.FREE), new Employee(104, "赵六", 8, 7777.77, Status.FREE), new Employee(105, "田七", 38, 5555.55, Status.BUSY) );    @Test public void test1(){ List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Integer sum = list.stream()
  .reduce(0, (x, y) -> x + y); // 0 是初始值

​ System.out.println(sum);

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

Optional<Double> op = emps.stream()
  .map(Employee::getSalary)
  .reduce(Double::sum);

​ System.out.println(op.get()); }     } ```

收集

| 方法 | 描述 | | -------------------- | --------------------------------------------------- | | collect(Collector c) | 将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法 |

Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

| 方法 | 返回类型 | 作用 | | ----------------- | --------------------- | --------------------------------------------------- | | toList | List | 把流中元素收集到 List | | toSet | Set | 把流中元素收集到 Set | | toCollection | Collection | 把流中元素收集到创建的集合 | | counting | Long | 计算流中元素的个数 | | summingInt | Integer | 对流中元素的整数属性求和 | | averagingInt | Double | 计算流中元素 Integer 属性的平均值 | | summarizingInt | IntSummaryStatistics | 收集流中 Integer 属性的统计值。如:平均值 | | joining | String | 连接流中每个字符串 | | maxBy | Optional | 根据比较器选择最大值 | | minBy | Optional | 根据比较器选择最小值 | | reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用 BinaryOperator 与流中元素逐个结合,从而归约成单个值 | | collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转换函数 | | groupingBy | Map > | 根据某属性值对流分组,属性为 K,结果为 T | | partitioningBy | Map > | 根据 true 或 false 进行分区 |

代码示例:

/** * 收集 *   collect:将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法 */ public class TestStreamAPI3 {    List<Employee> emps = Arrays.asList(        new Employee(102, "李四", 79, 6666.66, Status.BUSY),        new Employee(101, "张三", 18, 9999.99, Status.FREE),        new Employee(103, "王五", 28, 3333.33, Status.VOCATION),        new Employee(104, "赵六", 8, 7777.77, Status.BUSY),        new Employee(104, "赵六", 8, 7777.77, Status.FREE),        new Employee(104, "赵六", 8, 7777.77, Status.FREE),        new Employee(105, "田七", 38, 5555.55, Status.BUSY)   );    @Test    public void test3(){        List<String> list = emps.stream()           .map(Employee::getName)           .collect(Collectors.toList()); ​        list.forEach(System.out::println); ​        System.out.println("----------------------------------"); ​        Set<String> set = emps.stream()           .map(Employee::getName)           .collect(Collectors.toSet()); ​        set.forEach(System.out::println); ​        System.out.println("----------------------------------"); ​        HashSet<String> hs = emps.stream()           .map(Employee::getName)           .collect(Collectors.toCollection(HashSet::new)); ​        hs.forEach(System.out::println);   }    @Test    public void test4(){        Optional<Double> max = emps.stream()           .map(Employee::getSalary)           .collect(Collectors.maxBy(Double::compare)); ​        System.out.println(max.get()); ​        Optional<Employee> op = emps.stream()           .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))); ​        System.out.println(op.get()); ​        Double sum = emps.stream()           .collect(Collectors.summingDouble(Employee::getSalary)); ​        System.out.println(sum); ​        Double avg = emps.stream()           .collect(Collectors.averagingDouble(Employee::getSalary)); ​        System.out.println(avg); ​        Long count = emps.stream()           .collect(Collectors.counting()); ​        System.out.println(count); ​        System.out.println("--------------------------------------------"); ​        DoubleSummaryStatistics dss = emps.stream()           .collect(Collectors.summarizingDouble(Employee::getSalary)); ​        System.out.println(dss.getMax());   } ​    // 分组    @Test    public void test5(){        Map<Status, List<Employee>> map = emps.stream()           .collect(Collectors.groupingBy(Employee::getStatus)); ​        System.out.println(map);   } ​    // 多级分组    @Test    public void test6(){        Map<Status, Map<String, List<Employee>>> map = emps.stream()           .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {                if(e.getAge() >= 60)                    return "老年";                else if(e.getAge() >= 35)                    return "中年";                else                    return "成年";           }))); ​        System.out.println(map);   } ​    // 分区    @Test    public void test7(){        Map<Boolean, List<Employee>> map = emps.stream()           .collect(Collectors.partitioningBy((e) -> e.getSalary() >= 5000)); ​        System.out.println(map);   } ​    @Test    public void test8(){        String str = emps.stream()           .map(Employee::getName)           .collect(Collectors.joining("," , "----", "----")); ​        System.out.println(str);   } ​    @Test    public void test9(){        Optional<Double> sum = emps.stream()           .map(Employee::getSalary)           .collect(Collectors.reducing(Double::sum)); ​        System.out.println(sum.get());   } }

并行流与串行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。这样一来,你就可以自动把给定操作的工作负荷分配给多核处理器的所有内核,让他们都忙起来。整个过程无需程序员显示实现优化。

Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel()sequential() 在并行流与顺序流之间进行切换。

了解Fork/Join框架

Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。

image-20220209212215740

Fork/Join框架与传统线程池的区别

采用 工作窃取模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从另外一个随机线程的队列中偷一个并把它放在自己的队列中。即自己空闲时,就去偷取别线程的任务来执行。

相对于一般的线程池实现,Fork/Join 框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在 Fork/Join 框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。

代码示例

Java8 已经优化了 Fork/Join 框架,因为 Java7 之前使用该框架代码会比较繁琐。

如下 Java7 前的 Fork/Join 框架简易代码,计算累加值:

public class ForkJoinCalculate extends RecursiveTask<Long>{ ​   private static final long serialVersionUID = 13475679780L;     private long start;   private long end;     private static final long THRESHOLD = 10000L; // 临界值     public ForkJoinCalculate(long start, long end) {      this.start = start;      this.end = end;   }     @Override   protected Long compute() {      long length = end - start;            if(length <= THRESHOLD){         long sum = 0;                 for (long i = start; i <= end; i++) {            sum += i;         }                 return sum;     }else{  // 超出临界值后,将累加分成多个子任务,调用新的进程继续累加         long middle = (start + end) / 2;                 ForkJoinCalculate left = new ForkJoinCalculate(start, middle);         left.fork(); // 拆分,并将该子任务压入线程队列                 ForkJoinCalculate right = new ForkJoinCalculate(middle+1, end);         right.fork();                 return left.join() + right.join(); // 累加成功后,再把子任务的结果合并在一起     }         } ​ }

计算出累加 10 亿的花费时间:

public class TestForkJoin {   // Fork/Join 框架花费时间   @Test   public void test1(){      long start = System.currentTimeMillis();            ForkJoinPool pool = new ForkJoinPool();      ForkJoinTask<Long> task = new ForkJoinCalculate(0L, 10000000000L);            long sum = pool.invoke(task);      System.out.println(sum);            long end = System.currentTimeMillis();            System.out.println("耗费的时间为: " + (end - start)); // 花费时间:113808   }   // 传统 for 循环花费时间   @Test   public void test2(){      long start = System.currentTimeMillis();            long sum = 0L;            for (long i = 0L; i <= 10000000000L; i++) {         sum += i;     }            System.out.println(sum);            long end = System.currentTimeMillis();            System.out.println("耗费的时间为: " + (end - start)); // 花费时间:31583   }     ​ }

而 Java8 之后,Fork/Join 框架的代码优化更加简洁,通过 parallel()sequential() 在并行流与顺序流之间进行切换。如下:

public class TestForkJoin {    @Test    public void test3(){        long start = System.currentTimeMillis(); ​        Long sum = LongStream.rangeClosed(0L, 10000000000L)           .parallel()  // 并行流           .sum(); ​        System.out.println(sum); ​        long end = System.currentTimeMillis(); ​        System.out.println("耗费的时间为: " + (end - start)); // 花费时间:18926   } }

Optional API

Optional 类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

常用方法:

| 方法 | 作用 | | ------------------------ | ----------------------------------------------- | | Optional.of(T t) | 创建一个 Optional 实例 | | Optional.empty() | 创建一个空的 Optional 实例 | | Optional.ofNullable(T t) | 若 t 不为 null,创建 Optional 实例,否则创建空实例 | | isPresent() | 判断是否包含值 | | orElse(T t) | 如果调用对象包含值,返回该值,否则返回 t | | orElseGet(Supplier s) | 如果调用对象包含值,返回该值,否则返回 s 获取的值 | | map(Function f) | 如果有值对其处理,并返回处理后的 Optional,否则返回 Optional.empty() | | flatMap(Function mapper) | 与 map 类似,要求返回值必须是 Optional |

代码示例

/* * 一、Optional 容器类:用于尽量避免空指针异常 * Optional.of(T t) : 创建一个 Optional 实例 * Optional.empty() : 创建一个空的 Optional 实例 * Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例 * isPresent() : 判断是否包含值 * orElse(T t) : 如果调用对象包含值,返回该值,否则返回 t * orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值 * map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty() * flatMap(Function mapper):与 map 类似,要求返回值必须是Optional */ public class TestOptional { ​    @Test    public void test1(){        Optional<Employee> op = Optional.of(new Employee());        Employee emp = op.get();        System.out.println(emp);   } ​    @Test    public void test2(){        Optional<Employee> op = Optional.ofNullable(null);        System.out.println(op.get()); // 空指针 ​        Optional<Employee> op = Optional.empty();        System.out.println(op.get()); // 空指针   } ​    @Test    public void test3(){        Optional<Employee> op = Optional.ofNullable(new Employee()); ​        if(op.isPresent()){            System.out.println(op.get());       } ​        Employee emp = op.orElse(new Employee("张三"));        System.out.println(emp); ​        Employee emp2 = op.orElseGet(() -> new Employee());        System.out.println(emp2);   } ​    @Test    public void test4(){        Optional<Employee> op = Optional.of(new Employee(101, "张三", 18, 9999.99)); ​        Optional<String> op2 = op.map(Employee::getName);        System.out.println(op2.get()); ​        Optional<String> op3 = op.flatMap((e) -> Optional.of(e.getName()));        System.out.println(op3.get());   } ​    @Test    public void test5(){        Man man = new Man(); ​        String name = getGodnessName(man);        System.out.println(name);   } ​    //需求:获取一个男人心中女神的名字    public String getGodnessName(Man man){        if(man != null){            Godness g = man.getGod(); ​            if(g != null){                return g.getName();           }       } ​        return "苍老师";   } ​    // 运用 Optional 的实体类    @Test    public void test6(){        Optional<Godness> godness = Optional.ofNullable(new Godness("林志玲")); ​        Optional<NewMan> op = Optional.ofNullable(new NewMan(godness));        String name = getGodnessName2(op);        System.out.println(name);   } ​    public String getGodnessName2(Optional<NewMan> man){        return man.orElse(new NewMan())           .getGodness()           .orElse(new Godness("苍老师"))           .getName();   } }

模拟实际场景

每个男生心里都可能有一个女神,也可能没有,可以利用 Optional 来解决空指针异常。

首先创建没有利用 Optional,而是 if 判断 null 的男生实体类:

public class Man { ​    private Godness god; ​    public Man() {   } ​    public Man(Godness god) {        this.god = god;   } ​    public Godness getGod() {        return god;   } ​    public void setGod(Godness god) {        this.god = god;   } ​    @Override    public String toString() {        return "Man [god=" + god + "]";   } }

女神实体类(有名字):

public class Godness { ​    private String name; ​    public Godness() {   } ​    public Godness(String name) {        this.name = name;   } ​    public String getName() {        return name;   } ​    public void setName(String name) {        this.name = name;   } ​    @Override    public String toString() {        return "Godness [name=" + name + "]";   } }

模拟获取男生心里的女神名字:

/* * 一、Optional 容器类:用于尽量避免空指针异常 * Optional.of(T t) : 创建一个 Optional 实例 * Optional.empty() : 创建一个空的 Optional 实例 * Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例 * isPresent() : 判断是否包含值 * orElse(T t) : 如果调用对象包含值,返回该值,否则返回 t * orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值 * map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty() * flatMap(Function mapper):与 map 类似,要求返回值必须是Optional */ public class TestOptional { ​    @Test    public void test5(){        Man man = new Man(); ​        String name = getGodnessName(man);        System.out.println(name);   } ​    // 需求:获取一个男人心中女神的名字    public String getGodnessName(Man man){        if(man != null){            Godness g = man.getGod();            if(g != null){                return g.getName();           }       }        return "冰糖"; // 默认值   } }

上面代码是我们习惯使用的多层 if 来解决 null 问题,那么如何利用 Optional 解决呢?

创建 Optional 的男生实体类:

``` // 注意:Optional 不能被序列化 public class NewMan { ​ private Optional godness = Optional.empty(); // 必须给个值,否则默认为 null

private Godness god;

public Optional getGod(){ return Optional.of(god); } ​ public NewMan() { } ​ public NewMan(Optional godness) { this.godness = godness; } ​ public Optional getGodness() { return godness; } ​ public void setGodness(Optional godness) { this.godness = godness; } ​ @Override public String toString() { return "NewMan [godness=" + godness + "]"; } } ```

模拟获取男生心里的女神名字:

/* * 一、Optional 容器类:用于尽量避免空指针异常 * Optional.of(T t) : 创建一个 Optional 实例 * Optional.empty() : 创建一个空的 Optional 实例 * Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例 * isPresent() : 判断是否包含值 * orElse(T t) : 如果调用对象包含值,返回该值,否则返回 t * orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值 * map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty() * flatMap(Function mapper):与 map 类似,要求返回值必须是Optional */ public class TestOptional {    // 运用 Optional 的实体类    @Test    public void test6(){        Optional<Godness> godness = Optional.ofNullable(new Godness("雪梨")); ​        Optional<NewMan> op = Optional.ofNullable(new NewMan(godness));        String name = getGodnessName2(op);        System.out.println(name);   } ​    public String getGodnessName2(Optional<NewMan> man){        return man.orElse(new NewMan())           .getGodness()           .orElse(new Godness("冰糖"))  // 默认值           .getName();   } }

接口中的默认方法与静态方法

接口中的默认方法

Java 8 中允许接口中包含具有具体实现的方法,该方法称为「默认方法」,默认方法使用 default 关键字修饰。

例如:

public interface MyFun { default String getName(){ return "哈哈哈"; } }

如果有一个类也定义了相同的方法,如:

public class MyClass {    public String getName(){ return "嘿嘿嘿"; } }

再来一个类同时继承和实现 MyClassMyFun,该使用谁呢?使用类 MyClass

public class SubClass extends MyClass implements MyFun {     } class Test {    public static void main(String[] args) { SubClass sc = new SubClass();        System.out.println(sc.getName()); // 输出:嘿嘿嘿 } }

或者再多出一个有相同方法的接口

``` public interface MyInterface {

default String getName(){ return "呵呵呵"; } } ```

SubClass 类同时实现两个接口,那么该使用哪个的方法呢?需要重写方法来决定使用哪个接口的方法

public class SubClass implements MyFun, MyInterface {    @Override public String getName() { return MyInterface.super.getName(); } }

接口默认方法的「类优先」原则

若一个接口中定义了一个默认方法,而另外一个父类或接口中 又定义了一个同名的方法时

  • 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略
  • 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突

接口中的静态方法

Java8 中,接口中允许添加静态方法。

例如:

``` public interface MyInterface {

public static void show(){ System.out.println("接口中的静态方法"); } } ```

通过 MyInterface.show 调用即可。

新时间日期API

新时间日期API出现原因

为什么 Java 8 要出现新的时间日期 API,以前的 API 有什么问题吗?

当然有,那就是线程不安全问题,当多个线程同时调用一个日期 API 时,会报错。

举个例子:

public class TestSimpleDateFormat {     public static void main(String[] args) throws Exception {            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");            Callable<Date> task = new Callable<Date>() { ​         @Override         public Date call() throws Exception {            return sdf.parse("20161121");         }             }; ​      ExecutorService pool = Executors.newFixedThreadPool(10); // 创建 10 个线程            List<Future<Date>> results = new ArrayList<>();            for (int i = 0; i < 10; i++) {         results.add(pool.submit(task));  // 执行线程,并将结果添加到 List 集合     }            for (Future<Date> future : results) {         System.out.println(future.get()); // 报错,因为传统时间 API 有线程不安全问题     }            pool.shutdown();   } }

上面代码创建 10 个线程,循环 10 次获取某个时间点,最终瞬间执行 100 次获取时间的操作,导致报错,因为传统时间 API 有线程不安全问题,那么 Java 8 以前如何解决呢?

利用 ThreadLocal 来加锁即可,如下:

public class TestSimpleDateFormat { ​    public static void main(String[] args) throws Exception { ​        // 解决多线程安全问题        Callable<Date> task = new Callable<Date>() { ​            @Override            public Date call() throws Exception {                return DateFormatThreadLocal.convert("20161121");           } ​       }; ​        ExecutorService pool = Executors.newFixedThreadPool(10); ​        List<Future<Date>> results = new ArrayList<>(); ​        for (int i = 0; i < 10; i++) {            results.add(pool.submit(task));       } ​        for (Future<Date> future : results) {            System.out.println(future.get());       } ​        pool.shutdown();   } } // 加锁 class DateFormatThreadLocal { ​    private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){ ​        protected DateFormat initialValue(){            return new SimpleDateFormat("yyyyMMdd");       } ​   }; ​    public static final Date convert(String source) throws ParseException{        return df.get().parse(source);   } ​ }

Java 8 之后如何解决这个问题呢?

利用三大核心类的 LocalDate 可以更加简洁的替换 ThreadLocal 来解决日期线程不安全问题。如下:

``` public class TestSimpleDateFormat {

public static void main(String[] args) throws Exception {

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");

Callable<LocalDate> task = new Callable<LocalDate>() {

​ @Override public LocalDate call() throws Exception { LocalDate ld = LocalDate.parse("20161121", dtf); return ld; }

};

​ ExecutorService pool = Executors.newFixedThreadPool(10);

List<Future<LocalDate>> results = new ArrayList<>();

for (int i = 0; i < 10; i++) {
  results.add(pool.submit(task));
}

for (Future<LocalDate> future : results) {
  System.out.println(future.get());
}

pool.shutdown();

} ​ } ```

三大核心类

三大类 LocalDateLocalTimeLocalDateTime 类的实例是 不可变的对象(线程安全),分别表示使用 ISO-8601 日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

| 方法 | 描述 | | ----------------------------------------------- | ----------------------------------------- | | now() | 静态方法,根据当前时间创建对象 | | of() | 静态方法,根据指定日期/时间创建对象 | | plusDays, plusWeeks, plusMonths, plusYears | 向当前 LocalDate 对象添加几天、几周、几个月、几年 | | minusDays、minusWeeks、minusMonths、minusYears | 从当前 LocalDate 对象减去几天、几周、几个月、几年 | | plus、minus | 添加或减少一个 Duration 或 Period | | withDayOfMonth、withDayOfYear、withMonth、withYear | 将月份天数、年份天数、月份、年份修改为指定的值并返回新的 LocalDate 对象 | | getDayOfMonth | 获得月份天数(1-31) | | getDayOfYear | 获得年份天数(1-366) | | getDayOfWeek | 获得星期几(返回一个 DayOfWeek 枚举值) | | getMonth | 获得月份,返回一个Month 枚举值 | | getMonthValue | 获得月份(1-12) | | getYear | 获得年份 | | until | 获得两个日期之间的 Period 对象,或者指定ChronoUnits 的数字 | | isBefore、isAfter | 比较两个 LocalDate | | isLeapYear | 判断是否是闰年 |

now() 示例:

LocalDate localDate = LocalDate.now(); LocalDate localDate = LocalTime.now(); LocalDateTime localDateTime= LocalDateTime.now();

of() 示例:

LocalDate localDate = LocalDate.of(2016, 10, 26); LocalTime localTime = LocalTime.of(02, 22, 56); LocalDateTime localDateTime = LocalDateTime.of(2016, 10,  26, 12, 10, 55);

演示三大类的常用 API

public class TestLocalDateTime { ​    // LocalDateTime:相当于 LocalDate + LocalTime    @Test    public void test1() {        LocalDateTime ldt = LocalDateTime.now();        System.out.println(ldt); ​        LocalDateTime ld2 = LocalDateTime.of(2016, 11, 21, 10, 10, 10); // 直接指定年月日,时分秒        System.out.println(ld2); ​        LocalDateTime ldt3 = ld2.plusYears(20); // 加 20 年        System.out.println(ldt3); ​        LocalDateTime ldt4 = ld2.minusMonths(2); // 减 2 个月        System.out.println(ldt4); ​        System.out.println(ldt.getYear()); // 获取年        System.out.println(ldt.getMonthValue()); // 获取月        System.out.println(ldt.getDayOfMonth()); // 获取日        System.out.println(ldt.getHour()); // 获取时        System.out.println(ldt.getMinute()); // 获取分        System.out.println(ldt.getSecond()); // 获取秒   } }

Instant时间戳

用于「时间戳」的运算。它是以 Unix 元年(传统的设定为 UTC 时区 1970 年 1 月 1 日午夜时分)开始所经历的描述进行运算。

代码示例:

public class TestLocalDateTime { ​    // Instant : 时间戳。(使用 Unix 元年 1970 年 1 月 1 日 00:00:00 所经历的毫秒值)    @Test    public void test2() {        Instant ins = Instant.now();  // 默认使用 UTC 时区,所以比中国时间少 8 小时        System.out.println(ins); ​        OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8)); // 加 8 小时:中国时区        System.out.println(odt); ​        System.out.println(ins.getNano()); // 获取纳秒 ​        Instant ins2 = Instant.ofEpochSecond(5); // 获取秒        System.out.println(ins2);   } }

Duration和Period

  • Duration:用于计算两个「时间」间隔
  • Period:用于计算两个间隔

public class TestLocalDateTime { ​    // Duration : 用于计算两个「时间」间隔    // Period : 用于计算两个「日期」间隔    @Test    public void test3(){        Instant ins1 = Instant.now(); ​        try {            Thread.sleep(1000);       } catch (InterruptedException e) {       } ​        Instant ins2 = Instant.now(); ​        System.out.println("所耗费时间为:" + Duration.between(ins1, ins2)); // 计算时间 ​        System.out.println("----------------------------------"); ​        LocalDate ld1 = LocalDate.now();        LocalDate ld2 = LocalDate.of(2011, 1, 1); ​        Period pe = Period.between(ld2, ld1); // 计算日期        System.out.println(pe.getYears()); // 获取年        System.out.println(pe.getMonths()); // 获取月        System.out.println(pe.getDays()); // 获取日   } }

日期的操纵

  • TemporalAdjuster: 时间校正器。有时我们可能需要获取例如:将日期调整到「下个周日」等操作
  • TemporalAdjusters: 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现

代码示例:

public class TestLocalDateTime { ​    // TemporalAdjuster : 时间校正器    @Test    public void test4(){        LocalDateTime ldt = LocalDateTime.now();        System.out.println(ldt); ​        LocalDateTime ldt2 = ldt.withDayOfMonth(10); // 直接修改月份为 10        System.out.println(ldt2); ​        LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); // 下周日        System.out.println(ldt3); ​        // 自定义:下一个工作日        LocalDateTime ldt5 = ldt.with((l) -> {            LocalDateTime ldt4 = (LocalDateTime) l; ​            DayOfWeek dow = ldt4.getDayOfWeek(); // 获取日对象 ​            if(dow.equals(DayOfWeek.FRIDAY)){ // 如果是周五                return ldt4.plusDays(3); // 加 3 天就是周一           }else if(dow.equals(DayOfWeek.SATURDAY)){   // 如果是周六                return ldt4.plusDays(2); // 加 2 天就是周一           }else{ // 如果是周日                return ldt4.plusDays(1); // 加 1 天就是周一           }       });        System.out.println(ldt5);   } }

解析与格式化

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:

  • 预定义的标准格式
  • 语言环境相关的格式
  • 自定义的格式

public class TestLocalDateTime { ​    // DateTimeFormatter : 解析和格式化日期或时间    @Test    public void test5(){        // DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE; // 默认标准格式 ​        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E"); // 自定义格式 ​        LocalDateTime ldt = LocalDateTime.now();        String strDate = ldt.format(dtf); ​        System.out.println(strDate); ​        LocalDateTime newLdt = ldt.parse(strDate, dtf); // 转换为 LocalDateTime 标准格式        System.out.println(newLdt);   } }

时区的处理

Java8 中加入了对时区的支持,带时区的时间为分别为:ZonedDateZonedTimeZonedDateTime

其中每个时区都对应着 ID,地区 ID 都为「{区域}/{城市}」的格式 例如:Asia/Shanghai 等

  • ZoneId:该类中包含了所有的时区信息
  • getAvailableZoneIds():可以获取所有时区信息
  • of(id):用指定的时区信息获取ZoneId 对象

代码示例:

public class TestLocalDateTime { ​    // ZonedDate、ZonedTime、ZonedDateTime :带时区的时间或日期    @Test    public void test6(){        Set<String> set = ZoneId.getAvailableZoneIds(); // 获取所有时区信息        set.forEach(System.out::println);   } ​    @Test    public void test7(){        LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); // 指定时区        System.out.println(ldt); ​        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific")); // 指定时区        System.out.println(zdt);   } }

与传统日期处理的转换

| 类 | 传统方法 | 转换方法 | | ------------------------------------------------------------- | -------------------------------------- | --------------------------- | | java.time.Instant VS java.util.Date | Date.from(instant) | date.toInstant() | | java.time.Instant VS java.sql.Timestamp | Timestamp.from(instant) | timestamp.toInstant() | | java.time.ZonedDateTime VS java.util.GregorianCalendar | GregorianCalendar.from(zonedDateTim e) | cal.toZonedDateTime() | | java.time.LocalDate VS java.sql.Time | Date.valueOf(localDate) | date.toLocalDate() | | java.time.LocalTime VS java.sql.Time | Date.valueOf(localDate) | date.toLocalTime() | | java.time.LocalDateTime VS java.sql.Timestamp | Timestamp.valueOf(localDateTime) | timestamp.toLocalDateTime() | | java.time.ZoneId VS java.util.TimeZone | Timezone.getTimeZone(id) | timeZone.toZoneId() | | java.time.format.DateTimeFormatterVS java.text.DateFormat | formatter.toFormat() | 无 |

Try升级

Java 8 之前,我们写的 try-catch 结尾都要写 finally 来关闭资源,则 Java 8 升级了 try-catch 的用法,直接在 try 加个括号,里面填写对象的实例化即可自动关闭资源,对比代码如下:

// Java 8 之前 public void testTry1(){    InputStreamReader reader = null;    try{        reader  = new InputStreamReader(System.in);        //读取数据的过程:略        reader.read();   }catch (IOException e){        e.printStackTrace();   }finally{        //资源的关闭操作        if(reader != null){            try {                reader.close();           } catch (IOException e) {                e.printStackTrace();           }       }   } ​ } // Java 8 public void testTry2(){    try(InputStreamReader reader = new InputStreamReader(System.in)){        // 读取数据的过程:略        reader.read();   }catch(IOException e){        e.printStackTrace();   } }

注意:java 8 中要求资源对象的实例化,必须放在 try 的一对 () 内完成。

Java 9 又进行了升级,对象的实例化可以放在外面,try 的一对 () 传入对象即可,如下:

public void testTry3(){    InputStreamReader reader = new InputStreamReader(System.in);    try(reader){        //读取数据的过程:略        reader.read();   }catch(IOException e){        e.printStackTrace();   } }

重复注解与类型注解

Java 8 对注解处理提供了两点改进:

  • 可重复的注解,JDK 8 之前只允许一个类、方法体等上方只出现一个注解
  • 可用于类型的注解

代码示例

创建一个数组注解,用于开启可重复的注解:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.RUNTIME) publie @interface MyAnnotations {    MyAnnotation[] value(); }

创建可重复的注解类:

@Repeatabie(MyAnnotations.class) // 开启使用重复注解 @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation {    String value(); }

测试使用重复注解:

public class TestAnnotations {    @MyAnnotation("Hello")    @MyAnnotation("World")    public void show() {   } }

可用于类型的注解,即在参数或者属性上可以加入注解,类似于 Spring Boot 的 @RequestParam()

public class TestAnnotations {    @MyAnnotation("Hello")    @MyAnnotation("World")    public void show(@MyAnnotation("kele") String str) {   } }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JDK 新特性篇:JDK 8 新特性详解 的相关文章

  • 任务“:app:dexDebug”执行失败

    我目前正在处理我的项目 我决定将我的 Android Studio 更新到新版本 但在我导入项目后 它显示如下错误 Information Gradle tasks app assembleDebug app preBuild UP TO
  • 如何在 Eclipse 中用阿拉伯语读写

    我在 eclipse 中编写了这段代码来获取一些阿拉伯语单词 然后打印它们 public class getString public static void main String args throws Exception PrintS
  • Java 创建浮雕(红/蓝图像)

    我正在编写一个 Java 游戏引擎 http victoryengine org http victoryengine org 并且我一直在尝试生成具有深度的 3D 图像 您可以使用那些红色 蓝色眼镜看到 我正在使用 Java2D 进行图形
  • 由于连接超时,无法通过 ImageIO.read(url) 获取图像

    下面的代码似乎总是失败 URL url new URL http userserve ak last fm serve 126 8636005 jpg Image img ImageIO read url System out printl
  • JTree 节点不会被直观地选择

    不知何故 我无法为我的 JTree 节点启用 选择突出显示 我正在我的项目中使用自定义单元格渲染器 这很可能导致此问题 这是完整的渲染器类代码 protected class ProfessionTreeCellRenderer exten
  • PropertySources 中各种源的优先级

    Spring引入了新的注释 PropertySources对于所有标记为的类 Configuration since 4 0 需要不同的 PropertySource作为论证 PropertySources PropertySource c
  • Java 变量的作用域

    我不明白为什么这段代码的输出是10 package uno public class A int x 10 A int x 12 new B public static void main String args int x 11 new
  • 使用 java 按电子邮件发送日历邀请

    我正在尝试使用 java 发送每封电子邮件的日历邀请 收件人收到电子邮件 但不会显示接受或拒绝的邀请 而是将该事件自动添加到他的日历中 我正在使用 ical4j jar 构建活动 邀请 private Calendar getInvite
  • 想要开发像 Facebook 这样的网站 - 处理数百万个请求 - 高性能 [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我想用 Java 开发一个像 Fac
  • 如何在java中使jpeg无损?

    有没有人可以告诉我如何使用编写 jpeg 文件losslessjava中的压缩 我使用下面的代码读取字节来编辑字节 WritableRaster raster image getRaster DataBufferByte buffer Da
  • 打印包含 JBIG2 图像的 PDF

    请推荐一些库 帮助我打印包含 JBIG2 编码图像的 PDF 文件 PDFRenderer PDFBox别帮我 这些库可以打印简单的 PDF 但不能打印包含 JBIG2 图像的 PDF PDFRenderer尝试修复它 根据 PDFRedn
  • 覆盖 MATLAB 默认静态 javaclasspath 的最佳方法

    MATLAB 配置为在搜索用户可修改的动态路径之前搜索其静态 java 类路径 不幸的是 静态路径包含相当多非常旧的公共库 因此如果您尝试使用新版本 您可能最终会加载错误的实现并出现错误 例如 静态路径包含 google collectio
  • tomcat 过滤所有 web 应用程序

    问题 我想对所有网络应用程序进行过滤 我创建了一个过滤器来监视对 apache tomcat 服务器的请求 举例来说 它称为 MyFilter 我在 netbeans 中创建了它 它创建了 2 个独立的目录 webpages contain
  • 从 html 页面和 javascript 调用 java webservice

    我正在尝试从 javascript 调用 java 实现的 Web 服务 使用 NetBeans IDE 我读过很多关于 jQuery 和 AJAX 的内容 但我似乎无法掌握它 假设我的 Web 服务 WSDL 位于 http localh
  • 从 Java 日历迁移到 Joda 日期时间

    以前 当我第一次设计股票应用相关软件时 我决定使用java util Date表示股票的日期 时间信息 后来我体会到了大部分方法java util Date已弃用 因此 很快 我重构了所有代码以利用java util Calendar 然而
  • 从 Stax XMLStreamReader 读取以解组部分

    我正在使用 Stax 游标 API 从大型 xml 文件中提取数据 当前 我转到特殊标签的开头并使用 JAXB 解组该标签 这对于格式良好的 xml 文件效果很好 但不久前我有一个文档 其中数十万个标签中有一个未关闭 JAXB 使用 XML
  • 阻止 OSX 变音符号为所有用户禁用 Java 中的 KeyBindings?

    注 我知道这个问题 https stackoverflow com questions 40335285 java keybinds stop working after holding down a key用户必须输入终端命令才能解决此问
  • 如何移动图像(动画)?

    我正在尝试在 x 轴上移动船 还没有键盘 我如何将运动 动画与boat png而不是任何其他图像 public class Mama extends Applet implements Runnable int width height i
  • Path2D 上的鼠标指针检测

    我构建了一个Path2D http docs oracle com javase 7 docs api java awt geom Path2D html表示由直线组成的未闭合形状 我希望能够检测何时单击鼠标并且鼠标指针靠近路径 在几个像素
  • 重写Object类的finalize()方法有什么用?

    据我所知 在java中如果我们想手动调用垃圾收集器 我们可以执行System gc 1 我们在重写的finalize 方法中做了哪些操作 2 如果我们想手动调用JVM垃圾收集器 是否需要重写finalize 方法 我们在重写的 Finali

随机推荐

  • 俄罗斯方块(C++)

    目录 一 俄罗斯方块简易版的实现 1 图形的存储 2 图形的显示 3 图形的操作 4 碰撞检测 1 碰撞检测 移动 2 碰撞检测 旋转 5 消除整行 6 游戏结束 7 完整代码 二 俄罗斯方块简易版的升级 1 新增属性 2 更改初始化函数
  • CSS 定位(position) (三)

    叠放次序 z index 当对多个元素同时设置定位时 定位元素之间有可能会发生重叠 在CSS中 要想调整重叠定位元素的堆叠顺序 可以对定位元素应用z index层叠等级属性 其取值可为正整数 负整数和0 比如 z index 2 注意 z
  • 阿里云磁盘异常爆满的原因排查及解决方法

    阿里云磁盘异常爆满的原因排查及解决方法 网上搜了好多没有找到解决方法 第一步登录服务器 使用 df h 命令查看磁盘使用率 接下来逐步排查占用内存的文件 输入 du h 显示没有异常大文件 输入 du h max depth 1 后查到异常
  • 论文笔记之ST-GCN

    通过将图卷积网络扩展到时空图模型 设计了一种 用于动作识别的骨架序列 的通用表示 AAAI 2018 录用 论文地址 https arxiv org abs 1801 07455 1 摘要 动态人体骨架模型带有进行动作识别的重要信息 传统的
  • git clone项目报错,The requested URL returned error: 500

    git clone项目报错 The requested URL returned error 500 如何解决 试试修改电脑中存储的账号密码 有项目权限 至少在网页上能通过链接访问当前Git内容 曾在当前电脑登录过git账号 满足以上两种情
  • flutter 保存列表状态和控制列表状态

    class DiscoverActivePageState extends State
  • vue中列表渲染相关知识(v-for)

    普通的列表使用ul包含多个li实现 这样往往不够灵活 在原生js中能够动态渲染列表具体实现如下 ul ul 我们再看看vue中如何实现列表渲染的 b b
  • 微信小程序获取当前位置 地图定位导航-详细代码

    小程序获取当前位置 回到当前位置 地图定位 导航 效果 因为小程序更新了获取地理位置API接口 需要先在app json中配置一下permission字段 不然会报微信小程序getLocation 需要在app json中声明permiss
  • 扩展欧几里得算法

    扩展欧几里得算法是啥 那就要先知道什么是欧几里得算法 欧几里得算法 扩展欧几里得算法是欧几里得算法的推广 利用欧几里得算法的思想和递归求得贝祖等式a x b y gcd a b 不定方程中的一组x和y的解 原理如下 设a gt b 当b 0
  • Coqui TTS 安装与测试

    前言 本篇记录一下 Coqui TTS 的安装 Coqui TTS 的主要作者是德国人 这个库似乎之前和 Mozilla 的 TTS https github com mozilla TTS 有千丝万缕的关系 但是现在后者的 TTS 已经停
  • 2021年江苏省职业院校技能大赛中职 “网络信息安全”赛项(超详细)

    2021年中职组 网络空间安全 赛项 一 江苏省竞赛任务书 二 任务书解析 三 不懂的可以私信博主 一 江苏省竞赛任务书 一 竞赛时间 8 00 11 00 共计3小时 二 竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第 阶段
  • [附源码]计算机毕业设计Python课程在线测评系统(程序+源码+LW文档)

    该项目含有源码 文档 程序 数据库 配套开发软件 软件安装教程 项目运行 环境配置 Pychram社区版 python3 7 7 Mysql5 7 HBuilderX list pip Navicat11 Django nodejs 项目技
  • 一文理解pytorch张量概念和tensor的三种创建方式!

    1 张量是什么 张量是一个多维数组 它是标量 向量 矩阵的高维拓展 1 1 Variable Variable是 torch autograd中的数据类型 主要用于封装 Tensor 进行自动求导 data 被包装的Tensor grad
  • 超实用的IDEA插件推荐,百万级下载量

    超实用的30多款idea插件 有百万级下载量的优秀插件 你值得拥有 好的工具助你事半功倍 快速协助敲出更漂亮更有效率的代码 搬运工这里收集了很不错的IDEA插件 相信你一定会喜欢的 必备插件列表 Grep Console 自定义控制台输出格
  • C#常用代码

    最近学习用C 写了几个程序 记录一下常用的几个操作 方便以后Copy 文件操作 FileStream fs null try byte buf FileStream fs new FileStream strSampleFileName F
  • Ubuntu16.04下opencv2与ROSkinetic中自带opencv3不兼容问题总结

    1 背景 从ROSindigo换到ROSkinetic ROSkinetic中自带的opencv3 与原来indigo中opencv2不一样 所以原来的涉及opencv的程序都出了问题 最近这两天就一直在改兼容性 清明节最后一天了 总结一下
  • WebStorm2016.2 注册码及激活,2018.6.14亲测有效

    License server激活 这可能是最简单的了 在激活框 选择 License server 输入 http idea iteblog com key php 2018 6 14可用
  • table自定义表格的封装

    前言 对原生的table进行封装 让他满足我们一行显示不同个的需求 实现效果 如图所示 一行显示不同数量的内容 实现代码 1 封装的组件 custom table vue 源码看下面 1 一行显示几个td 2 表头数据 表格数据 3 js封
  • 深度学习python图像标记工具labelTool

    深度学习训练需要标记图像位置和类别 之前用的时候是叫做BBox Label Tool master 遇到大图像就显示不完整了 没有自适应缩放 这是改进后的Python脚本 目录结构 图片目录名images 标签目录名labels 图像目录下
  • JDK 新特性篇:JDK 8 新特性详解

    Java8新特性简介 Java 8 又称为 JDK 1 8 是 Java 语言开发的一个主要版本 Java 8 是 Oracle 公司于 2014 年 3 月发布 可以看成是自 Java 5 以来最具革命性的版本 Java 8 为 Java