当前位置: 首页 > 编程日记 > 正文

10万字总结Java8到21新特性详解

文章目录

Java 8 新特性—概述

Java 8 是Java历史上一个重大的版本更新,发布于2014年3月18日。

img

JEP 126:Lambda 表达式

Lambda 表达式是 Java 8 新特性中最重要且最显著的一个,为 Java 增加了函数式编程的能力,使得代码变得更加简洁和易读。Lambda 表达式主要用于简化匿名内部类的实现。

Lambda 表达式的基本语法:

(parameters) -> expression 或 (parameters) -> {
    statements; }
  • parameters :是 Lambda表达式的参数列表,可以为空或包含一个或多个参数。
  • -> :是 Lambda 操作符,用于将参数和 Lambda 主体分开。
  • expression :是 Lambda 表达式的返回值,或者在主体中执行的单一表达式。
  • { statements; } :是 Lambda 主体,包含了一系列语句,如果需要执行多个操作,就需要使用这种形式。

它具有如下几个特点:

  1. 无需声明类型:Lambda 表达式不需要声明参数类型,编译器可以自动推断参数类型。
  2. 可选的参数圆括号:当只有一个参数时,可以省略圆括号。但是当参数个数大于一个时,圆括号是必需的。空括号用于表示空参数集。
  3. 可选的大括号:当 Lambda 表达式的主体只包含一个表达式时,可以省略大括号。当表达式需要包含多个语句时,需要使用大括号。
  4. 可选的返回关键字:当 Lambda 表达式主体只有一个表达式,且该表达式会自动返回结果时,可以省略 return 关键字。

JEP 126:函数式接口

Java 8 引入函数式接口的主要目的是支持函数式编程范式,也就是 Lambda 表达式。在函数式编程语言中,函数被当做一等公民对待,Lambda 表达式的类型是函数,它可以像其他数据类型一样进行传递、赋值和操作。但是在 Java 中,“一切皆对象”是不可违背的宗旨,所以 Lambda 表达式是对象,而不是函数,他们必须要依附于一类特别的对象类型:函数式接口。所以函数式接口是与Lambda表达式紧密相连的,它为Java添加了一种新的抽象层次,允许将方法作为一等公民对待

函数式接口具有两个特点:

  1. 只包含一个抽象方法:函数式接口只能有一个抽象方法,但可以包含多个默认方法或静态方法。
  2. @FunctionalInterface注解标记:该注解不强制,但通常会使用它来标记该接口为函数式接口。这样做可以让编译器检查接口是否符合函数式接口的定义,以避免不必要的错误。

一般来说函数式接口有两个最主要的用途:

  1. 与 Lambda表达式一起使用,为Java带来更加函数式的编程风格。
  2. 用于实现简单的函数策略或行为,如回调、事件处理等。

JEP 179:方法引用

为了提升 Java 编程语言的表达力和可读性,特别是在配合 Lambda 表达式和函数式编程风格,Java 8 引入方法引用。

方法引用实际上是一个简化版的 Lambda 表达式,它允许我们以更简洁的方式引用方法。它有如下几种类型:

  1. 静态方法引用:使用类名::静态方法名的形式。

    • 例如,String::valueOf 相当于 x -> String.valueOf(x)
  2. 实例方法引用(对象的实例方法):使用 实例对象::实例方法名的形式。

    • 例如,假设有一个 String 对象 myString,那么 myString::length 相当于 () -> myString.length()
  3. 特定类型的任意对象的实例方法引用:使用 类名::实例方法名

    • 例如,String::length 相当于 str -> str.length()。这里不是调用特定对象的 length 方法,而是用于任意的 String 对象。
  4. 构造器引用:使用类名::new

  • 例如,ArrayList::new 相当于 () -> new ArrayList<>()

JEP 150:接口的默认方法

在 Java 8 之前,接口中可以申明方法和变量的,只不过变量必须是 public、static、final 的,方法必须是 public、abstract的。我们知道接口的设计是一项巨大的工作,因为如果我们需要在接口中新增一个方法,需要对它的所有实现类都进行修改,如果它的实现类比较少还可以接受,如果实现类比较多则工作量就比较大了。

为了解决这个问题,Java 8 引入了默认方法,默认方法允许在接口中添加具有默认实现的方法,它使得接口可以包含方法的实现,而不仅仅是抽象方法的定义。

默认方法是接口中带有 default 关键字的非抽象方法。这种方法可以有自己的实现,而不需要子类去覆盖它。

默认方法允许我们向接口添加新方法而不破坏现有的实现。它解决了在 Java 8 之前,向接口添加新方法意味着所有实现该接口的类都必须修改的问题。

JEP 107:Stream API

为了解决 Java 8 之前版本中集合操作的一些限制和不足,提高数据处理的效率和代码的简洁性,Java 8 引入 Stream API,它的引入标志着 Java 对集合操作迎来了的一种全新的处理方式,它在处理集合类时提供了一种更高效、声明式的方法。

Stream API 的核心思想是将数据处理操作以函数式的方式链式连接,以便于执行各种操作,如过滤、映射、排序、归约等,而无需显式编写传统的循环代码。

下面是 Stream API 的一些重要概念和操作:

  1. Stream(流):Stream 是 Java 8 中处理集合的关键抽象概念,它是数据渠道,用于操作数据源所生成的元素序列。这些数据源可以来自集合(Collection)、数组、I/O操作等等。它具有如下几个特点:

    1. Stream 不会存储数据。
    2. Stream 不会改变源数据对象,它返回一个持有结果的新的 Stream
    3. Stream 操作是延迟执行的,这就意味着他们要等到需要结果的时候才会去执行。
  2. 中间操作:这些操作允许您在 Stream 上执行一系列的数据处理。常见的中间操作有 filter(过滤)、map(映射)、distinct(去重)、sorted(排序)、limit(截断)、skip(跳过)等。这些操作返回的仍然是一个 Stream。

  3. 终端操作:终端操作是对流进行最终处理的操作。当调用终端操作时,流将被消费,不能再进行进一步的中间操作。常见的终端操作包括 forEach(遍历元素)、collect(将元素收集到集合中)、reduce(归约操作,如求和、求最大值)、count(计数)等。

  4. 惰性求值:Stream 操作是惰性的,只有在调用终端操作时才会执行中间操作。这可以提高性能,因为只处理需要的数据。

Optional 类

Java 8 引入了 Optional 类,这是一个为了解决空指针异常(NullPointerException)而设计的容器类。它可以帮助开发者在编程时更优雅地处理可能为 null 的情况。

JEP 170:新的日期时间 API

