Java 21 新特性的实践,确实很丝滑!

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 

dd766feb53b206850b96301b3103d75f.gif

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • Cloud 地址:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn

来源:medium.com/

b7e353d8ce6662bb69271e170a965ce6.jpeg


JDK 21 于 2023 年 9 月 19 日发布,是继之前的 LTS 版本 JDK 17 之后最新的长期支持 (LTS) 版本。在本文中,我们将探讨 JDK 21 新引入的功能。

e49615e368deafdcd16de8a84e85915e.png

以下是 JDK 21 的新功能列表:

  • 虚拟线程

  • 序列集合

  • 记录模式

  • 字符串模板(预览)

  • 未命名模式和变量(预览)

  • 未命名类和实例主要方法(预览)

  • 作用域值(预览)

  • 结构化并发(预览)

1 虚拟线程

从 Java 代码的角度来看,虚拟线程感觉就像普通线程,但它们没有 1:1 映射到操作系统/平台线程。它是从虚拟线程到载体线程进而到操作系统线程的M:N映射。

有一个所谓的载体线程池,虚拟线程临时映射(“安装”)到该线程池上。一旦虚拟线程遇到阻塞操作,虚拟线程就会从载体线程中移除(“卸载”),并且载体线程可以执行另一个虚拟线程(新的或之前被阻塞的虚拟线程)。

载体线程池是ForkJoinPool

7f83023fc741fdb2ae2203d9592ac2bb.png

虚拟线程的一些优点:

  • 提高应用程序吞吐量

  • 提高应用程序可用性

  • 减少内存消耗

创建虚拟线程

要创建虚拟线程,我们可以使用 Thread.ofVirtual() 工厂方法并传递可运行对象。

  1. Thread.ofVirtual().start(Runnable);

  2. Thread.ofVirtual().unstarted(Runnable);

如果你想让虚拟线程立即启动,你可以使用start() 方法,它会立即执行传递给它的Runnable start()

如果不希望虚拟线程立即启动,可以使用该unstarted()方法。

创建使用虚拟线程的ExecutorService

我们只需要替换newFixedThreadPoolnewVirtualThreadPerTaskExecutor

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VirtualThreadExample {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

        executor.submit(() -> {
            System.out.println(Thread.currentThread().getName())
        });

        executor.shutdown();
    }
}

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

2 顺序集合

顺序集合为我们提供了defined encounter order(是一种所见即所得的顺序,含义是从队列中取出元素的顺序既是你存放该元素时候的顺序),用于访问第一个和最后一个元素并以相反的顺序迭代。

这意味着我们可以在集合的两端添加、检索或删除元素。

4aafec8be53d8cae1a20fe13b17d9643.png
public interface SequencedCollection<E> extends Collection<E> {
    default void addFirst(E e) { ... }
    default void addLast(E e) { ... }
    default E getFirst() { ... }
    default E getLast() { ... }
    default E removeFirst() { ... }
    default E removeLast() { ... }
    SequencedCollection<E> reversed();
}

正如我们所看到的,除了reverse()之外的所有方法都是默认方法并提供默认实现。

这意味着现有的集合类(例如 ArrayList 和 LinkedList)都可以实现此接口,而无需更改其代码。

ArrayList<Integer> list = new ArrayList<>();
        list.add(1); // [1]
        list.addFirst(0); // [0, 1]
        list.addLast(2); // [0, 1, 2]
        list.getFirst(); // 0
        list.getLast(); // 2
        list.reversed(); // [2, 1, 0]

SequencedSet

SequencedSet 接口对于具有有序元素的 Set 非常有用,特别是当您必须执行某些操作(例如检索或删除第一个或最后一个索引处的元素)时。它还提供了一种反转元素的方法。

您还需要知道两个 SequencedSet 对象的比较与其他类型的 Set 相同,不依赖于元素顺序。

interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
    SequencedSet<E> reversed();
}

LinkedHashSet 是 Set 的一个实现,它实现了 SequencedSet 接口。

