JDK 8 新特性

JDK8新特性 JDK8新特性 ​ Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机,Java 仍是企业和开发人员的首选开发平台 内容介绍 了解Java发展史 Lambda表达式 接口的增强 函数式接口 方法引用 Stream API Optional 新时间日期API 其他新特性 一、Java发展历史 1. Java的发展历史 ​ Sun公司在1991年成立了一个称为绿色计划( Gre 阅读详情

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

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

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<Employee> 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<Employee> filterEmployeeAge(List<Employee> emps){
		List<Employee> list = new ArrayList<>();
		
		for (Employee emp : emps) {
			if(emp.getAge() <= 35){
				list.add(emp);
			}
		}
		return list;
	}
    // 需求:获取公司中工资大于 5000 的员工信息
	public List<Employee> filterEmployeeSalary(List<Employee> emps){
		List<Employee> 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<Employee> 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<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() {
			@Override
			public boolean test(Employee t) {
				return t.getId() <= 103;
			}
		});
		
		for (Employee employee : list) {
			System.out.println(employee);
		}
	}
    // 需求:获取公司中工资大于 5000 的员工信息
    public void filterEmployeeSalary(){
		List<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() {
			@Override
			public boolean test(Employee t) {
				return t.getSalary() >= 5000;
			}
		});
		
		for (Employee employee : list) {
			System.out.println(employee);
		}
	}
}

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

public class TestLambda {
    List<Employee> 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<Employee> 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<Employee> 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 消费型接口Tvoid对类型为 T 的对象应用操作,包含方法 void accept(T t);
Supplier 供给型接口无T返回类型为 T 的对象,包含方法 T get();
Function<T, R>函数型接口TR对类型为 T 的对象应用操作,并返回结果。结果是 R 类型的对象。包含方法 R apply(T t);
Predicate 断言型接口Tboolean确定类型为 T 的对象是否满足某约束,并返回 boolean 值。包含方法 boolean test(T t);

消费型接口 Consumer
接口的方法

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 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
接口的方法

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,UR对类型为 T,U 参数应用操作,返回 R 类型的 结果。包含方法为 R apply(T t, U u);
UnaryOperator(Function 子接口)TT对类型为 T 的对象进行一元运算, 并返回 T 类型的结果。包含方法为 T apply(T t);
BinaryOperator(BiFunction 子接口)T,TT对类型为 T 的对象进行二元运算,并返回 T 类型的结果。包含方法为 T apply(T t1, T t2);
BiConsumer<T, U>T,Uvoid对类型为 T,U 参数应用操作。包含方法为 void accept(T t, U u)
ToIntFunctionTint计算 int 值的函数
ToLongFunctionTlong计算 long 值的函数
ToDoubleFunctionTdouble计算 double 值的函数
IntFunctionintR参数为 int 类型的函数
LongFunctionlongR参数为 long 类型的函数
DoubleFunctiondoubleR参数为 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一个数据源(如:集合、数组),获取一个流
  • 中间操作一个中间操作链,对数据源的数据进行处理
  • 终止操作(终端操作)一个终止操作,执行中间操作链,并产生结果

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

如何区分 map 和 flatMap?利用去重 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) 时生成的单个流被合并起来,即扁平化为一个流。
两个方法效果如图:


举例说明
有二箱鸡蛋,每箱 5 个,现在要把鸡蛋加工成煎蛋,然后分给学生。
map 做的事情:把二箱鸡蛋分别加工成煎蛋,还是放成原来的两箱,分给 2 组学生;
flatMap 做的事情:把二箱鸡蛋分别加工成煎蛋,然后放到一起【10 个煎蛋】,分给 10 个学生。
总结
数组去重复时,如果去重复的是不同下标的整体元素,则使用 map,如果去重复的是每个元素自己的内容,则使用 flatMap。
排序

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

代码示例:

/**
 * sorted():自然排序(从小到大)
 * sorted(Comparator com):自定义排序
 */
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 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<Employee> 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<Employee> 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

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