作为 Java 开发者你一定直接或者间接使用过 java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 这三个类吧,这三个类是 Java 用于处理日期、日历、日期时间格式化的。由于他们存在一些问题,诸如:

  1. 线程不安全:

    1. java.util.Datejava.util.Calendar 线程不安全,这就导致我们在多线程环境使用需要额外注意。
    2. java.text.SimpleDateFormat 也是线程不安全的,这可能导致性能问题和日期格式化错误。而且它的模式字符串容易出错,且不够直观。
  2. 可变性java.util.Date类是可变的,这意味着我们可以随时修改它,如果一不小心就会导致数据不一致问题。

  3. 时区处理困难:Java 8 版本以前的日期 API 在时区处理上存在问题,例如时区转换和夏令时处理不够灵活和准确。而且时区信息在 Date 对象中存储不明确,这使得正确处理时区变得复杂。

  4. 设计不佳:

    1. 日期和日期格式化分布在多个包中。
    2. java.util.Date 的默认日期,年竟然是从 1900 开始,月从 1 开始,日从 1 开始,没有统一性。而且 java.util.Date 类也缺少直接操作日期的相关方法。
    3. 日期和时间处理通常需要大量的样板代码,使得代码变得冗长和难以维护。

基于上述原因,Java 8 重新设计了日期时间 API,以提供更好的性能、可读性和可用性,同时解决了这些问题,使得在 Java 中处理日期和时间变得更加方便和可靠。相比 Java 8 之前的版本,Java 8 版本的日期时间 API 具有如下几个优点:

  1. 不可变性(Immutability):Java 8的日期时间类(如LocalDateLocalTimeLocalDateTime)都是不可变的,一旦创建就不能被修改。这确保了线程安全,避免了并发问题。
  2. 清晰的API设计:Java 8 的日期时间 API 采用了更清晰、更一致的设计,相比于以前版本的 DateCalendar 更易于理解和使用。而且它们还提供了丰富的方法来执行日期和时间的各种操作,如加减、比较、格式化等。
  3. 本地化支持:Java 8 的日期时间 API 支持本地化,可以轻松处理不同地区和语言的日期和时间格式。它们能够自动适应不同的时区和夏令时规则。
  4. 新的时区处理:Java 8引入了 ZoneIdZoneOffset 等新的时区类,使时区处理更加精确和灵活。这有助于解决以前版本中时区处理的问题。
  5. 新的格式化API:Java 8引入了 DateTimeFormatter 类,用于格式化和解析日期和时间,支持自定义格式和本地化。这提供了更强大和灵活的格式化选项。
  6. 更好的性能:Java 8 的日期时间API 比以前的API 性能更佳。

JEP 120:重复注解

在 Java 8 之前的版本中,对于一个特定的类型,一个注解在同一个声明上只能使用一次。Java 8 引入了重复注解,它允许对同一个类型的注解在同一声明或类型上多次使用。

工作原理如下:

  1. 定义重复注解:您需要定义一个注解,并用 @Repeatable 元注解标注它。@Repeatable 接收一个参数,该参数是一个容器注解,用于存储重复注解的实例。
  2. 定义容器注解:容器注解定义了一个注解数组,用于存放重复注解的多个实例。这个容器注解也需要具有运行时的保留策略(@Retention(RetentionPolicy.RUNTIME))。

Base64 编码解码

在 Java 8 之前,我们通常需要依赖于第三方库(如 Apache Commons Codec)或者使用 Java 内部类(如 sun.misc.BASE64Encodersun.misc.BASE64Decoder)来处理 Base64 编解码。但是这些内部类并非 Java 官方的一部分,它们的使用并不推荐,因为它们可能会在未来的版本中发生变化,造成兼容性问题。同时使用非官方或内部 API 可能导致安全漏洞或运行时错误,所以 Java 8 引入一个新的 Base64 编解码 API,它处理 Base64 编码和解码的官方、标准化的方法。

Java 8 中的 Base64 API 包含在 java.util 包中。它提供了以下三种类型的 Base64 编解码器:

  1. 基本型(Basic):用于处理常规的 Base64 编码和解码。它不对输出进行换行处理,适合于在URLs和文件名中使用。
  2. URL和文件名安全型(URL and Filename Safe):输出映射到一组 URL 和文件名安全的字符集。它使用 ‘-’ 和 ‘_’ 替换标准 Base64 中的 ‘+’ 和 ‘/’ 字符。
  3. MIME型:用于处理 MIME 类型的数据(例如,邮件)。它在每行生成 76 个字符后插入一个换行符。

JEP 104:类型注解

在 Java 8 之前,注解仅限于声明(如类、方法或字段)。这种限制意味着注解的用途在许多编程情景中受到限制,特别是在需要对类型本身(而不仅仅是声明)进行描述时。为了提高注解的能力,Java 8 引入类型注解来增强注解的功能。