因此,您可以使用 LinkedHashSet 来创建 SequencedSet。

Set 的其他实现(例如 HashSet 和 TreeSet)未实现该接口。

让我们探索一些示例来演示如何访问第一个和最后一个元素,以及如何使用反向函数:

SequencedSet<String> values = new LinkedHashSet<>();
        values.add("one");
        values.add("two");
        System.out.println(values); // [one, two]

        values.addFirst("zero");
        System.out.println(values); // [zero, one, two]
        values.addFirst("one");
        System.out.println(values); // [one, zero, two]

        values.addLast("three");
        System.out.println(values); // [one, zero, two, three]

        values.removeFirst();
        System.out.println(values); // [zero, two, three]

        SequencedSet<String> reversedSet = values.reversed();
        System.out.println(reversedSet); // [three, two, zero]

        boolean isEqual = values.equals(reversedSet);
        System.out.println(isEqual); // true
        System.out.println(values.hashCode()); // 612888
        System.out.println(reversedSet.hashCode()); // 612888
        System.out.println(values.hashCode() == reversedSet.hashCode()); // true

SequencedMap

如果要使用 SequencedMap 中定义的新方法,则需要使用 Map 实现,例如 LinkedHashMap 或实现 SortedMap 的 Map。

HashMap 不利用 Sequenced Collections,因为它没有定义 defined encounter order(是一种所见即所得的顺序,含义是从队列中取出元素的顺序既是你存放该元素时候的顺序)。

interface SequencedMap<K,V> extends Map<K,V> {
    SequencedMap<K,V> reversed();
    SequencedSet<K> sequencedKeySet();
    SequencedCollection<V> sequencedValues();
    SequencedSet<Entry<K,V>> sequencedEntrySet();
    V putFirst(K, V);
    V putLast(K, V);
    Entry<K, V> firstEntry();
    Entry<K, V> lastEntry();
    Entry<K, V> pollFirstEntry();
    Entry<K, V> pollLastEntry();
}

在下面的示例中,正如您所看到的,我们可以通过firstEntry()lastEntry()方法访问第一个和最后一个元素。

pollFirstEntry()方法将删除并返回第一个键值元素,如果映射为空,则返回 null。

此外,调用reverse()只会比较元素,而不依赖于它们的顺序。

SequencedMap<String, Integer> myMap = new LinkedHashMap<>();
        myMap.put("one", 1);
        myMap.put("two", 2);
        System.out.println(myMap); // {one=1, two=2}

        Entry<String, Integer> firstEntry = myMap.firstEntry();
        System.out.println(firstEntry); // one=1

        Entry<String, Integer> lastEntry = myMap.lastEntry();
        System.out.println(lastEntry); // two=2

        myMap.putFirst("zero", 0);
        System.out.println(myMap); // {zero=0, one=1, two=2}
        myMap.putFirst("one", -1);
        System.out.println(myMap); // {one=-1, zero=0, two=2}

        Entry<String, Integer> polledFirstEntry = myMap.pollFirstEntry();
        System.out.println(polledFirstEntry); // one=-1
        System.out.println(myMap); // {zero=0, two=2}

        SequencedMap<String, Integer> reversedMap = myMap.reversed();
        System.out.println(reversedMap); // {two=2, zero=0}

        boolean isEqual = myMap.equals(reversedMap);
        System.out.println(isEqual); // true
        System.out.println(myMap.hashCode()); // 692224
        System.out.println(reversedMap.hashCode()); // 692224
        System.out.println(myMap.hashCode() == reversedMap.hashCode()); // true

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

3 字符串模板

这是预览功能,默认禁用,我们需要使用

--enable-preview启用字符串模板。

首先,在深入探讨字符串模板之前,我将探讨一些用于组合字符串的技术。

+(加号)运算符: 最大的缺点是每次使用 + 运算符时都会创建一个新字符串。

