从Java8到Java23值得期待的x个特性

你好,我是看山。

本文收录在 《从小工到专家的 Java 进阶之旅》 系列专栏中。

概述

从 2017 年开始,Java 版本更新遵循每六个月发布一次的节奏,LTS版本则每两年发布一次,以快速验证新特性,推动 Java 的发展。

Java版本分布

数据来源2024 State of the Java Ecosystem

有意思的特性

可以从 《从小工到专家的 Java 进阶之旅》 系列专栏中查看各个版本的特性。

大家都很熟悉的特性

Java8中的Lambda表达式、Stream流、Optional是大版本特性,Java8是2014年发布,已有十年历史了。大家应该分厂熟悉了,这里不在赘述。推荐两篇文章:

补全技能树

Record 类型(Java 16)

Java新增了一个关键字record,它是定义不可变数据类型封装类的关键字,主要用在特定领域类上。

我们都知道,在Java开发中,我们需要定义POJO作为数据存储对象,根据规范,POJO中除了属性是个性化的,其他的比如gettersetterequalshashCodetoString都是模板化的写法,所以为了简便,很多类似Lombok的组件提供Java类编译时增强,通过在类上定义@Data注解自动添加这些模板化方法。在Java14中,我们可以直接使用record解决这个问题。

比如,我们定义一个Person类:

public record Person(String name, String address) {
}

我们转换为之前的定义会是一坨下面这种代码:

public final class PersonBefore {
    private final String name;
    private final String address;