该特性扩展了注解的应用范围,允许我们将注解应用于任何使用类型的地方,而不仅仅是声明。包括以下情况:

  • 对象创建(如 new 表达式)
  • 类型转换和强制类型转换
  • 实现(implements)语句
  • 泛型类型参数(如 List<@NonNull String>

JEP 101:类型推断优化

在 Java 8 之前,Java 的类型推断主要局限于泛型方法调用的返回类型。这意味着在许多情况下,我们不得不显式指定泛型参数,即使它们可以从上下文中推断出来。这种限制使得代码变得冗长且不够直观,特别是在使用泛型集合和泛型方法时。

为了提高编码效率和可读性,同时简化泛型使用,Java 8 中引入了对类型推断机制的优化,扩大了类型推断的范围,使其能在更多情况下自动推断出类型信息,包括:

  1. Lambda 表达式中的类型推断:在使用 Lambda 表达式时,编译器可以根据上下文推断出参数类型,从而减少了在某些情况下编写显式类型的需求。
  2. 泛型方法调用的改进:在调用泛型方法时,编译器可以更好地推断方法参数、返回类型以及链式调用中间步骤的类型。
  3. 泛型构造器的类型推断:在创建泛型对象时,编译器能够推断出构造器参数的类型。

JEP 174:Nashorn JavaScript 引擎

在 Java 8 之前,Java 平台的主要 JavaScript 引擎是 Mozilla 的 Rhino。Rhino 是一个成熟的引擎,但由于其架构和设计年代较早,它在性能和与 Java 的集成方面存在一些限制。随着 JavaScript 在 Web 和服务器端应用中日益重要,需要一个更现代、更高效的 JavaScript 引擎来提供更好的性能和更深度的 Java 集成。因此,Nashorn 引擎被引入作为 Java 平台的一部分。

Nashorn 是一个基于 Java 的 JavaScript 引擎,它完全用 Java 语言编写,并且是 Rhino 的替代品。主要特点:

  1. 基于 JVM 的执行:Nashorn 是作为 Java 虚拟机的一个原生组件实现的,它直接编译 JavaScript 代码到 Java 字节码。这意味着它可以充分利用 JVM 的性能优化和管理能力。
  2. 高性能:与 Rhino 相比,Nashorn 提供了显著的性能提升,特别是在执行 JavaScript 代码方面。
  3. 与 Java 的深度集成:Nashorn 允许 JavaScript 代码和 Java 代码之间有更紧密的交互。开发者可以在 JavaScript 中方便地调用 Java 类库和对象,反之亦然。
  4. ECMAScript 5.1 支持:Nashorn 支持 ECMAScript 5.1 规范,为开发者提供了一个符合标准的现代 JavaScript 编程环境。

JEP 122:移除Permgen

在 Java 8 之前,JJVM使用永久代(PermGen)的内存区域来存储类的元数据和方法数据。随着时间的推移,这个设计开始显现出一些问题,特别是在应用程序频繁加载和卸载类的场景中,比如在 Java EE 应用服务器和热部署环境中。

永久代有一个固定的大小限制,当类的数量和大小超过这个限制时,就会抛出 OutOfMemoryError: PermGen space 错误。这种设计限制了 Java 的灵活性和可伸缩性。

Java 8 移除永久代并用元空间(Metaspace)的新内存区域来取代它。相比永久代,元空间的具有如下优势:

  1. 基于本地内存:元空间不在 JVM 的堆内存中,而是直接使用本地内存(操作系统的内存)。这意味着它不再受到 Java 堆大小的限制。
  2. 动态调整大小:元空间的大小可以根据应用程序的需求动态调整。这减少了内存溢出的风险,并允许应用更高效地管理内存。
  3. 更好的性能:由于移除了固定大小的限制,元空间可以提供更好的性能,尤其是在大型应用和复杂的部署环境中。

Java 8 新特性—Lambda 表达式

img

什么是 Lambda 表达式

Lambda 表达式是在 Java 8 中引入,并且被吹捧为 Java 8 最大的特性。它是函数式编程的的一个重要特性,标志着 Java 向函数式编程迈出了重要的第一步。

它的语法如下:

(parameters) -> expression

或者

(parameters) -> {
    statements; }

其中

  • parameters :是 Lambda表达式的参数列表,可以为空或包含一个或多个参数。
  • -> :是 Lambda 操作符,用于将参数和 Lambda 主体分开。
  • expression :是 Lambda 表达式的返回值,或者在主体中执行的单一表达式。
  • { statements; } :是 Lambda 主体,包含了一系列语句,如果需要执行多个操作,就需要使用这种形式。

Java 8 引入 Lambda 表达式的主要作用是简化部分匿名内部类的写法。使用它可以完成用少量的代码实现复杂的功能,极大的简化代码代码量和代码结构。同时,JDK 中也增加了大量的内置函数式接口供我们使用,使得在使用 Lambda 表达式时更加简单、高效。

下面我们就来看它的一些常见用法。

常见用法

无参数,无返回值

例如 Runnable 接口的 run()

在 Java 8 版本之前的版本,我们一般都是这样用:

        new Thread(new Runnable() {
   
            @Override
            public void run() {
   
                System.out.println("死磕 Java 就是牛逼...");
            }
        }).start();

从 Java 8 开始,无参数匿名内部类可以简写成如下这种方式:

() -> {
   
    执行语句
}

所以上面代码可以简写成这样的:

new Thread(() -> System.out.println("死磕 Java 就是牛逼...")).start();

单参数,无返回值

只有一个参数,无返回值,如下:

(x) -> System.out.println(x);

在 Java 8 中,有一个函数式接口 Consumer,它定义如下:

@FunctionalInterface
public interface Consumer<T> {
   

    void accept(T t);
}

我们用它来演示下:

        Consumer<String> consumer = (String s) -> {
   
            System.out.println(s);
        };
        
        consumer.accept("死磕 Java 就是牛...");

是不是比较简便,但是这段代码还不够简便,它还可以进行多次优化,

  • 如果 Lambda 主体只有一条语句,则 {、} 可以省略
Consumer<String> consumer = (String s) -> System.out.println(s);
  • Lambda 表达式有一个依据:类型推断机制。在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指名。所以 (String s) 可以简写为 (s)
Consumer<String> consumer = (s) -> System.out.println(s);
  • 对于只有一个参数的情况,左侧括号可以省略:
Consumer<String> consumer = s -> System.out.println(s);

多参数,有返回值

如 Comparator 接口的 compare(T o1, T o2) 方法,在 Java 8 之前,写法如下:

        Comparator<Integer> comparator = new Comparator<Integer>() {
   
            @Override
            public int compare(Integer o1, Integer o2) {
   
                System.out.println("o1:" + o1);
                System.out.println("o2:" + o2);
                return o1.compareTo(o2);
            }
        };
        
        comparator.compare(12,13);

使用 Lambda 表达式后:

        Comparator<Integer> comparator = (o1, o2) -> {
   
            System.out.println("o1:" + o1);
            System.out.println("o2:" + o2);
            return o1.compareTo(o2);
        };

        comparator.compare(12,13);

当然,如果去掉 System.out.println(),还可以简写为 Comparator<Integer> comparator = (o1, o2) -> o1.compareTo(o2); ,这里是可以省略 return 关键字的。

这里就 Lambda 的简写做一个总结:

  1. 类型推断:编译器可以根据上下文推断 Lambda 表达式的参数类型,从而可以省略参数类型的声明。
  2. 单一参数:当 Lambda 表达式只有一个参数时,可以省略参数外的括号。如: (x) → x * 2 可以简写为 x → x * 2
  3. 单表达式:当 Lambda 表达式只有一行代码时,可以省略大括号和 return 关键字。如 (x,y) → {return x + y} 可以简写为 (x,y) → x + y

Lambda 简写依据

Lambda 简写的依据有两个:

1、必须有相应的函数式接口

所谓函数式接口函数式就是指只包含一个抽象方法的接口,它是在 Java 8 版本中引入的,其主要目的是支持函数式编程,有了函数式接口我们可以将函数作为参数传递、将函数作为返回值返回,同时也为使用 Lambda 表达式提供了支持。

函数式接口具有以下特征:

  1. 只包含一个抽象方法:函数式接口只能有一个抽象方法,但可以包含多个默认方法或静态方法(Java 8 中有另一个新特性:default 关键字)。这个唯一的抽象方法通常用来表示某种功能或操作。
  2. **@FunctionalInterface**注解标记:注解不强制,但通常会使用它来标记该接口为函数式接口。这样做可以让编译器检查接口是否符合函数式接口的定义,以避免不必要的错误。

2、类型推断机制

类型推断机制则是允许编译器根据上下文自动推断 Lambda 表达式的参数类型。这个推断过程包括两个方面:

  1. 目标类型推断

编译器会根据 Lambda 表达式在赋值、传参等地方的上下文来推断Lambda表达式的目标类型。例如,如果Lambda表达式被赋值给一个接口类型的变量,编译器会根据该接口的抽象方法来推断Lambda表达式的参数类型。

Runnable runnable = () -> System.out.println("死磕 Java 就是牛...");

Lambda表达式被赋值给了 Runnable 类型的变量,所以编译器知道 Lambda 表达式需要没有参数且返回类型为void的方法。

  1. 参数类型推断

如果 Lambda 表达式的参数类型可以从上下文中唯一确定,编译器会自动推断参数的类型。例如:

List<String> skList = Arrays.asList("死磕 Java 并发", "死磕 Netty", "死磕 NIO","死磕 Spring");
skList.forEach(sk -> System.out.println(sk));

forEach方法期望一个参数类型为Consumer<String>的函数,编译器可以从 sk的类型推断出Lambda表达式的参数类型为String

虽然类型推断机制允许省略Lambda表达式的参数类型,但有时候显式声明参数类型可以增强代码的可读性和处理复杂的泛型情况,这个时候我们还是将参数类型写上会显得更加友好。


Java 8 新特性—函数式接口

在上边 Lambda 表达式 提过,Lambda 能够简化的一个依据就是函数式接口,这篇文章我们就来深入了解函数式接口。

img

什么是函数式接口

函数式接口是一个只有一个抽象方法的接口,最开始的时候也叫做 SAM 类型的接口(Single Abstract Method)。它具有两个特点:

  1. 只包含一个抽象方法:函数式接口只能有一个抽象方法,但可以包含多个默认方法或静态方法。
  2. @FunctionalInterface注解标记:该注解不强制,但通常会使用它来标记该接口为函数式接口。这样做可以让编译器检查接口是否符合函数式接口的定义,以避免不必要的错误。

Java 引入函数式接口的主要目的是支持函数式编程范式,也就是 Lambda 表达式。在函数式编程语言中,函数被当做一等公民对待,Lambda 表达式的类型是函数,它可以像其他数据类型一样进行传递、赋值和操作。但是在 Java 中,“一切皆对象”是不可违背的宗旨,所以 Lambda 表达式是对象,而不是函数,他们必须要依附于一类特别的对象类型:函数式接口。

所以,从本质上来说 Lambda 表达式就是一个函数式接口的实例。这就是 Lambda 表达式和函数式接口的关系。简单理解就是只要一个对象时函数式接口的实例,那么该对象就可以用 Lambda 表达式来表示。

自定义函数式接口

根据函数式接口的定义和特点,我们可以自定义函数式接口:

@FunctionalInterface
public interface FunctionInterface {
   

    /**
     * 抽象方法
     */
    void doSomething();

    /**
     * 默认方法
     * @param s
     */
    default void defaultMethod(String s) {
   
        System.out.println("默认方法:" + s);
    }

    /**
     * 静态方法
     * @param s
     */
    static void staticMethod(String s) {
   
        System.out.println("静态方法:" + s);
    }
}

FunctionInterface 是一个自定义函数式接口,它只包含一个抽象方法 doSomething(),还包含一个默认方法 defaultMethod(String s) 和一个静态方法 staticMethod(String s),这两个方法都是可选的。

@FunctionalInterface 注解是可写可可不写的,但是我们一般都推荐写,写上他可以让编译器检查接口是否符合函数式接口的定义,以避免不必要的错误,比如:

img

上面接口定义了两个抽象方法,它会明确告诉你错误了。

使用如下:

        FunctionInterface functionInterface = () -> {
            System.out.println("死磕 Java 就是牛...");
        };

        // 调用抽象方法
        functionInterface.doSomething();
        // 调用默认方法
        functionInterface.defaultMethod("死磕 Netty 就是牛...");
        // 调用静态方法
        FunctionInterface.staticMethod("死磕 Java 并发就是牛...");

执行如下:

img

常用函数式接口

其实在 Java 8 之前就已经有了大量的函数式接口,我们最熟悉的就是 java.lang.Runnable接口了。Java 8 之前已有的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

而在 Java 8 中,新增的函数式接口都在 java.util.function 包中,里面有很多函数式接口,用来支持 Java 的函数式编程,从而丰富了 Lambda 表达式的使用场景。我们使用最多的也是最核心的函数式接口有四个:

  • java.util.function.Consumer:消费型接口
  • java.util.function.Function:函数型接口
  • java.util.function.Supplier:供给型接口
  • java.util.function.Predicate:断定型接口

下面我们就来看这四个函数式接口的使用方法

Consumer 接口

Consumer 代表这一个接受一个输入参数并且不返回任何结果的操作。它包含一个抽象方法 accept(T t),该方法接受一个参数 t,并对该参数执行某种操作:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

由于 Consumer 接口中包含的抽象方法不返回结果,所以它通常用于对对象进行一些操作,如修改、输出、打印等。它的使用方法也比较简单,分为两步。

  1. 创建一个 Consumer 对象:使用 Lambda 表达式来创建一个对象,定义在 accept(T t) 中要执行的操作。
Consumer<String> consumer = str -> System.out.println(str);
  1. 使用 Consumer 对象
consumer.accept("死磕 Java 就是牛...");

// 输出结果...

死磕 Java 就是牛...

在 Consumer 接口中还有一个默认方法 andThen(),该方法接受一个 Consumer 实例对象 after,它允许我们将两个 Consumer 对象组合在一起,形成一个新的 Consumer 对象,该新对象按照顺序执行这两个 Consumer 对象的操作。先执行调用andThen()接口的accept(),然后再执行andThen()参数after中的accept()

Consumer<String> consumer1 = str -> System.out.println("consumer1:" + str);
Consumer<String> consumer2 = str -> System.out.println("consumer2:" + str);

consumer1.andThen(consumer2).accept("死磕 Java 就是牛..");

// 输出结果...

consumer1:死磕 Java 就是牛..
consumer2:死磕 Java 就是牛..

Function 接口

Function 代表一个接受一个输入参数并且产生一个输出结果的函数。它包含一个抽象方法 R apply(T t),该方法接受一个参数 t(类型为 T),并返回一个结果(类型为 R),我们可以理解为根据一个数据类型 T ,经过一系列的操作后得到类型 R。Function 接口是非常通用的,应该是他们四个当中使用最为广泛的。

用途一:函数转换

Function 可以用于将一个类型的值转换为另一个类型的值。它可以用于各种转换操作,如类型转换、数据映射等。

Function<String,Integer> function = str -> Integer.parseInt(str);
int result = function.apply("456");
// 输出结果...
456

用途二:数据处理

Function 可用于对输入数据进行处理并生成输出结果。它可以用于执行各种操作,如过滤、计算、提取、格式化等。

Function<List<String>, String> function = list -> {
    StringBuilder result = new StringBuilder();
        for (String str : list) {
            if (str.startsWith("李")) {
                result.append(str).append(",");
            }
        }

        return result.toString();
    };
List<String> list = Arrays.asList("张三","李四","李武","李柳");
System.out.println(function.apply(list));
// 输出结果...
李四,李武,李柳,   

andThen():方法链式调用

andThen() 接受一个 Function 作为参数,并返回一个新的 Function,该新函数首先应用当前函数,然后将结果传递给参数函数。这种方法链的方式可以用于将多个函数组合在一起,以执行一系列操作。

Function<String,Integer> function1 = t -> Integer.parseInt(t);
Function<Integer,Integer> function2 = t -> t * 10;
System.out.println(function1.andThen(function2).apply("20"));

先将 String 转换为 Integer,然后再 * 10,利用 andThen() 我们可以进行一系列复杂的操作。

compose():顺序执行

compose()andThen()相反,它首先应用参数函数,然后再应用当前函数,这种可能更加好理解些,常用于一些顺序执行。

Function<String,Integer> function1 = t -> {
  System.out.println("function1");
  return Integer.parseInt(t);
};
Function<Integer,Integer> function2 = t -> {
  System.out.println("function2");
  return t * 10;
};
Function<Integer,String> function3 = t -> {
  System.out.println("function3");
  return t.toString();
};
System.out.println(function3.compose(function2.compose(function1)).apply("20"));
        
// 输出结果...
function1
function2
function3
200

从输出结果中可以更加直观地看清楚他们的执行顺序。

identity()恒等函数

identity() 返回一个恒等函数,它仅返回其输入值,对输入值不进行任何操作。源码如下:

    static <T> Function<T, T> identity() {
        return t -> t;
    }

一看感觉 identity() 没啥用处,其实它在某些场景大有用处,例如

  • 作为默认函数

identity() 可以作为函数组合链中的起点或默认函数。当我们想构建一个函数组合链时,可以使用 identity 作为初始函数,然后使用 andThen()compose() 方法添加其他函数。这种方式允许您以一种优雅的方式处理链的起点。

Function<String,String> function1 = Function.identity();
Function<String,String> function2 = str -> str.toUpperCase();
Function<String,String> function3 = str -> str + " WORLD!!!";

System.out.println(function3.compose(function2.compose(function1)).apply("hello"));
复制代码
  • 保持一致性

在某些情况下,我们可能需要一个函数,但不需要对输入进行任何操作。使用 identity() 可以确保函数的签名(输入和输出类型)与其他函数一致。

Supplier 接口

Supplier 是一个代表生产(或供应)某种结果的接口,它不接受任何参数,但能够提供一个结果。它定义了一个 get() 的抽象方法,用于获取结果。

接口定义简单,使用也简单:

Supplier<LocalDate> supplier = () -> LocalDate.now();
LocalDate localDate = supplier.get();

Supplier 接口通常用于惰性求值,只有在需要结果的时候才会执行 get() 。这对于延迟计算和性能优化非常有用。

Predicate 接口

Predicate 表示一个谓词,它接受一个输入参数并返回一个布尔值,用于表示某个条件是否满足。抽象方法为 test(),使用如下:

Predicate<String> predicate = str -> str.length() > 10;
boolean result = predicate.test("www.skjava.com");

判断某个字符长度是否大于 10。

and():表示两个 Predicate 的 与操作

Predicate<Integer> predicate1 = x -> x > 10;
Predicate<Integer> predicate2 = x -> x % 2 == 0;
boolean result = predicate1.and(predicate2).test(13);

or():表示两个 Predicate 的或操作

Predicate<Integer> predicate1 = x -> x > 10;
Predicate<Integer> predicate2 = x -> x % 2 == 0;
boolean result = predicate1.or(predicate2).test(13);

negate():表示 Predicate 的逻辑非操作

Predicate<Integer> predicate1 = x -> x > 10;
boolean result = predicate1.negate().test(14);

其他函数式接口

除了上面四个常用的函数式接口外,java.util.function 包下面还定义了很多函数式接口,下面做一个简单的介绍:

接口说明
BiConsumer<T,U>表示接受两个不同类型的参数,但不返回任何结果的操作
BiFunction<T,U,R>表示接受两个不同类型的参数,并返回一个其它类型的结果的操作
BinaryOperator表示接受两个相同类型的参数,并返回一个同一类型的结果的操作
BiPredicate<T,U>表示接受两个不同诶行的参数,且返回布尔类型的结果的操作
BooleanSupplier不接受任何参数,且返回一个布尔类型的结果的操作
DoubleBinaryOperator表示接受两个double类型的参数,并返回double类型结果的操作
DoubleConsumer表示接受一个double类型的参数,但不返回任何结果的操作
DoubleFunction表示接受一个double类型的参数,且返回一个R类型的结果的操作
DoublePredicate表示一个接受两个double类型的参数,且返回一个布尔类型的结果的操作
DoubleSupplier表示一个不接受任何参数,但返回布尔类型的结果的操作
DoubleToIntFunction表示接受两个double类型的参数,但返回一个int类型的结果的操作
DoubleToLongFunction表示接受两个double类型的参数,但返回一个long类型的结果的操作
DoubleUnaryOperator表示接受一个double类型的参数,且返回一个double类型的结果的操作
IntBinaryOperator表示一个接受两个int类型的参数,且返回一个int类型的结果的操作
IntConsumer表示接受一个int类型的参数,但不返回任何结果的操作
IntFunction表示接受一个int类型的参数,但返回一个R类型的结果的操作
IntPredicate表示接受一个int类型的参数,但返回布尔类型的结果的操作
IntSupplier表示不接受任何参数,但返回一个int类型的结果的操作
IntToDoubleFunction表示接受一个int类型的参数,但返回一个double类型的结果的操作
IntToLongFunction表示接受一个int类型的参数,但返回一个long类型的结果的操作
IntUnaryOperator表示接受一个int类型的参数,且返回一个int类型的结果的操作
LongBinaryOperator表示接受两个long类型的参数,且返回一个long类型的结果的操作
LongConsumer表示不接受任何参数,但返回一个long类型的结果的操作
LongFunction表示接受一个loing类型的参数,但返回一个R类型的结果的操作
LongPredicate表示接受一个long类型的参数,但返回布尔类型的结果的操作
LongSupplier表示不接受任何参数,但返回一个long类型的结果的操作
LongToDoubleFunction表示接受一个long类型的参数,但返回一个double类型的结果的函数
LongToIntFunction表示接受一个long类型的参数,但返回int类型的结果的函数
LongUnaryOperator表示接受一个long类型的参数,并返回一个long类型的结果的操作
ObjDoubleConsumer表示接受两个参数,一个为T类型的对象,另一个double类型,但不返回任何结果的操作
ObjIntConsumer表示接受两个参数,一个为T类型的对象,另一个int类型,但不返回任何结果的操作
ObjLongConsumer表示接受两个参数,一个为T类型的对象,另一个double类型,但不返回任何结果的操作
ToDoubleBiFunction<T,U>表示接受两个不同类型的参数,但返回一个double类型的结果的操作
ToDoubleFunction表示一个接受指定类型T的参数,并返回一个double类型的结果的操作
ToIntBiFunction<T,U>表示接受两个不同类型的参数,但返回一个int类型的结果的操作
ToIntFunction表示一个接受指定类型T的参数,并返回一个int类型的结果的操作
ToLongBiFunction<T,U>表示接受两个不同类型的参数,但返回一个long类型的结果的操作
ToLongFunction表示一个接受指定类型的参数,并返回一个long类型的结果的操作
UnaryOperator表示接受一个参数,并返回一个与参数类型相同的结果的操作

函数式接口使用非常灵活,上面的举例都是很简单的 demo,它需要我们在日常开发过程中多多使用才能灵活地运用它。


Java 8 新特性—方法引用和构造器引用

在前面我们了解了 Lambda 表达式,它能够简化我们的程序,但是它还不是最简单的,Java 8 引入了方法引用可以对 Lambda 表达式再进一步简化。

img

什么是方法引用

我们先看一个例子。首先定义一个 Student 类:

public class Student {
    private String name;

    private Integer age;

    public static int compareByAge(Student a,Student b) {
        return a.getAge().compareTo(b.getAge());
    }
}

Student 中含有一个静态方法 compareByAge(),它是用来比较年龄的。

现在需要实现一个需求,有一批学生我们希望能够根据 age 进行排序。

在没有学习 Lambda 表达式时,我们这样写:

public class MethodReferenceTest {
    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(
                new Student("小明",16),
                new Student("小红",14),
                new Student("小兰",15),
                new Student("小李",18),
                new Student("小张",14),
                new Student("小林",15)
        );

        Collections.sort(studentList, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge().compareTo(o2.getAge());
            }
        });
        System.out.println(studentList);
    }
}