/**
 * 归约
 *   reduce(T identity, BinaryOperator) / reduce(BinaryOperator):可以将流中元素反复结合起来,得到一个值。
 */
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 test1(){
		List<Integer> 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 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方法返回类型作用
toListList把流中元素收集到 List
toSetSet把流中元素收集到 Set
toCollectionCollection把流中元素收集到创建的集合
countingLong计算流中元素的个数
summingIntInteger对流中元素的整数属性求和
averagingIntDouble计算流中元素 Integer 属性的平均值
summarizingIntIntSummaryStatistics收集流中 Integer 属性的统计值。如:平均值
joiningString连接流中每个字符串
maxByOptional根据比较器选择最大值
minByOptional根据比较器选择最小值
reducing归约产生的类型从一个作为累加器的初始值开始,利用 BinaryOperator 与流中元素逐个结合,从而归约成单个值
collectingAndThen转换函数返回的类型包裹另一个收集器,对其结果转换函数
groupingByMap<K, List>根据某属性值对流分组,属性为 K,结果为 T
partitioningByMap<Boolean, List>根据 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 汇总。

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> godness = Optional.empty(); // 必须给个值,否则默认为 null
	
	private Godness god;
	
	public Optional<Godness> getGod(){
		return Optional.of(god);
	}

	public NewMan() {
	}

	public NewMan(Optional<Godness> godness) {
		this.godness = godness;
	}

	public Optional<Godness> getGodness() {
		return godness;
	}

	public void setGodness(Optional<Godness> 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 "嘿嘿嘿";
	}
}

再来一个类同时继承和实现 MyClass 和 MyFun,该使用谁呢?使用类 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();
	}

}

三大核心类

三大类 LocalDate、LocalTime、LocalDateTime 类的实例是 不可变的对象(线程安全),分别表示使用 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 中加入了对时区的支持,带时区的时间为分别为:ZonedDate、ZonedTime、ZonedDateTime。
其中每个时区都对应着 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.DateDate.from(instant)date.toInstant()
java.time.Instant VS java.sql.TimestampTimestamp.from(instant)timestamp.toInstant()
java.time.ZonedDateTime VS java.util.GregorianCalendarGregorianCalendar.from(zonedDateTim e)cal.toZonedDateTime()
java.time.LocalDate VS java.sql.TimeDate.valueOf(localDate)date.toLocalDate()
java.time.LocalTime VS java.sql.TimeDate.valueOf(localDate)date.toLocalTime()
java.time.LocalDateTime VS java.sql.TimestampTimestamp.valueOf(localDateTime)timestamp.toLocalDateTime()
java.time.ZoneId VS java.util.TimeZoneTimezone.getTimeZone(id)timeZone.toZoneId()
java.time.format.DateTimeFormatterVS java.text.DateFormatformatter.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) {
    }
}
PLL锁相环性能上不去?可能是电荷泵的电流失配在捣鬼(附Jae-Shin Lee经典方案解析) 本文深入分析了PLL锁相环性能瓶颈的根源——电荷泵电流失配问题,详细解析了Current Mismatch和Current Deviation的区别及其对系统的影响。重点介绍了Jae-Shin Lee的多级电流镜技术,该方案通过运放构建虚拟短路和四级电流镜结构,显著降低了电流失配,使参考杂散改善15dB以上。文章还提供了从仿真到流片的完整优化流程,帮助工程师系统解决电荷泵性能问题。 阅读详情

相关推荐

winpcap无法安装提示新版本已经安装解决办法

问题显示如下该问题会导致ensp里的云识别不到网卡可能是因为之前安装了npcap,而且安装时勾选了第四个选项导致无法安装,需要完全卸载npcap解决办法:办法一:在控制面板的程序与卸载里面卸载npcap和winPcap,然后再安装winPcap,如果搜不到往下看办法二:在C:\Windows\SysWOW64目录下搜索wpcap.dll和packet.dll并重命名为wpcap.dll.old和packet.dll.old办法三:如果搜不到这两个文件需要安装win10Pcap才能找到这两个文件下载地址:Wi

qq_73202377的博客 3万+

女同事问我JDK8以后有哪些新特性?给我整不会了

整理了JDK9 - JDK16有用的新特性,让你在欢声笑语中掌握它!

陈哈哈的菜园子 1万+

安防相机宽动态(WDR)技术解析:从原理到实战应用

本文深入解析安防相机宽动态(WDR)技术原理,对比多次曝光与DOL-WDR等实现方式,并区分硬件WDR与数字WDR(DWDR)的本质差异。文章重点阐述了WDR在逆光出入口、夜间道路监控等五大典型场景的实战应用与调参技巧,旨在帮助用户根据实际需求正确选择与配置,确保监控系统在复杂光线下清晰成像。

weixin_29191081的博客 427

JDK21 vs JDK8 新特性

摘要: JDK 21作为最新LTS版本,相比JDK 8实现了全面革新。核心差异包括: 语言特性:新增虚拟线程(百万级轻量级并发)、结构化并发、记录模式(深度解构对象)及增强的模式匹配(switch/instanceof); 性能优化:ZGC低延迟垃圾回收、向量API(SIMD加速计算)、紧凑字符串存储; 开发体验:简化单文件程序(无类名main方法)、标准HTTP客户端、字符串模板(类似f-string); 安全性:强制封装内部API、默认TLS 1.3加密; 移除内容:Applet、RMI激活等老旧功能。