StringBuffer 和 StringBuilder: StringBuffer 是线程安全的,而 StringBuilder 是在 Java 5 中添加的,性能更高,但不是线程安全的替代方案。

它们的主要缺点是冗长,尤其是对于更简单的字符串:

var greeting = new StringBuilder()
        .append("Hello, welcome ")
        .append(name)
        .toString();

String::format 和 String::formatter: 它们允许可重用模板,但它们要求我们指定格式并以正确的顺序提供变量。

var format = "Good morning %s, It's a beautiful day!";
        var text = String.format(format, name);
// Java 15+
        var text = format.formatter(name);

尽管我们节省了字符串分配的数量,但现在 JVM 必须解析/验证模板字符串。

java.text.MessageFormat: 与String格式相同,但更详细

var format = new MessageFormat("Good morning {0}, It's a beautiful day!");
        var greeting = format.format(name);

现在我们有字符串模板来拯救

它简单、简洁,处理字符串的新方法称为模板表达式。它们可以执行插值,还为我们提供了组合字符串的灵活性,并将结构化文本转换为任何对象,而不仅仅是字符串。

模板表达式由三个组成部分组成:

  • 模板处理器:Java 提供了两种用于执行字符串插值的模板处理器:STR 和 FMT

  • 包含包装表达式的模板,如 {name}

  • 点 (.) 字符

以下是一些关于如何将字符串模板与模板处理器一起使用的示例:

package com.mina.stringtemplates;

import static java.util.FormatProcessor.FMT;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class StringTemplateExamples {

    public static String greeting(String firstName, String lastName) {
        return STR."Hello! Good morning \{ firstName } \{ lastName }" ;
    }

    public static String multiplyWithArithmeticExpressions(int a, int b) {
        return STR."\{ a } times \{ b } = \{ a * b }" ;
    }

    public static String multiplyWithJavaExpression(int a, int b) {
        return STR."\{ a } times \{ b } = \{ Math.multiplyExact(a, b) }" ;
    }

    //  multiplication with floating point numbers rounded to two decimal places using the FMT template processor
    public static String multiplyFloatingNumbers(double a, double b) {
        return FMT."%.2f\{ a } times %.2f\{ b } = %.2f\{ a * b }" ;
    }

    public static String getErrorResponse(int httpStatus, String errorMessage) {
        return STR."""
                {
                  "httpStatus": \{ httpStatus },
                  "errorMessage": "\{ errorMessage }"
                }""" ;
    }

    public static String getCurrentDate() {
        return STR."Today's date: \{
        LocalDate.now().format(
                DateTimeFormatter.ofPattern("yyyy-MM-dd")
        ) }" ;
}
}

4 记录模式

记录模式匹配是一种在单个步骤中匹配记录类型并访问其组件的方法。

我们用它来测试一个值是否是记录类类型的实例,如果是,则对其组件值执行模式匹配。

下面的示例测试是否是具有记录模式transaction的记录实例TransactionTransaction(String type, double amount)

package com.mina.recordpattern;

public class RecordPatternExample {

    // I'm using "_" for readability here, this won't compile
    public static String getTransactionType(Transaction transaction) {
        return switch (transaction) {
            case null -> throw new IllegalArgumentException("Transaction can not be null.");
            case Transaction(String type, double amount) when type.equals("Deposit") && amount > 0 -> "Deposit";
            case Transaction(String type, _) when type.equals("Withdrawal") -> "Withdrawal";
                default -> "Unknown transaction type";
        };
    }

    record Transaction(String type, double amount) {

    }
}

如果事务为空,会发生什么?你是对的——抛出了一个空指针异常。这也是Java 21中的情况,但是现在我们可以通过写case null->来显式地使用null case,这样可以避免NullPointerException。

保护模式:也可以保护特定情况。例如,我们使用when关键字来检查相等性。

5 switch 模式匹配

switch模式匹配在 Java 17 中作为预览功能引入,并在 Java 21 中永久保留。

语句switch将控制转移到多个语句或表达式之一,具体取决于其选择器表达式的值(可以是任何类型),并且case标签可以具有模式。