学习了 Lambda 表达式后,我们知道 Comparator 接口是一个函数式接口,因此我们可以使用Lambda表达式,而不需要使用这种匿名内部类的方式:

public class MethodReferenceTest {
    public static void main(String[] args) {
        // 省略代码...

        Collections.sort(studentList, (o1,o2) -> Student.compareByAge(o1,o2));
        System.out.println(studentList);
    }
}

注意,这里我们是使用 Student 类中的静态方法:compareByAge()。到这里后其实还有进一步的优化空间:

public class MethodReferenceTest {
    public static void main(String[] args) {
        // 省略代码...

        Collections.sort(studentList, Student::compareByAge);
        System.out.println(studentList);
    }
}

这段代码将 Lambda 表达式 (o1,o2) -> Student.compareByAge(o1,o2) 转变为了 Student::compareByAge 是不是很懵逼?

Student::compareByAge 写法就是我们这篇文章要讲的方法引用。那什么是方法引用呢?

方法引用是 Java 8 引入的特性,它提供了一种更加简洁的可用作 Lambda 表达式的表达方式。 定义:方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。

我们可以简单认为,方法引用是一种更加简洁易懂的 Lambda表达式。当 Lambda 表达式的主体中只有一个执行方法的调用时,我们可以不使用 Lambda 表达式,而是选择更加简洁的方法引用,这样可读性更高一些。