csdn_tom_168的博客 3216

从‘全步’到‘微步’:一张图看懂步进电机细分控制,选对模式让你的项目更丝滑

本文深入解析步进电机细分控制技术,从全步到微步的演进过程,帮助工程师根据项目需求选择最优控制模式。通过对比不同细分等级的性能特点和应用场景,提供3D打印、实验室自动化等领域的黄金配置方案,实现更精准、平稳的电机控制效果。

weixin_29290259的博客 470

从封装到先进封装:一文讲透Chiplet、2.5D/3D堆叠与系统集成

封装是芯片产业链中连接硅片与电路板的桥梁,承担物理保护、电气连接、散热与机械支撑等核心任务。传统封装从DIP、QFP发展到BGA,通过提升引脚密度满足早期性能需求。随着摩尔定律放缓,单纯缩小晶体管尺寸难以为继,先进封装应运而生,其核心理念从“封装单颗芯片”转向“系统级高密度集成”。扇出型封装、2.5D硅中介层、3D堆叠、Chiplet和系统级封装等技术,通过微米级互连和垂直堆叠,大幅提升I/O密度与带宽,成为AI芯片、HBM存储、高性能计算等领域的关键支撑。本文梳理先进封装的技术演进、分层架构与工程难点,帮

weixin_29185167的博客 365

JDK8、JDK17、JDK21新特性

Java 8 使得Java 平台又前进了一大步,尤其是 Stream 流操作,使用的时候非常的爽,整个代码看起来也更加的简洁、直观、舒服!Java21虚拟线程,多线程性能更快、更稳定

longzhutengyue的博客 2207

spdk + fio测试nvme 设备的性能

fio的测试对象可以是块设备、文件等,在spdk的使用过程中会unbind默认的nvme driver,所以在系统中是看不到nvme块设备的,在spdk中可以通过fio_plugin的方式,将spdk的用户态driver部分的io功能打包成一个ioengine提供给fio使用,可以综合spdk的高性能和fio提供的复杂场景。更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加。这个jobfile是spdk中提供的例子。领取,关注我持续更新哦!2.7 jobfile的例子。

lingshengxiyou的博客 1088

Jdk8新特性

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

Java100q的博客 1901

别再让 AI 瞎写 SAP 代码了!这套 Claude Code Skill 的验证机制才是关键

本文深入解析了专为SAP CAP开发设计的AI辅助工具sap-cap-capire的技术架构。该工具采用四层设计:协议层集成MCP和LSP实现实时模型访问与语法检查;智能层通过4个专业化Agent(数据建模、服务开发等)分工协作;验证层确保代码质量;知识层存储文档模板。这种工程化设计解决了通用AI在CAP开发中面临的上下文不足、专业性缺失和代码质量保证等核心问题,通过本地化服务和实时反馈机制显著提升了开发效率。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 880

18、深入解析航空飞行数据中的秘密:从数据清理到高级分析

本文详细解析了如何使用R语言处理和分析航空飞行数据,从基础的数据清理到复杂的回归分析和机器学习模型构建。文章涵盖了探索性数据分析、天气数据的整合、地理信息可视化、时间序列预测以及航班延误的优化策略等内容,展示了如何通过数据挖掘技术提高航空运营效率和乘客满意度,并探讨了数据质量评估与隐私保护的重要性。

convnet3designer的博客 106

JDK 8 和 JDK 17 的区别和新特性

本文介绍了Java Development Kit (JDK) 8和JDK 17之间的区别和差异,以及它们各自的优势和特点。JDK 8是一种较老的JDK版本,它在性能和稳定性方面得到了广泛的验证和应用。JDK 17是最新版本的JDK,它包括许多新的特性和改进,如基于类的记录(Records)、弱引用垃圾回收器(ZGC)等,这些新特性和改进为Java开发者提供了更多的选择和机会,同时也需要不断学习和更新自己的知识和技能,以适应Java技术的发展和变化。文章提供了部分简单代码使用示例

qq_37686995的博客 1万+

Plant Simulation与ABB机器人虚实结合

Plant Simulation与ABB机器人虚实结合

santayana的专栏 1869

JDK17新特性深度解析:从 JDK8 到现代 Java 的进化之路

JDK8与JDK17作为Java的长期支持版本,分别代表了不同时代的核心特性。JDK8凭借Lambda、Stream API等革新成为经典,而JDK17通过Record类型、Sealed类、模式匹配、ZGC等新特性,在性能、安全性和开发效率上实现全面提升。

m0_46421522的博客 2452

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

JDK17 vs JDK8 新特性