    public PersonBefore(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String name() {
        return name;
    }

    public String address() {
        return address;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PersonBefore14 that = (PersonBefore14) o;
        return Objects.equals(name, that.name) && Objects.equals(address, that.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, address);
    }

    @Override
    public String toString() {
        return "PersonBefore{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

我们可以发现Record类有如下特征:

  1. 一个构造方法
  2. getter方法名与属性名相同
  3. equals()hashCode()方法
  4. toString()方法
  5. 类对象和属性被final修饰,所以构造函数是包含所有属性的,而且没有setter方法

Class类中也新增了对应的处理方法:

  • getRecordComponents():返回一组java.lang.reflect.RecordComponent对象组成的数组,该数组的元素与Record类中的组件相对应,其顺序与在记录声明中出现的顺序相同,可以从该数组中的每个RecordComponent中提取到组件信息,包括其名称、类型、泛型类型、注释及其访问方法。
  • isRecord():返回所在类是否是 Record 类型,如果是,则返回 true。

看起来,Record类和Enum很像,都是一定的模板类,通过语法糖定义,在Java编译过程中,将其编译成特定的格式,功能很好,但如果没有习惯使用,可能会觉得限制太多。

密封类和接口(Java 17)

目前,Java 没有提供对继承的细粒度控制,只有 public、protected、private、包内控制四种非常粗粒度的控制方式。

为此,密封类的目标是允许单个类声明哪些类型可以用作其子类型。这也适用于接口,并确定哪些类型可以实现它们。该功能特性新增了sealednon-sealed修饰符和permits关键字。

我们可以做如下定义:

public sealed class Person permits Student, Worker, Teacher {}

public sealed class Student extends Person
        permits Pupil, JuniorSchoolStudent, HighSchoolStudent, CollegeStudent, GraduateStudent {}

public final class Pupil extends Student {}

public non-sealed class Worker extends Person {}

public class OtherClass extends Worker {}

public final class Teacher extends Person {}

我们可以先定义一个sealed修饰的类Person,使用permits指定被继承的子类,这些子类必须是使用finalsealednon-sealed修饰的类。其中Student是使用sealed修饰,所以也需要使用permits指定被继承的子类。Worker类使用non-sealed修饰,成为普通类,其他类都可以继承它。Teacher使用final修饰,不可再被继承。

从类图上看没有太多区别:

密封类和接口

但是从功能特性上,起到了很好的约束作用,我们可以放心大胆的定义可以公开使用,但又不想被非特定类继承的类了。

全新的 HTTP 客户端(Java 11)

老版 HTTP 客户端存在很多问题,大家开发的时候基本上都是使用第三方 HTTP 库,比如 Apache HttpClient、Netty、Jetty 等。

新版 HTTP 客户端的目标很多,毕竟这么多珠玉在前,如果还是做成一坨,指定是要被笑死的。所以新版 HTTP 客户端列出了 16 个目标,包括简单易用、打印关键信息、WebSocket、HTTP/2、HTTPS/TLS、良好的性能、非阻塞 API 等等。

@Test
void testHttpClient() throws IOException, InterruptedException {
    final HttpClient httpClient = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2)
            .connectTimeout(Duration.ofSeconds(20))
            .build();
    final HttpRequest httpRequest = HttpRequest.newBuilder()
            .GET()
            .uri(URI.create("https://www.howardliu.cn/robots.txt"))
            .build();
    final HttpResponse<String> httpResponse = httpClient.send(httpRequest, BodyHandlers.ofString());
    final String responseBody = httpResponse.body();
    assertTrue(responseBody.contains("Allow"));
}

有序集合(Java 21)

有序集合是Java21版本中引入的一个新特性,旨在为Java集合框架添加对有序集合的支持。

有序集合是一种具有定义好的元素访问顺序的集合类型,它允许以一致的方式访问和处理集合中的元素,无论是从第一个元素到最后一个元素,还是从最后一个元素到第一个元素。

在 Java 中,集合类库非常重要且使用频率非常高,但是缺乏一种能够表示具有定义好的元素访问顺序的集合类型。

例如,ListDeque都定义了元素的访问顺序,但它们的共同父接口Collection却没有。同样,Set不定义元素的访问顺序,其子类型如HashSet也没有定义,但子类型如SortedSetLinkedHashSet则有定义。因此,支持访问顺序的功能散布在整个类型层次结构中,使得在API中表达某些有用的概念变得困难。Collection太通用,将此类约束留给文档规范,可能导致难以调试的错误。

而且,虽然某些集合有顺序操作方法,但是却不尽相同,比如


First element Last element
List list.get(0) list.get(list.size() - 1)
Deque deque.getFirst() deque.getLast()
SortedSet sortedSet.first() sortedSet.last()
LinkedHashSet linkedHashSet.iterator().next() 缺失

提供了有序集合SequencedCollectionSequencedSetSequencedMap

interface SequencedCollection<E> extends Collection<E> {
    // new method
    SequencedCollection<E> reversed();
    // methods promoted from Deque
    void addFirst(E);
    void addLast(E);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();
}

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

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

SequencedCollectionreversed()方法提供了一个原始集合的反向视图。任何对原始集合的修改都会在视图中可见。如果允许,视图中的修改会写回到原始集合。

JEP 431: 有序集合(Sequenced Collections)

我们看一个例子,假设我们有一个LinkedHashSet,现在我们想要获取它的反向视图并以反向顺序遍历它:

LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>(Arrays.asList(3, 2, 1));

// 获取反向视图
SequencedCollection<Integer> reversed = linkedHashSet.reversed();

// 反向遍历
System.out.println("原始数据:" + linkedHashSet);
System.out.println("反转数据:" + reversed);

// 运行结果:
// 原始数据:[3, 2, 1]
// 反转数据:[1, 2, 3]

这些方法都是便捷方法,内部数据结构没有变化,其实本质也是原来的用法。比如ArrayList中的getFirstgetLast方法:

/**
 * {@inheritDoc}
 *
 * @throws NoSuchElementException {@inheritDoc}
 * @since 21
 */
public E getFirst() {
    if (size == 0) {
        throw new NoSuchElementException();
    } else {
        return elementData(0);
    }
}

/**
 * {@inheritDoc}
 *
 * @throws NoSuchElementException {@inheritDoc}
 * @since 21
 */
public E getLast() {
    int last = size - 1;
    if (last < 0) {
        throw new NoSuchElementException();
    } else {
        return elementData(last);
    }
}

文末总结

想要了解各版本的详细特性,可以从从小工到专家的 Java 进阶之旅 系列专栏中查看。

青山不改,绿水长流,我们下次见。

推荐阅读


你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。

个人主页:https://www.howardliu.cn
个人博文:从Java8到Java23值得期待的x个特性(1):有意思的特性
CSDN 主页:https://kanshan.blog.csdn.net/
CSDN 博文:从Java8到Java23值得期待的x个特性(1):有意思的特性

👇🏻欢迎关注我的公众号「看山的小屋」,领取精选资料👇🏻

公众号:看山的小屋