三种方法引用类型

方法引用的标准格式是:类名::方法名。它有如下三种类型:

类型格式
引用静态方法类名::静态方法名
引用对象的实例方法实例对象::方法名
引用类型的任意对象的实例方法类名::实例方法名

下面我们来看这三种类型的使用方法。

引用静态方法

引用静态方法的格式是:类名::静态方法名。这个是其实和我们使用静态方法一样,只不过是将 “.” 替换成了 “::”。其实我们上面那个例子就是引用静态方法的例子,这里大明哥再举一个示例,java.lang.Math 中有很多静态方法,比如:

// Lambda 表达式
Function<Integer,Integer> function1 = t -> Math.abs(t);
int result1 = function1.apply(-123);
        
// 方法引用
Function<Integer,Integer> function2 = Math::abs;
int result2 = function2.apply(-123); 

引用对象的实例方法

引用对象的实力方法格式是:实例对象名::实例方法名,这种方式引用的是一个实例方法,所以需要提供一个对象实例来引用,如下:

Student student = new Student("小明",15);

// Lambda 表达式
Supplier<String> supplier1 = () -> student.getName();
String name1 = supplier1.get();
 
// 方法引用  
Supplier<String> supplier2 = student::getName;
String name2 = supplier2.get();

这种方式在我们使用 Stream 来操作集合时用得非常多。