JDK17相比JDK8带来了重大革新:语言特性方面新增模式匹配、密封类、记录类、文本块和Switch表达式,大幅简化代码;API升级包括HTTP Client标准化、集合框架增强和NIO优化;性能上引入ZGC和Shenandoah垃圾回收器,提升JIT编译器;安全方面禁用SHA-1并支持新加密算法。建议新项目直接采用JDK17,存量项目可逐步迁移以利用新特性和性能优势,同时注意移除功能的替代方案。JDK17在代码简洁性、现代化API、性能优化和安全性方面全面超越JDK8。

csdn_tom_168的博客 1838

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

jdk8新特性(jdk8新特性面试)

jdk8.101和jdk8.102的区别 在JDK1.7的新特性方面主要有下面几方面的增强: 1.jdk7语法上 1.1二进制变量的表示,支持将整数类型用二进制来表示,用0b开头。 1.2 Switch语句支持string类型 1.3 Try-with-resource语句 注意:实现java.la...

计算机毕业论文源码,学生个人网页制作html源码。贴近用户做网络推广和互联网优化。 1293

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

JDK8升级到JDK17的新特性

随着 AI 时代的浪潮席卷而来,人工智能技术在各行各业的应用愈发广泛,开发领域也迎来了新的变革。为了更好地适配 AI 场景的开发需求,SpringAI 已正式发布,它为开发者提供了构建 AI 应用的强大工具和框架。而要使用 SpringAI,与之配套的 Springboot 需要升级到 3.X 版本,这就对 JDK 的版本提出了新的要求 ——Springboot 3.X 明确要求 JDK 必须在 17 及以上版本。​。

LLLLLiSHI的博客 1184

探索JDK8的新特性

Java 8这个版本提供了很多实用的新特性,针对接口推出了接口默认方法,接口静态方法以及函数式接口,同时为了简化代码编写,推出了lambda表达式,为了增强对数据的操作,还定义了Stream操作等。这个版本目前是市场上一个应用最广泛,也是最重要的一个版本。JDK8中对接口规范进行了新的定义,允许在接口中定义默认方法(使用default关键字修饰),静态方法,同时还推出了函数式接口(使用@FunctionInterface注解描述)设计。

南 阳 的博客 1861

JDK8到JDK17都升级了那些新特性?又有哪些能常用好用的?

Java依然是服务器端开发、Android移动应用和大数据处理等领域的首选语言之一。凭借其稳定性、广泛的生态系统及不断的技术改进,Java维持着强大的生命力和竞争力。

Tamate的博客 1129

【JavaSE之JDK8新特性】三万字详文带你了解JDK8新特性

1.了解了什么是lambda 表达式,lambda 表达式和匿名内部类的区别; 2.了解了默认方法是个啥,JDK8使得接口里也可以有静态方法了; 3.介绍了四个函数式接口,Supplier,Consumer,Function,Predicate; 4.方法引用的概念以及其中的语法格式; 5.引入了Stream流(遍历数据集的高级迭代器); 6.介绍了Stream的常用方法,学习了如何更好的处理数据

qq_63691275的博客 2752

jdk8新特性_JDK8的十大新特性介绍

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。JDK8新特性: 1.Lambda表达式 2.新增Stream类 3.引入Optional 4.使用Base64...

weixin_39714191的博客 858

JDK 8 新特性详解

JDK 8 是 Java 语言的一次重大更新,引入了许多新特性,使得 Java 更加现代化和简洁。从 Lambda 表达式、Stream API 到新的日期时间 API,JDK 8 不仅提升了开发效率,还改善了代码的可读性和可维护性。随着这些新特性的不断普及,Java 变得更加适应现代编程需求,尤其在并发、函数式编程和数据处理方面提供了强大的支持。

L13290287351的博客 2670

jdk8的十大新特性

了解jdk8的新特性首先要了解jdk7的新特新都有哪些方面增强: 1.jdk语法上 1.1二进制变量的表示,支持将整数类型用二进制来表示,用Ob开头。 1.2Switch语句支持String类型 1.3Try-with-resource语句 注意:实现Java.lang.AutoCloseable接口的资源都...

super_weilin的博客 9715

jdk7和8的一些新特性介绍

更多ppt内容请查看:http://www.javaarch.net/jiagoushi/927.htm 本文是我学习了解了jdk7和jdk8的一些新特性的一些资料,有兴趣的大家可以浏览下下面的内容。 官方文档:http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html 在jdk7的新特性方

程序员之路 2万+

Jdk1.8新特性

Jdk1.8新特性

老顽固也可爱 2219
上一篇: Spring 数据绑定
下一篇: JDK 9 新特性
优秀小白熊
博客等级 码龄3年 1155粉丝 195原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值