它检查其选择器表达式是否与模式匹配,与测试其选择器表达式是否完全等于常量相比,这更具可读性和灵活性。

package com.mina.switchpatternmatching;

import com.mina.switchpatternmatching.SwitchPatternMatchingExample.Transaction.Deposit;
import com.mina.switchpatternmatching.SwitchPatternMatchingExample.Transaction.Withdrawal;

public class SwitchPatternMatchingExample {

    public static String getTransactionType(Transaction transaction) {
        return switch (transaction) {
            case null:
                throw new IllegalArgumentException("Transaction can't be null.");
            case Deposit deposit when deposit.getAmount() > 0: //  Guarded pattern with when clause
                yield "Deposit";
            case Withdrawal withdrawal:
                yield "Withdrawal";
            default:
                yield "Unknown transaction type";
        };
    }

    sealed class Transaction permits Deposit, Withdrawal {

        private double amount;

        public Transaction(double amount) {
            this.amount = amount;
        }

        public double getAmount() {
            return amount;
        }


        final class Withdrawal extends Transaction {

            public Withdrawal(double amount) {
                super(amount);
            }
        }

        final class Deposit extends Transaction {

            public Deposit(double amount) {
                super(amount);
            }
        }
    }
}

希望这篇文章可以帮助您更好地了解 Java 21 的新功能!


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

2ab2d9946608929684ab87b43defafbc.png

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

2b698d785a0ee7ff3f0b33fb8db8b7e1.png

fa73e5b922f0c36280f726141ee96ef7.pngb79ef1bb26c16225d1f52b191504eec7.png4cefd055d00904af206dba7288d64326.pngd1fd77d0d3970992ff5cd987b60fc039.png

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
### 如何在UniApp中实现平的分类切换效果 在UniApp中,可以通过配置`animationType`和`animationDuration`属性来实现页面切换时的动画效果。例如,`fade-in`表示淡入,`fade-out`表示淡出[^1]。此外,还可以使用组合动画,如淡入加上动同时进行,以增强视觉效果[^2]。 对于更复杂的分类切换效果,可以利用Vue的过渡系统结合CSS动画或第三方动画库(如Animate.css)来实现。以下是一个示例代码,展示如何通过CSS动画实现的分类切换效果: ```html <template> <view class="container"> <view v-for="(item, index) in categories" :key="index" @click="switchCategory(index)" :class="['category', { active: currentIndex === index }]"> {{ item }} </view> <transition name="fade-slide"> <view v-if="currentIndex >= 0" class="content"> <text>当前分类: {{ categories[currentIndex] }}</text> </view> </transition> </view> </template> <script> export default { data() { return { categories: ['分类1', '分类2', '分类3', '分类4'], currentIndex: -1 }; }, methods: { switchCategory(index) { this.currentIndex = index; } } }; </script> <style> .category { padding: 10px; margin: 5px; border: 1px solid #ccc; cursor: pointer; } .category.active { background-color: #f0f0f0; } .content { padding: 20px; background-color: #fff; border: 1px solid #ddd; } .fade-slide-enter-active, .fade-slide-leave-active { transition: all 0.5s ease; } .fade-slide-enter-from, .fade-slide-leave-to { opacity: 0; transform: translateX(20px); } </style> ``` 上述代码展示了如何通过Vue的`<transition>`标签结合CSS动画实现的分类切换效果。当用户点击不同的分类时,内容区域会以淡入淡出并伴随水平动的方式显示或隐藏[^3]。 #### 性能优化建议 为了确保动画效果流畅,应避免过度使用复杂的动画,尤其是在需要频繁切换的场景下[^1]。可以通过以下方式优化性能: - 使用硬件加速:为动画元素添加`transform`或`opacity`属性,而非直接操作`left`或`top`。 - 减少DOM操作:尽量减少动画过程中对DOM树的修改。 - 合理设置动画持续时间:过长或过短的动画都会影响用户体验。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值