引用类型的任意对象的实例方法

引用类型的任意对象的实例方法的格式是:类名::实例方法名,这个有点儿不是很好理解。这种引用方式引用的是一个特定对象的实例方法,通常在函数式接口中作为第一个参数传递给方法引用,怎么理解呢?我们看下面两个例子:

比如 Comparator 中的 int compare(T o1, T o2),我们需要比较两个字符串的大小,使用方式如下:

Comparator<String> comparator = (o1,o2) -> o1.compareTo(o2);
System.out.println(comparator.compare("sike","sk"));

改成 类名::实例方法名 怎么改呢?

Comparator<String> comparator = String::compareTo;
System.out.println(comparator.compare("sike","sk"));

是不是比较懵逼?再看一个:

// Lambda 表达式
List<String> list = Arrays.asList("xiaoming","xiaohong","xiaoli","xiaodu");
list.forEach(name -> name.toUpperCase());
// 方法引用
List<String> list = Arrays.asList("xiaoming","xiaohong","xiaoli","xiaodu");
list.forEach(String::toUpperCase);

是不是比较懵?其实大明哥看到这个也比较懵,确实是不好理解,但是没关系,最后面大明哥教你们一个终极神器,让你使用方法引用不再困难。

方法引用的前提条件

方法引用确实可以极大地降低我们的代码量也更加清晰了,但是并不是所有的 Lambda 表达式都可以转换为方法引用。它有如下几个前提条件。

1、Lambda 表达式中只有一个调用方法的代码

注意这个一个调用方法的含义,它包含两重意思。

  • 只有一行代码
List<String> list = Arrays.asList("xiaoming","xiaohong","xiaoli","xiaodu");
list.forEach(name -> {
    System.out.println("www.skjava.com");
    name.toUpperCase();
});

这个 Lambda 中有两行代码,这是无法转换为方法引用的。

  • 只有一个调用方法
List<String> list = Arrays.asList("xiaoming","xiaohong","xiaoli","xiaodu");
list.forEach(name -> System.out.println(name.toUpperCase()));

这种写法也是转换不了的,虽然只有一行代码,但是它调用了两个方法。

2、方法引用的目标方法必须与Lambda 表达式的函数接口的抽象方法参数类型和返回类型相匹配

这就意味着目标方法的参数数量、类型以及返回类型必须与函数接口的要求一致。但是它只能规范引用静态方法引用对象的实例方法,而引用类型的任意对象的实例方法这种类型其实是不适用。

3、如果方法引用是通过对象引用来实现的,那么 Lambda 表达式中的参数列表的第一个参数必须是方法

相关文章:

并发编程下的集合:数组寻址、LinkedList、HashMap、ConcurrentHashMap

如果发现hash取模后的数组索引位下无元素则直接新增,若不是空那就说明存在hash冲突,则判断数组索引位链表结构中的第一个元素的key以及hash值是否与新的key一致则直接覆盖,若不一致则判断当前的数组索引下的链表结构是否为红黑树,若为红黑树则走红黑树的新增方法,若不为红黑树则遍历当前链表结构,遍历中发现某个节点元素的next为null是则直接将新元素指针与next进行关联,若在遍历到next为空前判断到,某个节点的key以及key的hash值与新的key与新的keyhash值一致时则走覆盖。

【日常开发之插件篇】IDEA plugins 神器助我!!

今早因为老代码的一些bug让我突然觉得Idea的一些插件特别好用,我准备将我平时所用到的一些插件做个推荐以及记录。

【日常开发之FTP】Windows开启FTP、Java实现FTP文件上传下载

FTP是一个专门进行文件管理的操作服务,一般来讲可以在任意的操作系统之中进行配置,但是如果考虑到简便性,一般来讲可以直接在Linux系统下进行安装。FTP (File Transfer Protocol、文件传输协议)是TCP/IP协议中的一部分,属于应用层协议。使用FTP最主要的功能是对文件进行管理,所以在FTP内部对于文件支持有两种传输模式:文本模式(ASCII、默认)和二进制模式(Binary),通常文本文件使用ASCIl模式,而对于图片、视频、声音、压缩等文件则会使用二进制的方式进行传输。

【Linux之升华篇】Linux内核锁、用户模式与内核模式、用户进程通讯方式

alloc_pages(gfp_mask, order),_ _get_free_pages(gfp_mask, order)等。字符设备描述符 struct cdev,cdev_alloc()用于动态的分配 cdev 描述符,cdev_add()用于注。外,还支持语义符合 Posix.1 标准的信号函数 sigaction(实际上,该函数是基于 BSD 的,BSD。从最初的原子操作,到后来的信号量,从。(2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的。

【Mongdb之数据同步篇】什么是Oplog、Mongodb 开启oplog,java监听oplog并写入关系型数据库、Mongodb动态切换数据源

oplog是local库下的一个固定集合,Secondary就是通过查看Primary 的oplog这个集合来进行复制的。每个节点都有oplog,记录这从主节点复制过来的信息,这样每个成员都可以作为同步源给其他节点。Oplog 可以说是Mongodb Replication的纽带了。

【日常开发之Windows共享文件】Java实现Windows共享文件上传下载

下拉框选择你选择的用户点击添加,然后共享确定。创建一个文件夹然后点击属性界面,点击共享。maven版本存在于SMB协议的兼容问题。首先开启服务,打开控制面板点击程序。点击启用或关闭Windows功能。我这边是专门创建了一个用户。SMB1.0选中红框内的。

CXFServlet类的作用

CXFServlet是Apache CXF框架中的一个核心组件,用于处理HTTP请求并将它们转换为Web服务调用。通过配置CXFServlet,你可以轻松地部署和管理SOAP和RESTful Web服务。

@Scheduled注解的scheduler属性什么作用

注解是 Spring Framework 提供的一种机制,用于定义计划任务,即周期性执行的任务。 注解可以应用于方法上,以指示 Spring 容器在特定的时间间隔或按照某种调度规则来调用该方法。 属性是 注解的一个可选属性,它的作用是允许开发者指定一个自定义的 对象来控制任务的调度方式。默认情况下, 注解使用 Spring 内部的 来执行任务,但如果需要更高级的定制化需求,可以通过 属性指定一个自定义的 实现。自定义调度器:共享调度器资源:高级调度需求:假设你想使用 作为调度器,并且希望所有带有

过滤器、拦截器、aop的先后顺序和作用范围&拦截器preHandle(),postHandle(),afterComplation()方法执行顺序

在Spring框架中,过滤器(Filter)、拦截器(Interceptor)和面向切面编程(AOP)都是用于处理请求和处理流程的组件,但它们的作用范围和触发时机有所不同。下面我会解释这三者的先后顺序和作用范围。执行顺序:请注意,这个顺序可能因具体的配置和使用的技术而有所不同。在实际应用中,建议根据项目的具体需求来合理配置和使用这些组件。拦截器执行流程图:实现拦截器需要实现这个接口,这个 接口中有三个默认方法,这三个方法的执行顺序:我们实现接口然后重写这三个方法,就会在对应的时机被自动执行。这里就是调用处理

Zookeeper概要、协议、应用场景

Zoopkeeper提供了一套很好的分布式集群管理的机制,就是它这种基于层次型的目录树的数据结构并对树中的节点进行有效管理,从而可以设计出多种多样的分布式的数据管理模型,作为分布式系统的沟通调度桥梁。

spring.factories文件的作用

即spring.factories文件是帮助spring-boot项目包以外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器中。在Spring Boot启动时,它会扫描classpath下所有的spring.factories文件,加载其中的自动配置类,并将它们注入到Spring ApplicationContext中,使得项目能够自动运行。spring.factories文件是Spring Boot自动配置的核心文件之一,它的作用是。

Spring事务七大传播机制与五个隔离级别,嵌套事务

如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。

常见的七种加密算法及实现

**数字签名**、**信息加密** 是前后端开发都经常需要使用到的技术,应用场景包括了用户登入、交易、信息通讯、`oauth` 等等,不同的应用场景也会需要使用到不同的签名加密算法,或者需要搭配不一样的 **签名加密算法** 来达到业务目标。这里简单的给大家介绍几种常见的签名加密算法和一些典型场景下的应用。## 正文### 1. 数字签名**数字签名**,简单来说就是通过提供 **可鉴别** 的 **数字信息** 验证 **自身身份** 的一种方式。一套 **数字签名** 通常定义两种 **互补

7min到40s:SpringBoot 启动优化实践

然后重点排查这些阶段的代码。先看下。

SpringBoot系列教程之Bean之指定初始化顺序的若干姿势

之前介绍了@Order注解的常见错误理解,它并不能指定 bean 的加载顺序,那么问题来了,如果我需要指定 bean 的加载顺序,那应该怎么办呢?本文将介绍几种可行的方式来控制 bean 之间的加载顺序。

在Java中使用WebSocket

WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接,这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发,并于2011年成为标准。

3种方案,模拟两个线程抢票

在多线程编程中,资源竞争是一个常见的问题。资源竞争发生在多个线程试图同时访问或修改共享资源时,可能导致数据不一致或其他并发问题。在模拟两个线程抢票的场景中,我们需要考虑如何公平地分配票,并确保每个线程都有机会成功获取票。本篇文章将通过三种方式来模拟两个线程抢票的过程,以展示不同的并发控制策略。使用 Synchronized 来确保一次只有一个线程可以访问票资源。使用 ReentrantLock 来实现线程间的协调。使用 Semaphore 来限制同时访问票的线程数量。

替代Druid,HakariCP 为什么这么快?

这次源码探究,真的感觉看到了无数个小细节,无数个小优化,积少成多。平时开发过程中,一些小的细节也一定要“扣”。

Java中volatile 的使用场景有哪些?

volatile是一种轻量级的同步机制,它能保证共享变量的可见性,同时禁止重排序保证了操作的有序性,但是它无法保证原子性。所以使用volatilevolatile。

JDK22 正式发布了 !

Java 22 除了推出了新的增强功能和特性,也获得 Java Management Service (JMS) 的支持,这是一项新的 Oracle 云基础设施远程软件服务(Oracle Cloud Infrastructure, OCI) 原生服务,提供统一的控制台和仪表盘,帮助企业管理本地或云端的 Java 运行时和应用。使包含运行时计算值的字符串更容易表达,简化 Java 程序的开发工作,同时提高将用户提供的值编写成字符串,并将字符串传递给其他系统的程序的安全性。支持开发人员自由地表达构造器的行为。

Jackson 用起来!

你可以创建自定义序列化器和反序列化器以自定义特定字段或类的序列化和反序列化行为。为此,请创建一个实现或接口的类,并在需要自定义的字段或类上使用和注解。@Override// ...其他代码...优势性能优异:Jackson在序列化和反序列化过程中表现出优秀的性能,通常比其他Java JSON库更快。灵活性:通过注解、自定义序列化器/反序列化器等功能,Jackson提供了丰富的配置选项,允许你根据需求灵活地处理JSON数据。易于使用:Jackson的API设计简洁明了,易于学习和使用。

拜托!别再滥用 ! = null 判空了!!

另外,也许受此习惯影响,他们总潜意识地认为,所有的返回都是不可信任的,为了保护自己程序,就加了大量的判空。如果你养成习惯,都是这样写代码(返回空collections而不返回null),你调用自己写的方法时,就能大胆地忽略判空)这种情况下,null是个”看上去“合理的值,例如,我查询数据库,某个查询条件下,就是没有对应值,此时null算是表达了“空”的概念。最终,项目中会存在大量判空代码,多么丑陋繁冗!,而不要返回null,这样调用侧就能大胆地处理这个返回,例如调用侧拿到返回后,可以直接。

详解Java Math类的toDegrees()方法:将参数从弧度转换为角度

Java Math 类的 toDegrees() 方法是将一个角度的弧度表示转换为其度表示,返回值为double类型,表示从弧度数转换而来的角度数。这就是Java Math 类的 toDegrees() 方法的攻略。我们已经了解了该方法的基本概念、语法、注意事项以及两个示例。希望这篇攻略对你有所帮助。

SpringBoot接口防抖(防重复提交)的一些实现方案

作为一名老码农,在开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。啥是防抖所谓防抖,一是防用户手抖,二是防网络抖动。

公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!

前段时间,同事在代码中KW扫描的时候出现这样一条:上面出现这样的原因是在使用foreach对HashMap进行遍历时,同时进行put赋值操作会有问题,异常ConcurrentModificationException。于是帮同简单的看了一下,印象中集合类在进行遍历时同时进行删除或者添加操作时需要谨慎,一般使用迭代器进行操作。于是告诉同事,应该使用迭代器Iterator来对集合元素进行操作。同事问我为什么?这一下子把我问蒙了?对啊,只是记得这样用不可以,但是好像自己从来没有细究过为什么?

每天一个摆脱if-else工程师的技巧——优雅的参数校验

在日常的开发工作中,为了程序的健壮性,大部分方法都需要进行入参数据校验。最直接的当然是在相应方法内对数据进行手动校验,但是这样代码里就会有很多冗余繁琐的if-else。throw new IllegalArgumentException("用户姓名不能为空");throw new IllegalArgumentException("性别不能为空");throw new IllegalArgumentException("性别错误");

SpringBoot请求转发与重定向

但是可能由于B网址相对于A网址过于复杂,这样搜索引擎就会觉得网址A对用户更加友好,因而在重定向之后任然显示旧的网址A,但是显示网址B的内容。在平常使用手机的过程当中,有时候会发现网页上会有浮动的窗口,或者访问的页面不是正常的页面,这就可能是运营商通过某种方式篡改了用户正常访问的页面。重定向,是指在Nginx中,重定向是指通过修改URL地址,将客户端的请求重定向到另一个URL地址的过程,Nginx中实现重定向的方式有多种,比如使用rewrite模块、return指令等。使用场景:在返回视图的前面加上。

SSO 单点登录和 OAuth2.0 有何区别?

此方法的缺点是它依赖于浏览器和会话状态,对于分布式或者微服务系统而言,可能需要在服务端做会话共享,但是服务端会话共享效率比较低,这不是一个好的方案。在单点登录的上下文中,OAuth 可以用作一个中介,用户在一个“授权服务器”上登录,并获得一个访问令牌,该令牌可以用于访问其他“资源服务器”上的资源。首先,SSO 主要关注用户在多个应用程序和服务之间的无缝切换和保持登录状态的问题。这种方法通过将登录认证和业务系统分离,使用独立的登录中心,实现了在登录中心登录后,所有相关的业务系统都能免登录访问资源。

TCP协议-TCP连接管理

TCP协议是 TCP/IP 协议族中一个非常重要的协议。它是一种面向连接、提供可靠服务、面向字节流的传输层通信协议。TCP(Transmission Control Protocol,传输控制协议)。

接口响应慢?那是你没用 CompletableFuture 来优化!

大多数程序员在平时工作中,都是增删改查。这里我跟大家讲解如何利用CompletableFuture优化项目代码,使项目性能更佳!