vlambda博客
学习文章列表

JDK17 |java17学习 输出和文件

Chapter 6: Data Structures, Generics, and Popular Utilities

本章介绍 Java 集合框架及其三个主要接口,ListSet Map,包括泛型的讨论和演示。 equals()hashCode() 方法也在 Java 集合的上下文中讨论。用于管理数组、对象和时间/日期值的实用程序类也有相应的专用部分。学习本章后,您将能够在程序中使用所有主要的数据结构。

本章将涵盖以下主题:

  • ListSetMap接口
  • Collections 实用程序
  • Arrays 实用程序
  • Object 实用程序
  • java.time

让我们开始!

Technical requirements

为了能够执行本章提供的代码示例,您将需要以下内容:

  • 装有 Microsoft Windows、Apple macOS 或 Linux 操作系统的计算机
  • Java标准版 (SE) 17 或更高版本
  • 集成开发环境 (IDE) 或您首选的代码编辑器

本书的第 1 章Java 17 入门。本章的代码示例文件可在 GitHub 上的 https://github .com/PacktPublishing/Learn-Java-17-Programming.git 存储库,位于 examples/src/main/java/com/packt/learnjava/ch06_collections 文件夹中.

List, Set, and Map interfaces

Java 集合框架由 实现 集合数据结构的接口。集合类似于数组,因为它们可以保存对对象的引用并且可以作为一个组进行管理。不同之处在于,数组需要在使用之前定义其容量,而集合可以根据需要自动增加和减少其大小。您只需添加或删除对集合的对象引用,集合就会相应地更改其大小。另一个区别是集合的元素不能是原始类型,例如 shortint。如果需要存储此类类型值,则元素必须是对应的包装类型,例如 ShortInteger

Java 集合支持存储和访问集合元素的各种算法:有序列表、唯一集、字典(称为 map in Java),一个 stack,一个 queue 和一些 其他。 Java集合框架的所有类和接口都属于java.util Java 类库 (JCL)。 java.util 包包含以下内容:

  • 扩展Collection接口的接口:ListSetQueue,列出最流行的
  • 实现前面列出的接口的类:ArrayListHashSetStackLinkedList
  • Map 接口及其 ConcurrentMapSortedMap 子接口,以命名一对夫妇
  • 实现Map相关接口的类:HashMapHashTable和< code class="literal">TreeMap,列举最常用的三个

查看 java.util 包的所有类和接口需要一本专门的书。因此,在本节中,我们将简要概述三个主要接口 - ListSet class="literal">Map——以及它们每个的一个实现类——ArrayListHashSetHashMap。我们从 ListSet 接口共享的方法开始。 ListSet 的主要区别在于 Set 不允许元素的重复。另一个区别是 List 保留了元素的顺序并允许对它们进行排序。

为了识别集合中的元素,使用 equals() 方法。为了提高性能,实现 Set 接口的类也经常使用 hashCode() 方法。这有助于在大多数情况下(但并非总是如此)快速计算整数(称为 哈希值 或 哈希码) ) 对每个元素都是唯一的。元素 相同的哈希值被放置在同一个 bucket 中。在确定集合中是否已经存在某个值的同时,检查内部哈希表并查看该值是否已被使用就足够了。如果不是,则新元素是唯一的。如果是,则可以将新元素与具有相同哈希值的每个元素进行比较(使用 equals() 方法)。这样的过程比将新元素与集合中的每个元素逐个比较要快。

这就是为什么我们经常看到一个类的名字有一个hash前缀,说明这个类使用了一个hash值,所以元素必须实现hashCode() 方法。执行此操作时,您必须确保已实现它,以便每次 equals() 方法返回 true for两个对象, hashCode() 方法也一样。否则,刚才描述的使用哈希值的算法将不起作用。

最后,在谈论 java.util 接口之前,先谈谈泛型。

Generics

您可以在以下声明中最常看到 这些:

List<String> list = new ArrayList<String>();
Set<Integer> set = new HashSet<Integer>();

在前面的示例中,generics 是用尖括号括起来的元素性质声明。如您所见,它们是多余的,因为它们在赋值语句的左侧和右侧重复出现。这就是为什么 Java 允许用空括号 (<>) 替换右侧的泛型,称为 a 钻石,如以下代码片段所示:

List<String> list = new ArrayList<>();
Set<Integer> set = new HashSet<>();

泛型通知编译器有关集合元素的预期类型。这样,编译器可以检查程序员试图添加到已声明集合中的元素是否属于兼容类型。例如,请注意以下事项:

List<String> list = new ArrayList<>();
list.add("abc");
list.add(42);   //compilation error

这有助于避免运行时错误。它还提示程序员(因为 IDE 会在程序员编写代码时编译代码)关于可能对集合元素进行的操作。

我们还将看到这些其他类型的泛型:

  • 表示 一个类型是 T 或者是 T,其中 T 是用作集合泛型的类型。
  • 表示一种类型 T 或其任何基类(父) 类,其中 T 是用作集合泛型的类型。

有了这个,让我们从如何创建实现 ListSet 接口的类的对象开始——或者,在也就是说,ListSet类型的变量可以被初始化。为了演示这两个接口的方法,我们将使用两个类:ArrayList(实现List)和HashSet(实现 Set)。

How to initialize List and Set

由于 Java 9,ListSet 接口 具有可用于初始化集合的静态 of() 工厂方法,如下所述:

  • of():返回一个空集合。
  • of(E... e):返回一个集合,其中包含调用期间传入的元素数量。它们可以以逗号分隔的列表或数组的形式传递。

这里有一些例子:

//Collection<String> coll 
//        = List.of("s1", null); //does not allow null
Collection<String> coll = List.of("s1", "s1", "s2");
//coll.add("s3");         //does not allow add element
//coll.remove("s1");   //does not allow remove element
//((List<String>) coll).set(1, "s3");    
                       //does not allow modify element
System.out.println(coll);       //prints: [s1, s1, s2]
//coll = Set.of("s3", "s3", "s4");     
                            //does not allow duplicate
//coll = Set.of("s2", "s3", null);     
                                 //does not allow null
coll = Set.of("s3", "s4");
System.out.println(coll);  
                        //prints: [s3, s4] or [s4, s3]
//coll.add("s5");         //does not allow add element
//coll.remove("s2");   //does not allow remove element

如您所料,Set 的工厂方法不允许重复,因此我们将这一行注释掉(否则,前面的示例将在该行停止运行)。不太期望的是,您不能拥有 null 元素,并且在使用 of() 方法。这就是为什么我们注释掉了前面例子的一些行。如果您需要在集合初始化后添加元素,您必须使用构造函数或其他一些创建可修改集合的实用程序对其进行初始化(我们将看到 Arrays.asList()< /code> 很快)。

Collection 接口提供了两种向实现了 Collection 的对象添加元素的方法(ListSet) 如下所示:

  • boolean add(E e):这会尝试将提供的元素 e 添加到集合中;它在成功的情况下返回 true,在无法完成的情况下返回 false(例如,当这样一个Set 接口中已经存在元素)。
  • boolean addAll(Collection c):这会尝试将提供的集合中的所有元素添加到集合中;如果添加了至少一个元素,则返回 true,如果无法添加 false _idIndexMarker738">一个 元素到集合(例如,当提供的集合 c 的所有元素已经存在时Set 界面中)。

下面是一个使用 add() 方法的例子:

List<String> list1 = new ArrayList<>();
list1.add("s1");
list1.add("s1");
System.out.println(list1);     //prints: [s1, s1]
Set<String> set1 = new HashSet<>();
set1.add("s1");
set1.add("s1");
System.out.println(set1);      //prints: [s1]

下面是一个使用 addAll() 方法的例子:

List<String> list1 = new ArrayList<>();
list1.add("s1");
list1.add("s1");
System.out.println(list1);      //prints: [s1, s1]
List<String> list2 = new ArrayList<>();
list2.addAll(list1);
System.out.println(list2);      //prints: [s1, s1]
Set<String> set = new HashSet<>();
set.addAll(list1);
System.out.println(set);        //prints: [s1]

这是 add()addAll() 方法的功能:

List<String> list1 = new ArrayList<>();
list1.add("s1");
list1.add("s1");
System.out.println(list1);     //prints: [s1, s1]
List<String> list2 = new ArrayList<>();
list2.addAll(list1);
System.out.println(list2);      //prints: [s1, s1]
Set<String> set = new HashSet<>();
set.addAll(list1);
System.out.println(set);        //prints: [s1]
Set<String> set1 = new HashSet<>();
set1.add("s1");
Set<String> set2 = new HashSet<>();
set2.add("s1");
set2.add("s2");
System.out.println(set1.addAll(set2)); //prints: true
System.out.println(set1);              //prints: [s1, s2]

请注意,在 前面代码片段中的最后一个示例中,set1.addAll( set2) 方法返回 true,尽管并未添加所有元素。查看 add()addAll() 方法返回 false ,看下面的例子:

Set<String> set = new HashSet<>();
System.out.println(set.add("s1"));   //prints: true
System.out.println(set.add("s1"));   //prints: false
System.out.println(set);             //prints: [s1]
Set<String> set1 = new HashSet<>();
set1.add("s1");
set1.add("s2");
Set<String> set2 = new HashSet<>();
set2.add("s1");
set2.add("s2");
System.out.println(set1.addAll(set2)); //prints: false
System.out.println(set1);              //prints: [s1, s2]

ArrayListHashSet 类也有接受集合的构造函数,如以下代码片段所示:

Collection<String> list1 = List.of("s1", "s1", "s2");
System.out.println(list1);      //prints: [s1, s1, s2]
List<String> list2 = new ArrayList<>(list1);
System.out.println(list2);      //prints: [s1, s1, s2]
Set<String> set = new HashSet<>(list1);
System.out.println(set);        //prints: [s1, s2]
List<String> list3 = new ArrayList<>(set);
System.out.println(list3);      //prints: [s1, s2]

现在,在 我们 了解了如何初始化集合之后,我们可以转向 ListSet 接口。

java.lang.Iterable interface

Collection 接口 扩展了 java.lang.Iterable 接口,这意味着那些实现 Collection 接口的类——无论是否直接——也实现了 java.lang.Iterable 接口。 Iterable 接口中只有三个方法,如下所述:

  • 迭代器<T> iterator():返回一个实现java.util.Iterator接口的类的对象;它允许在 FOR 语句中使用该集合,如下例所示:
    Iterable<String> list = List.of("s1", "s2", "s3"); System.out.println(list);       //prints: [s1, s2, s3] for(字符串 e:列表){     System.out.print(e + " ");  //prints: s1 s2 s3 }
  • default void forEach (Consumer function):这应用了函数">Consumer 类型到集合的每个元素,直到所有元素都被处理或函数抛出异常。我们将在第 13 章中讨论什么是函数>, 函数式编程;现在,我们将在这里仅提供一个示例:
    Iterable<String> list = List.of("s1", "s2", "s3"); System.out.println(list);   //打印:[s1, s2, s3] list.forEach(e -> System.out.print(e + " "));                              //打印:s1 s2 s3
  • 默认拆分器<T> splititerator():返回一个实现java.util.Spliterator接口的类的对象;它主要用于实现允许并行处理的方法,超出了本书的范围。

Collection interface

正如我们已经 提到的,ListSet 接口扩展了 < code class="literal">Collection接口,表示Collection接口的所有方法都被List 和 设置。这些方法在这里列出:

  • boolean add(E e):这会尝试将元素添加到集合中。
  • boolean addAll(Collection c):这会尝试添加所提供集合中的所有元素。
  • boolean equals(Object o):将集合与提供的 o 对象进行比较。如果提供的对象不是集合,则此对象返回 false;否则,它将集合的组成与提供的集合的组成进行比较(作为 o 对象)。在 List 的情况下,它还比较元素的顺序。让我们用几个例子来说明这一点,如下所示:
    Collection
                       
                 
                   
                 
                   list1 = List.of("s1", "s2", "s3"); System.out.println(list1);     //打印:[s1, s2, s3] 集合<字符串> list2 = List.of("s1", "s2", "s3"); System.out.println(list2);     //打印:[s1, s2, s3] System.out.println(list1.equals(list2));                                          //打印:真 集合<字符串> list3 = List.of("s2", "s1", "s3"); System.out.println(list3);     //打印:[s2, s1, s3] System.out.println(list1.equals(list3));                                          //打印:false 集合<字符串> set1 = Set.of("s1", "s2", "s3"); System.out.println(set1);                //prints: [s2, s3, s1] 或者不同的顺序 集合<字符串> set2 = Set.of("s2", "s1", "s3"); System.out.println(set2);                //prints: [s2, s1, s3] 或者不同的顺序 System.out.println(set1.equals(set2));                                           //打印:真 集合<字符串> set3 = Set.of("s4", "s1", "s3"); System.out.println(set3);                //prints: [s4, s1, s3] 或不同的顺序 System.out.println(set1.equals(set3));                                          //prints: false 
                 
  • int hashCode():这个返回集合的哈希值;它用于集合是需要 hashCode() 方法实现的集合的元素的情况。
  • boolean isEmpty():如果集合没有任何元素,则返回 true
  • int size():返回集合的元素个数;当 isEmpty() 方法返回 true 时,该方法返回 0
  • void clear():这会从集合中删除所有元素;调用该方法后,isEmpty()方法返回truesize( ) 方法返回 0
  • boolean contains(Object o):如果集合包含提供的 o<,则返回 true /代码>对象。要使此方法正常工作,集合的每个元素和提供的对象都必须实现 equals() 方法,并且对于 设置,应该实现hashCode()方法。
  • boolean containsAll(Collection :如果集合包含提供的集合中的所有元素,则返回 true。要使此方法正常工作,集合的每个元素和提供的集合的每个元素都必须实现 equals() 方法,并且对于 设置,应该实现hashCode()方法。
  • boolean remove(Object o):这会尝试从该集合中删除指定的元素,如果存在则返回 true。要使此方法正常工作,集合的每个元素和提供的对象都必须实现 equals() 方法,并且对于 设置,应该实现hashCode()方法。
  • boolean removeAll(Collection :这会尝试从集合中移除所提供集合的所有元素;类似于 addAll() 方法,如果至少有一个元素被删除,此方法返回 true;否则,它返回 false。要使此方法正常工作,集合的每个 元素和提供的集合的每个元素都必须实现 equals() 方法,并且在 Set 的情况下,应该实现 hashCode() 方法。
  • default boolean removeIf(Predicate filter):这会尝试从集合中移除所有满足给定谓词的元素;这是我们将在 第 13 章中描述的功能/em>函数式编程。如果至少删除了一个元素,则返回 true
  • boolean retainAll(Collection<?> c):这试图在集合中只保留提供的集合中包含的元素。类似于 addAll() 方法,如果至少保留一个元素,则此方法返回 true;否则,它返回 false。要使此方法正常工作,集合的每个元素和提供的集合的每个元素都必须实现 equals() 方法,并且对于 设置,应该实现hashCode()方法。
  • Object[] toArray(), T[] toArray(T[] a):这会将集合转换为数组。
  • default T[] toArray(IntFunction :使用提供的函数将集合转换为数组。我们将在 第 13 章中解释函数函数式编程
  • 默认流<E> stream():这会返回一个 Stream 对象(我们在 第 14 章Java 标准流)。
  • 默认流<E> parallelStream():这会返回一个可能并行的 Stream 对象(我们第 14 章Java 标准流)。

List interface

List 接口有 其他几个不属于其任何父接口的方法,如下所述:

  • 静态工厂 of() 方法,在 如何初始化 List 和 Set 小节中描述。
  • void add(int index, E element):这会将提供的元素插入到列表中提供的位置。
  • 静态列表<E> copyOf(Collection coll) :这将返回一个不可修改的 List 接口,其中包含给定 Collection 的元素接口并保留它们的顺序。以下代码片段演示了此方法的功能:
    Collection
                       
                 
                   
                 
                   list = List.of("s1", "s2", "s3"); System.out.println(list);    //prints: [s1, s2, s3] 列表<字符串> list1 = List.copyOf(list); //list1.add("s4");                //运行时错误 //list1.set(1, "s5");             //运行时错误 //list1.remove("s1");             //运行时错误 设置<字符串> set = new HashSet<>(); System.out.println(set.add("s1")); System.out.println(set);          //prints: [s1] 设置<字符串> set1 = Set.copyOf(set); //set1.add("s2");                 //运行时错误 //set1.remove("s1");              //运行时错误 设置<字符串> set2 = Set.copyOf(list); System.out.println(set2);    //打印:[s1, s2, s3] 
                 
  • E get(int index):这返回位于列表中指定位置的元素。
  • 列表<E> subList(int fromIndex, int toIndex):提取fromIndex(包括)和toIndex(不包括)之间的子列表.
  • int indexOf(Object o):返回列表中指定元素的第一个索引(位置);列表中的第一个元素的索引(位置)为 0
  • int lastIndexOf(Object o):返回列表中指定元素的最后一个索引(位置);列表中的最后一个元素具有 list.size() - 1 索引位置。
  • E remove(int index):移除位于列表中指定位置的元素;它返回删除的元素。
  • E set(int index, E element):替换位于列表中指定位置的元素;它返回被替换的元素。
  • default void replaceAll(UnaryOperator operator) :这通过将提供的函数应用于每个元素来转换列表。 UnaryOperator 函数将在 第 13 章函数式编程
  • ListIterator<E> listIterator():返回一个允许向后遍历列表的ListIterator对象。
  • ListIterator<E> listIterator(int index):返回一个ListIterator对象,允许子列表(从提供的位置开始)为向后遍历。例如,请注意以下事项:
    List<String> list = List.of("s1", "s2", "s3"); ListIterator<字符串> li = list.listIterator(); 而(li.hasNext()){     System.out.print(li.next() + " ");                                     //打印:s1 s2 s3 } while(li.hasPrevious()){     System.out.print(li.previous() + " ");                                   //打印:s3 s2 s1 } ListIterator<字符串> li1 = list.listIterator(1); 而(li1.hasNext()){     System.out.print(li1.next() + " ");                                           //打印:s2 s3 } ListIterator<字符串> li2 = list.listIterator(1); 而(li2.hasPrevious()){     System.out.print(li2.previous() + " ");                                           //打印:s1 }
  • default void sort(Comparator c):这个按照Comparator接口生成的顺序对列表进行排序假如。观察下面的,例如:
    List<String> list = new ArrayList<>(); list.add("S2"); list.add("s3"); list.add("s1"); System.out.println(list);     //prints: [S2, s3, s1] list.sort(String.CASE_INSENSITIVE_ORDER); System.out.println(list);     //prints: [s1, S2, s3] //list.add(null);      //导致NullPointerException list.sort(Comparator.naturalOrder()); System.out.println(list);     //prints: [S2, s1, s3] list.sort(Comparator.reverseOrder()); System.out.println(list);     //prints: [s3, s1, S2] list.add(null); list.sort(Comparator.nullsFirst(Comparator                                     .naturalOrder())); System.out.println(list);                           //打印:[null,S2,s1,s3] list.sort(Comparator.nullsLast(Comparator                                    .naturalOrder())); System.out.println(list);                                  //打印:[S2,s1,s3,空] 比较器<字符串>比较器 =      (s1, s2) -> s1 == 空? -1 : s1.compareTo(s2); list.sort(比较器); System.out.println(list);                                    //打印:[null, S2, s1, s3] 比较器<字符串>比较器 = (s1, s2) -> s1 == 空? -1 : s1.compareTo(s2); list.sort(比较器); System.out.println(list);                                   //打印:[null, S2, s1, s3]< /上一个> 

列表排序主要有两种方式,如下:

  • 使用Comparable接口实现(称为自然顺序)
  • 使用 Comparator 接口实现

Comparable 接口只有一个 compareTo() 方法。在前面的例子中,我们在 Comparable 接口实现的基础上实现了 Comparator 接口">字符串 类。如您所见,此实现提供与 Comparator.nullsFirst(Comparator.naturalOrder()) 相同的排序顺序。这种实现方式被称为函数式编程,我们将在第 13 章函数式编程< /em>。

Set interface

Set 接口具有 以下不属于其任何父接口的方法:

  • 静态 of() 工厂方法,在 如何初始化 List 和 Set 小节中描述。
  • 静态集合<E> copyOf(Collection 方法:这将返回一个不可修改的 Set 接口,其中包含给定 Collection 的元素;它的工作方式与 static <E>列出<E> List interface 部分中描述的 copyOf(Collection coll) 方法。

Map interface

Map 接口有许多类似于ListSet 方法,如下所示:

  • int size()
  • void clear()
  • int hashCode()
  • boolean isEmpty()
  • 布尔等于(Object o)
  • default void forEach(BiConsumer action)
  • 静态工厂方法:of(), of(K, V v), of( K k1, V v1, K k2, V v2),以及除此之外的许多其他方法

但是,Map 接口不扩展 IterableCollection 或就此而言,任何其他界面。它被设计成能够通过它们的键存储值。每个键都是唯一的,而在同一张地图上可以使用不同的键存储多个相等的值。 key 和 value 的组合构成 Entry,它是 Map 的内部接口。 valuekey 对象都必须实现 equals() 方法。 key 对象还必须实现 hashCode() 方法。

Map 接口的许多方法具有与 List 完全相同的签名和功能设置接口,这里不再赘述。我们将只介绍 Map 特定的方法,如下所示:

  • V get(Object key):根据提供的key取值;如果没有这样的键,它返回 null
  • 设置<K> keySet():这会从地图中检索所有键。
  • 集合<V> values():这会从地图中检索所有值。
  • boolean containsKey(Object key):如果提供的键存在于地图中,则返回 true
  • boolean containsValue(Object value):如果提供的值,则返回true存在于地图中。
  • V put(K key, V value):这会将value和它的key添加到map中;它返回使用相同键存储的先前值。
  • void putAll(Map<K,V> m):从地图中复制提供了所有的键值对。
  • default V putIfAbsent(K key, V value):存储提供的值并映射到提供的键,如果这样的键还没有被映射使用。它返回映射到提供的键的值——现有的或新的。
  • V remove(Object key):这会从地图中移除键和值;如果没有这样的键,或者该值为 null,它会返回一个值或 null
  • default boolean remove(Object key, Object value):如果映射中存在这样的键值对,则从映射中删除该键值对。
  • default V replace(K key, V value):如果提供的键当前映射到提供的值,则替换值。如果被替换,则返回旧值;否则,它返回 null
  • default boolean replace(K key, V oldValue, V newValue):这会将 oldValue 值替换为 oldValue 值,则提供literal">newValue 值。如果 oldValue 值被替换,则返回 true;否则,它返回 false
  • default void replaceAll(BiFunction :这个 将提供的函数应用到每个键值对在地图中并将其替换为结果,如果不可能,则抛出异常。
  • 设置<Map.Entry<K,V>> entrySet():这将返回一组所有键值对作为 Map.Entry 的对象。
  • default V getOrDefault(Object key, V defaultValue):这将返回映射到提供的键的值或 defaultValue 值,如果地图没有提供密钥。
  • 静态 Map.Entry<K,V> entry(K key, V value):这将返回一个不可修改的 Map.Entry 对象,其中包含 key 对象和value 对象。
  • 静态地图<K,V> copy(Map 这会将提供的 Map 接口转换为不可修改的接口。

下面的 Map 方法对于本书的范围来说太复杂了,所以我们只是为了完整起见而提到它们。它们允许在 Map 接口中将多个值组合或计算并聚合为单个现有值,或者创建一个新值:

  • default V merge(K key, V value, BiFunction remappingFunction) :如果提供的key-value对存在且value不是null,提供的函数用于计算新值;如果新计算的值为 null,它会删除键值对。如果提供的键值对不存在或值为null,则提供的非null值替换当前的值.此方法可用于聚合多个值;例如,它可用于连接以下字符串值:map.merge(key, value, String::concat)。我们将在 String::concat 的含义>第 13 章函数式编程。
  • default V compute(K key, BiFunction remappingFunction) :使用提供的函数计算新值。
  • default V computeIfAbsent(K key, Function :仅当提供的键尚未与值关联时,才使用提供的函数计算新值,或 值为 null
  • default V computeIfPresent(K key, BiFunction<K,V,V> remappingFunction):仅当提供的键已与某个值关联时,才使用提供的函数计算新值并且值不是 null

最后一组 computingmerging 方法很少使用。到目前为止,最流行的是 V put(K key, V value)V get(Object key) 方法,它允许使用主要的 Map 函数来存储键值对并使用键检索值。 Set<K> keySet() 方法通常用于遍历映射的键值对,尽管 entrySet() 方法似乎是一种更自然的方法。这是一个例子:

Map<Integer, String> map = Map.of(1, "s1", 2, "s2", 3, "s3");
for(Integer key: map.keySet()){
    System.out.print(key + ", " + map.get(key) + ", ");  
                                 //prints: 3, s3, 2, s2, 1, s1,
}
for(Map.Entry e: map.entrySet()){
    System.out.print(e.getKey() + ", " + e.getValue() + ", "); 
                                 //prints: 2, s2, 3, s3, 1, s1,
}

前面代码示例中的第一个 for 循环使用更广泛的方式通过遍历键来访问映射的键对值。第二个 for 循环遍历条目集,(在我们看来)这是一种更自然的方式。请注意,打印出的值与我们将它们放在地图中的顺序不同。这是因为,从 Java 9 开始,不可修改的集合(即 of() 工厂方法产生的)已经将随机化添加到 Set 的顺序 元素,在不同的代码执行之间改变元素的顺序。这样的设计是为了确保程序员不依赖于特定顺序的 Set 元素,这不能保证一套。

Unmodifiable collections

请注意,of() 工厂方法生成的 集合曾经是在 Java 9 中称为 immutable,在 Java 10 中称为 unmodifiable。这是因为 immutable 意味着您不能更改集合中的任何内容,而,实际上,如果集合元素是可修改的对象,则可以更改它们。例如,让我们构建一个 Person1 类的对象集合,如下所示:

class Person1 {
    private int age;
    private String name;
    public Person1(int age, String name) {
        this.age = age;
        this.name = name == null ? "" : name;
    }
    public void setName(String name){ this.name = name; }
    @Override
    public String toString() {
        return "Person{age=" + age +
                ", name=" + name + "}";
    }
}

在以下代码片段中,为简单起见,我们将创建一个仅包含一个元素的列表,然后尝试 修改该元素:

Person1 p1 = new Person1(45, "Bill");
List<Person1> list = List.of(p1);
//list.add(new Person1(22, "Bob"));
                         //UnsupportedOperationException
System.out.println(list);    
                    //prints: [Person{age=45, name=Bill}]
p1.setName("Kelly");       
System.out.println(list);    
                   //prints: [Person{age=45, name=Kelly}]

如您所见,虽然无法将元素添加到 of() factory 方法创建的列表中,如果对元素的引用存在于列表之外,则仍然可以修改其元素。

Collections utilities

有两个 类具有处理集合的静态方法,非常流行和有用,如下所示:

  • java.util.Collections
  • org.apache.commons.collections4.CollectionUtils

方法是静态的这一事实意味着它们不依赖于对象状态,因此它们是称为无状态方法或实用方法。

java.util.Collections class

Collections 中的许多 方法管理集合以及分析、排序和比较它们。其中有 70 多个,所以我们没有机会谈论所有这些。相反,我们将看看主流应用程序开发人员最常用的那些,如下所示:

  • static copy(List<T> dest, List<T> src):这会将 src 列表的元素复制到 < code class="literal">dest 列表并保留元素的顺序及其在列表中的位置。 dest 列表大小必须等于或大于 src 列表大小,否则会引发运行时异常。这是此方法的用法示例:
    List<String> list1 = Arrays.asList("s1","s2"); 列表<字符串> list2 = Arrays.asList("s3", "s4", "s5"); Collections.copy(list2, list1); System.out.println(list2);    //打印:[s1, s2, s5]
  • static void sort(List<T> list):根据实现的compareTo(T)方法对列表进行排序按每个元素(称为 自然排序)。它仅接受具有实现 Comparable 接口的元素的列表(这需要实现 compareTo (T) 方法)。在下面的示例中,我们使用 List<String> 因为 String 类实现了 Comparable
    //List<String>清单 =          //List.of("a", "X", "10", "20", "1", "2"); 列表<字符串>清单 =      Arrays.asList("a", "X", "10", "20", "1", "2"); Collections.sort(list); System.out.println(list);                            //打印:[1, 10 , 2, 20, X, a]

请注意,我们不能使用 List.of() 方法来创建列表,因为该列表是不可修改的,并且其顺序也无法更改。另外,查看生成的顺序:数字在前,然后是大写字母,然后是小写字母。这是因为 String 类中的 compareTo() 方法使用字符的代码点到 建立秩序。这是演示这一点的代码

List<String> list = 
           Arrays.asList("a", "X", "10", "20", "1", "2");
Collections.sort(list);
System.out.println(list);  //prints: [1, 10, 2, 20, X, a]
list.forEach(s -> {
    for(int i = 0; i < s.length(); i++){
       System.out.print(" " + 
                            Character.codePointAt(s, i));
    }
    if(!s.equals("a")) {
       System.out.print(","); 
                   //prints: 49, 49 48, 50, 50 48, 88, 97
    }
});

如您所见,顺序由组成字符串的字符的代码点的值定义。

  • static void sort(List<T> list, Comparator :这个按照 Comparator排序列表的顺序code> 对象提供,无论列表元素是否实现 Comparable 接口。例如,让我们对包含 Person 类中的对象的列表进行排序,如下所示:
    类人  {     私信年龄;     私有字符串名称;     public Person(int age, String name) {         this.age = 年龄;         this.name = name == null ? ““ : 姓名;     }     public int getAge() { return this.age; }     public String getName() { return this.name; }     @Override     public String toString() {         return "Person{name=" + name +                        ",年龄= " + 年龄 + "}";     } }
  • 这里是 Comparator 类,用于对 列表进行排序="literal">Person 对象:
    class ComparePersons 实现 Comparator
                       
                 
                   
                 
                   {     public int compare(Person p1, Person p2){         int 结果 = p1.getName().compareTo(p2.getName());         if (result != 0) { 返回结果; }         返回p1.age - p2.getAge();     } } 
                 

现在,我们可以使用 PersonComparePersons 类,如下所示:

List<Person> persons = 
      Arrays.asList(new Person(23, "Jack"),
                    new Person(30, "Bob"), 
                    new Person(15, "Bob"));
Collections.sort(persons, new ComparePersons());
System.out.println(persons);    
                //prints: [Person{name=Bob, age=15}, 
                //         Person{name=Bob, age=30}, 
                //         Person{name=Jack, age=23}]

正如我们已经提到的,Collections 类中有更多实用程序,因此我们建议您查看相关文档 at至少一次并了解其所有 功能。

CollectionUtils class

Apache Commons 项目中的 org.apache.commons.collections4.CollectionUtils a>包含静态无状态方法,它们补充了 java.util.Collections 类的方法。它们有助于搜索、处理和比较 Java 集合。

要使用此类,您需要将以下依赖项添加到 Maven pom.xml 配置文件中:

 <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
 </dependency>

这个类中有很多方法,随着时间的推移可能会添加更多的方法。这些实用程序是在 Collections 方法之外创建的,因此它们更加复杂和细微,不适合本书的范围。为了让您了解 CollectionUtils 类中可用的方法,下面是这些方法的简要说明,按功能分组:

  • 从集合中检索元素的方法
  • 将一个元素或一组元素添加到集合的方法
  • Iterable 元素合并到集合中的方法
  • 删除或保留有或没有条件的元素的方法
  • 比较两个集合的方法
  • 转换集合的方法
  • 从集合中选择和过滤的方法
  • 生成两个集合的并集、交集或差集的方法
  • 创建不可变空集合的方法
  • 检查集合大小和空的方法
  • 反转数组的方法

最后一个方法应该属于处理数组的实用类,这就是我们现在要讨论的。

Arrays utilities

有两个 类具有处理集合的静态方法,非常流行和有用,如下所示:

  • java.util.Arrays
  • org.apache.commons.lang3.ArrayUtils

我们将简要回顾它们中的每一个。

java.util.Arrays class

我们 已经多次使用过 java.util.Arrays 类。它是阵列管理的主要实用程序类。由于 asList(T...a) 方法,这个实用程序类曾经非常流行。这是创建和初始化集合的最紧凑的方式,如下面的代码片段所示:

List<String> list = Arrays.asList("s0", "s1");
Set<String> set = new HashSet<>(Arrays.asList("s0", "s1");

它仍然是创建可修改列表的一种流行方式——我们也使用过它。然而,在引入 List.of() 工厂方法后,Arrays 类大幅下降。

不过,如果您需要管理数组,那么 Arrays 类可能会有很大帮助。它包含 160 多个方法,其中大多数都使用不同的参数和数组类型进行了重载。如果我们按照方法名进行分组,将会有 21 组,如果我们进一步按功能分组,则只有以下 10 组将涵盖所有 Arrays 类的功能:

  • asList():这会根据提供的数组或逗号分隔的参数列表创建一个 ArrayList 对象。
  • binarySearch():这会搜索一个数组或只搜索它的指定部分(根据索引的范围)。
  • compare()mismatch()equals() 和 < code class="literal">deepEquals():这些比较两个数组或其元素(根据索引的范围)。
  • copyOf()copyOfRange():这将复制所有数组或仅复制指定的(根据索引范围)部分他们。
  • hashcode()deepHashCode():这会根据提供的数组生成哈希码值。
  • toString()deepToString():这将创建一个 String 表示数组。
  • fill()setAll()parallelPrefix() 和 < code class="literal">parallelSetAll():这为数组的每个元素或根据索引范围指定的元素设置一个值(固定或由提供的函数生成)。
  • sort()parallelSort():对数组的元素或部分元素进行排序(根据范围指定指数)。
  • splititerator():这将返回一个 Splititerator 对象,用于并行处理数组或其一部分(根据范围指定指数)。
  • stream():这会生成数组元素或其中一些元素的流(根据索引范围指定);请参阅第 14 章,< em class="italic">Java 标准流.

所有这些方法都很有用,但我们想提请您注意 equals(a1, a2)deepEquals(a1, a2) 方法。它们对数组比较特别有用,因为 array 对象不能实现 equals() 自定义方法,并且使用 equals() 的实现code class="literal">Object 类(仅比较引用)。 equals(a1, a2)deepEquals(a1, a2)方法不仅可以比较 a1a2 引用,还可以使用 equals() 方法来比较元素。这是代码示例,用于演示这些方法的工作原理:

String[] arr1 = {"s1", "s2"};
String[] arr2 = {"s1", "s2"};
System.out.println(arr1.equals(arr2));   //prints: false
System.out.println(Arrays.equals(arr1, arr2));     
                                         //prints: true
System.out.println(Arrays.deepEquals(arr1, arr2));  
                                         //prints: true
String[][] arr3 = {{"s1", "s2"}};
String[][] arr4 = {{"s1", "s2"}};
System.out.println(arr3.equals(arr4));   //prints: false
System.out.println(Arrays.equals(arr3, arr4));     
                                         //prints: false
System.out.println(Arrays.deepEquals(arr3, arr4));
                                         //prints: true

可以看到,Arrays.deepEquals() 每次比较两个相等的数组时返回 true 当一个数组的每个元素都相等时另一个数组的元素在同一 位置,而 Arrays.equals() 方法 做同样的事情,但对于 一维 (1D) 数组 < /a>仅。

ArrayUtils class

org.apache.commons.lang3.ArrayUtils 类补充了 java.util.Arrays通过向数组添加新方法管理工具包以及在以下情况下处理null的能力,否则, NullPointerException 可能会被抛出。要使用此类,您需要将以下依赖项添加到 Maven pom.xml 配置文件中:

<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>3.12.0</version>
</dependency>

ArrayUtils 类有大约 300 个重载方法,可分为以下 12 组:

  • add()addAll()insert():这些向数组中添加元素。
  • clone():这个克隆一个数组,类似于copyOf()方法Arrays 类和 java.lang.System.arraycopy() 方法
  • getLength():当数组本身为 null 时,返回一个数组长度或 0
  • hashCode():计算一个数组的哈希值,包括嵌套数组。
  • contains()indexOf()lastIndexOf():这些搜索一个数组。
  • isSorted()isEmptyisNotEmpty():这些检查数组和句柄 null.
  • isSameLength()isSameType():这些比较数组。
  • nullToEmpty():这会将 null 数组转换为空数组。
  • remove(), removeAll(), removeElement(), removeElements()removeAllOccurances():这些删除某些或所有元素。
  • reverse()shift()shuffle()和< code class="literal">swap():这些改变数组元素的顺序。
  • subarray():根据索引范围提取数组的一部分。
  • toMap(), toObject(), toPrimitive(), toString()toStringArray():这些 转换 将数组转换为另一种类型并处理 null 值。

Objects utilities

本节介绍了以下 两个实用程序:

  • java.util.Objects
  • org.apache.commons.lang3.ObjectUtils

它们在类创建过程中特别有用,因此我们将主要关注与此任务相关的方法。

java.util.Objects class

Objects 类只有 17 个方法,它们都是静态的。让我们在将它们应用到 Person 类时看看其中的一些。假设这个类是集合的一个元素,这意味着它必须实现 equals()hashCode()方法。代码是 图解 在以下片段中:

class Person {
    private int age;
    private String name;
    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }
    public int getAge(){ return this.age; }
    public String getName(){ return this.name; }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null) return false;
        if(!(o instanceof Person)) return false;
        Person = (Person)o;
        return age == person.getAge() &&
                Objects.equals(name, person.getName()); 
    }
    @Override
    public int hashCode(){
        return Objects.hash(age, name);
    }
}

请注意,我们不检查 nullname 属性,因为 Object.equals()<当任何参数为 null 时,/code> 不会中断。它只是完成比较对象的工作。如果其中只有一个是 null,则返回 false。如果两者都是 null,则返回 true

使用 Object.equals() 是实现 equals() 方法的安全方式;但是,如果您需要比较可能是数组的对象,最好使用 Objects.deepEquals() 方法,因为它不仅处理 null,就像 Object.equals() 方法所做的那样,但也比较所有 数组元素的值,甚至如果数组是 多维的,如下所示:

String[][] x1 = {{"a","b"},{"x","y"}};
String[][] x2 = {{"a","b"},{"x","y"}};
String[][] y =  {{"a","b"},{"y","y"}};
System.out.println(Objects.equals(x1, x2));
                                       //prints: false
System.out.println(Objects.equals(x1, y));  
                                       //prints: false
System.out.println(Objects.deepEquals(x1, x2));
                                       //prints: true
System.out.println(Objects.deepEquals(x1, y));
                                      //prints: false

Objects.hash() 方法也处理 null 值。要记住的一件重要事情是,在 equals() 方法中比较的属性列表必须与传递给 Objects.hash 的属性列表相匹配() 作为参数。否则,两个相等的 Person 对象将具有不同的哈希值,这使得基于哈希的集合无法正常工作。

还有一点值得注意的是,还有另外一个hash相关的Objects.hashCode()方法,它只接受一个参数,但是它生成的值不等于<代码 class="literal">Objects.hash() 只有一个参数。例如,请注意以下事项:

System.out.println(Objects.hash(42) ==
               Objects.hashCode(42));    //prints: false
System.out.println(Objects.hash("abc") ==
               Objects.hashCode("abc")); //prints: false

为避免此警告,请始终使用 Objects.hash()

以下代码片段展示了另一个潜在的混淆来源:

System.out.println(Objects.hash(null));      //prints: 0
System.out.println(Objects.hashCode(null));  //prints: 0
System.out.println(Objects.hash(0));         //prints: 31
System.out.println(Objects.hashCode(0));     //prints: 0

如您所见,Objects.hashCode() 方法为 null0,对于某些基于哈希值的 算法,这 可能会出现问题。

静态 <T> int compare (T a, T b, Comparator 是另一种流行的方法,它返回 0 (如果参数相等);否则,它返回 c.compare(a, b) 的结果。它对于实现 Comparable 接口(为自定义对象排序建立自然顺序)非常有用。例如,请注意以下事项:

class Person implements Comparable<Person> {
    private int age;
    private String name;
    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }
    public int getAge(){ return this.age; }
    public String getName(){ return this.name; }
    @Override
    public int compareTo(Person p){
        int result = Objects.compare(name, p.getName(),
                                    Comparator.naturalOrder());
        if (result != 0) { 
           return result;
        }
        return Objects.compare(age, p.getAge(),
                                    Comparator.naturalOrder());
    }
}

这样,您可以通过设置Comparator.reverseOrder()轻松更改排序算法 值或通过添加 Comparator.nullFirst()Comparator.nullLast()

此外,我们在上一节中使用的 Comparator 实现可以通过使用 Objects.compare() 方法变得更加灵活,如下:

class ComparePersons implements Comparator<Person> {
    public int compare(Person p1, Person p2){
        int result = Objects.compare(p1.getName(),
           p2.getName(), Comparator.naturalOrder());
        if (result != 0) { 
           return result;
        }
        return Objects.compare(p1.getAge(), p2.getAge(),
                              Comparator.naturalOrder());
    }
}

最后,我们将要讨论的 Objects 类的最后两个方法是生成对象的字符串表示的方法。当您需要在对象上调用 toString() 方法但不确定对象引用是否为 null。例如,请注意以下事项:

List<String> list = Arrays.asList("s1", null);
for(String e: list){
    //String s = e.toString();  //NullPointerException
}

前面的 示例中,我们知道每个元素的确切值;但是,想象一下将列表作为参数传递给方法的场景。然后,我们被迫写这样的东西:

void someMethod(List<String> list){
    for(String e: list){
        String s = e == null ? "null" : e.toString();
    }

这似乎没什么大不了的。但是这样的代码写了十几遍之后,程序员自然会想到某种实用方法来完成所有这些,那就是 Objects 类的以下两个方法:

  • static String toString(Object o):返回参数不是 toString()的结果class="literal">null 并在参数值为 null 时返回 null
  • static String toString(Object o, String nullDefault):返回第一个参数调用toString()的结果不是null,当第一个参数值为null<时返回第二个nullDefault参数值/代码>。

以下代码片段演示了这两种方法:

List<String> list = Arrays.asList("s1", null);
for(String e: list){
    String s = Objects.toString(e);
    System.out.print(s + " ");          //prints: s1 null
}
for(String e: list){
    String s = Objects.toString(e, "element was null");
    System.out.print(s + " ");        
                                  //prints: s1 element was null
}

截至编写时,Objects类有17个方法。我们建议您 熟悉它们,以避免在已经存在相同实用程序的情况下编写自己的实用程序。

ObjectUtils class

上一节的最后一个语句适用于org.apache.commons.lang3。 Apache Commons 库的 ObjectUtils 类,它补充了 java.util.Objects 类中描述的方法前一节。本书的范围和分配的大小不允许详细回顾 ObjectUtils 类下的所有方法,因此我们将根据它们的相关功能分组简要介绍它们.要使用此类,您需要将以下依赖项添加到 Maven pom.xml 配置文件中:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

ObjectUtils类的所有方法可以分为七组,如下:

  • 对象克隆方法
  • 支持比较两个对象的方法
  • notEqual() 方法,比较两个对象的不等式,其中一个对象或两个对象都可能为 null
  • 几个 identityToString() 方法生成所提供对象的 String 表示,就好像由 生成toString(),这是 Object 基类的默认方法,并且可以选择将其附加到另一个对象
  • allNotNull()anyNotNull() 方法,用于分析 null 的对象数组
  • firstNonNull()defaultIfNull() 方法,它们分析对象数组并返回第一个 not-null 对象或默认值
  • max()min()median()mode() 方法,分析一组 对象并返回对应于 的对象方法名

The java.time package

java.time 包及其子包中有很多类。它们被引入作为处理日期和时间的其他(旧包)的替代品。新类是线程安全的(因此,更适合多线程处理),同样重要的是它们的设计更加一致且更易于理解。此外,新实施遵循 国际标准化组织 (ISO) 标准至于日期和时间格式,但也允许使用任何其他自定义格式。

我们将描述以下五个主要类并演示如何使用它们:

  • java.time.LocalDate
  • java.time.LocalTime
  • java.time.LocalDateTime
  • java.time.Period
  • java.time.Duration

java.time 包的所有这些和其他类,以及它的子包,都具有涵盖所有实际案例的丰富功能。但我们不会讨论所有这些。我们将只介绍基础知识和最流行的用例。

LocalDate class

LocalDate 不带时间。它表示 ISO 8601 格式的 日期(yyyy-MM-dd ) 并显示在以下代码片段中:

System.out.println(LocalDate.now()); 
                    //prints: current date in format yyyy-MM-dd

这是撰写本文时该位置的当前日期。该值是从计算机时钟中获取的。同样,您可以使用该静态 now(ZoneId zone) 方法获取任何其他时区的当前日期。 ZoneId 对象可以使用静态 ZoneId.of(String zoneId) 方法构造,其中 String zoneIdZoneId.getAvailableZoneIds() 方法返回的任何字符串值,如以下代码片段所示:

Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for(String zoneId: zoneIds){
    System.out.println(zoneId);
}

前面的 代码 打印近 600 个时区 标识符 (ID)。这里有几个:

Asia/Aden
Etc/GMT+9
Africa/Nairobi
America/Marigot
Pacific/Honolulu
Australia/Hobart
Europe/London
America/Indiana/Petersburg
Asia/Yerevan
Europe/Brussels
GMT
Chile/Continental
Pacific/Yap
CET
Etc/GMT-1
Canada/Yukon
Atlantic/St_Helena
Libya
US/Pacific-New
Cuba
Israel
GB-Eire
GB
Mexico/General
Universal
Zulu
Iran
Navajo
Egypt
Etc/UTC
SystemV/AST4ADT
Asia/Tokyo

让我们尝试 使用 "Asia/Tokyo",例如,如下:

ZoneId = ZoneId.of("Asia/Tokyo");
System.out.println(LocalDate.now(zoneId)); 
           //prints: current date in Tokyo in format yyyy-MM-dd

LocalDate 对象可以使用以下方法表示过去或将来的任何日期:

  • LocalDate parse(CharSequence text):从 ISO 8601 格式的字符串(yyyy-MM-dd)。
  • LocalDate parse(CharSequence text, DateTimeFormatter formatter):这会从 DateTimeFormatter 对象指定格式的字符串构造一个对象,拥有丰富的模式系统和许多预定义的格式——以下是其中的一些:
    • BASIC_ISO_DATE——例如,20111203
    • ISO_LOCAL_DATE ISO——例如,2011-12-03
    • ISO_OFFSET_DATE——例如,2011-12-03+01:00
    • ISO_DATE——例如,2011-12-03+01:00; 2011-12-03
    • ISO_LOCAL_TIME——例如,10:15:30
    • ISO_OFFSET_TIME——例如,10:15:30+01:00
    • ISO_TIME——例如,10:15:30+01:00; 10:15:30
    • ISO_LOCAL_DATE_TIME——例如,2011-12-03T10:15:30
  • LocalDate of(int year, int month, int dayOfMonth):从年、月、日构造一个对象。
  • LocalDate of(int year, Month, int dayOfMonth):这会从年、月(枚举常量)和日构造一个对象。
  • LocalDate ofYearDay(int year, int dayOfYear):这会根据年份和年份构造一个对象。

以下代码片段演示了上述项目符号中列出的方法:

LocalDate lc1 = LocalDate.parse("2023-02-23");
System.out.println(lc1);           //prints: 2023-02-23
LocalDate lc2 = LocalDate.parse("20230223",
                     DateTimeFormatter.BASIC_ISO_DATE);
System.out.println(lc2);           //prints: 2023-02-23
DateTimeFormatter frm =
              DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate lc3 =  LocalDate.parse("23/02/2023", frm);
System.out.println(lc3);           //prints: 2023-02-23
LocalDate lc4 =  LocalDate.of(2023, 2, 23);
System.out.println(lc4);           //prints: 2023-02-23
LocalDate lc5 =  LocalDate.of(2023, Month.FEBRUARY, 23);
System.out.println(lc5);           //prints: 2023-02-23
LocalDate lc6 = LocalDate.ofYearDay(2023, 54);
System.out.println(lc6);           //prints: 2023-02-23

一个LocalDate对象可以提供各种值,如如下代码所示片段:

LocalDate lc = LocalDate.parse("2023-02-23");
System.out.println(lc);                  //prints: 2023-02-23
System.out.println(lc.getYear());        //prints: 2023
System.out.println(lc.getMonth());       //prints: FEBRUARY
System.out.println(lc.getMonthValue());  //prints: 2
System.out.println(lc.getDayOfMonth());  //prints: 23
System.out.println(lc.getDayOfWeek());   //prints: THURSDAY
System.out.println(lc.isLeapYear());     //prints: false
System.out.println(lc.lengthOfMonth());  //prints: 28
System.out.println(lc.lengthOfYear());   //prints: 365

LocalDate 对象 可以进行修改,如下所示:

LocalDate lc = LocalDate.parse("2023-02-23");
System.out.println(lc.withYear(2024));     //prints: 2024-02-23
System.out.println(lc.withMonth(5));       //prints: 2023-05-23
System.out.println(lc.withDayOfMonth(5));  //prints: 2023-02-05
System.out.println(lc.withDayOfYear(53));  //prints: 2023-02-22
System.out.println(lc.plusDays(10));       //prints: 2023-03-05
System.out.println(lc.plusMonths(2));      //prints: 2023-04-23
System.out.println(lc.plusYears(2));       //prints: 2025-02-23
System.out.println(lc.minusDays(10));      //prints: 2023-02-13
System.out.println(lc.minusMonths(2));     //prints: 2022-12-23
System.out.println(lc.minusYears(2));      //prints: 2021-02-23

可以比较 LocalDate 对象,如下所示:

LocalDate lc1 = LocalDate.parse("2023-02-23");
LocalDate lc2 = LocalDate.parse("2023-02-22");
System.out.println(lc1.isAfter(lc2));       //prints: true
System.out.println(lc1.isBefore(lc2));      //prints: false

LocalDate 类中有 很多 其他有用的方法。如果您必须使用日期,我们建议您阅读应用程序编程接口 (该类的API)和java.time包及其子包的其他类。

LocalTime class

LocalTime 包含没有日期的时间。它具有与 LocalDate 类的方法类似的 方法。下面是如何创建 LocalTime 类的对象:

System.out.println(LocalTime.now()); //prints: 21:15:46.360904
ZoneId = ZoneId.of("Asia/Tokyo");
System.out.println(LocalTime.now(zoneId)); 
                                     //prints: 12:15:46.364378
LocalTime lt1 =  LocalTime.parse("20:23:12");
System.out.println(lt1);                     //prints: 20:23:12
LocalTime lt2 = LocalTime.of(20, 23, 12);
System.out.println(lt2);                     //prints: 20:23:12

时间值的每个分量都可以从 LocalTime 对象中提取,如下所示:

LocalTime lt2 =  LocalTime.of(20, 23, 12);
System.out.println(lt2);                     //prints: 20:23:12
System.out.println(lt2.getHour());           //prints: 20
System.out.println(lt2.getMinute());         //prints: 23
System.out.println(lt2.getSecond());         //prints: 12
System.out.println(lt2.getNano());           //prints: 0

LocalTime类的object可以修改,如下:

LocalTime lt2 = LocalTime.of(20, 23, 12);
System.out.println(lt2.withHour(3));      //prints: 03:23:12
System.out.println(lt2.withMinute(10));   //prints: 20:10:12
System.out.println(lt2.withSecond(15));   //prints: 20:23:15
System.out.println(lt2.withNano(300)); 
                                   //prints: 20:23:12.000000300
System.out.println(lt2.plusHours(10));    //prints: 06:23:12
System.out.println(lt2.plusMinutes(2));   //prints: 20:25:12
System.out.println(lt2.plusSeconds(2));   //prints: 20:23:14
System.out.println(lt2.plusNanos(200));
                                   //prints: 20:23:12.000000200
System.out.println(lt2.minusHours(10));   //prints: 10:23:12
System.out.println(lt2.minusMinutes(2));  //prints: 20:21:12
System.out.println(lt2.minusSeconds(2));  //prints: 20:23:10
System.out.println(lt2.minusNanos(200));
                                   //prints: 20:23:11.999999800

并且LocalTime类的两个对象也可以进行比较,如下:

LocalTime lt2 =  LocalTime.of(20, 23, 12);
LocalTime lt4 =  LocalTime.parse("20:25:12");
System.out.println(lt2.isAfter(lt4));       //prints: false
System.out.println(lt2.isBefore(lt4));      //prints: true

LocalTime 类中还有 许多其他有用的方法。如果您 必须使用日期,我们建议您阅读该类的 API 和 java.time 的其他类包及其子包。

LocalDateTime class

LocalDateTime 包含日期和时间,并具有所有方法LocalDateLocalTime 类都有,这里不再赘述。我们将只展示如何创建 LocalDateTime 类的对象,如下所示:

System.out.println(LocalDateTime.now());       
                     //prints: 2019-03-04T21:59:00.142804
ZoneId = ZoneId.of("Asia/Tokyo");
System.out.println(LocalDateTime.now(zoneId)); 
                    //prints: 2019-03-05T12:59:00.146038
LocalDateTime ldt1 = 
           LocalDateTime.parse("2020-02-23T20:23:12");
System.out.println(ldt1);  //prints: 2020-02-23T20:23:12
DateTimeFormatter formatter =
     DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
LocalDateTime ldt2 =
  LocalDateTime.parse("23/02/2020 20:23:12", formatter);
System.out.println(ldt2);  //prints: 2020-02-23T20:23:12
LocalDateTime ldt3 = 
               LocalDateTime.of(2020, 2, 23, 20, 23, 12);
System.out.println(ldt3);  //prints: 2020-02-23T20:23:12
LocalDateTime ldt4 =
  LocalDateTime.of(2020, Month.FEBRUARY, 23, 20, 23, 12);
System.out.println(ldt4);  //prints: 2020-02-23T20:23:12
LocalDate ld = LocalDate.of(2020, 2, 23);
LocalTime lt = LocalTime.of(20, 23, 12);
LocalDateTime ldt5 = LocalDateTime.of(ld, lt);
System.out.println(ldt5); //prints: 2020-02-23T20:23:12

LocalDateTime 类中还有许多其他有用的方法。如果您必须使用 日期,我们建议您阅读此 类和java.time 包及其子包。

Period and Duration classes

java.time.Periodjava.time.Duration 类旨在包含< /a> 时间,如 所述:

  • Period 对象包含以年、月和日为单位的时间量。
  • Duration 对象包含以小时、分钟、秒和纳秒为单位的时间量。

以下代码片段演示了使用 LocalDateTime 类创建和使用它们的方法,但 LocalDate 中存在相同的方法(对于 Period) 和 LocalTime(对于 Duration)类:

LocalDateTime ldt1 = LocalDateTime.parse("2023-02-23T20:23:12");
LocalDateTime ldt2 = ldt1.plus(Period.ofYears(2));
System.out.println(ldt2);      //prints: 2025-02-23T20:23:12

以下方法的工作方式与 LocalTime 类的方法相同:

LocalDateTime ldt = LocalDateTime.parse("2023-02-23T20:23:12");
ldt.minus(Period.ofYears(2));
ldt.plus(Period.ofMonths(2));
ldt.minus(Period.ofMonths(2));
ldt.plus(Period.ofWeeks(2));
ldt.minus(Period.ofWeeks(2));
ldt.plus(Period.ofDays(2));
ldt.minus(Period.ofDays(2));
ldt.plus(Duration.ofHours(2));
ldt.minus(Duration.ofHours(2));
ldt.plus(Duration.ofMinutes(2));
ldt.minus(Duration.ofMinutes(2));
ldt.plus(Duration.ofMillis(2));
ldt.minus(Duration.ofMillis(2));

其他方法 a>创建和使用 Period 对象在以下代码片段中演示:

LocalDate ld1 =  LocalDate.parse("2023-02-23");
LocalDate ld2 =  LocalDate.parse("2023-03-25");
Period = Period.between(ld1, ld2);
System.out.println(period.getDays());       //prints: 2
System.out.println(period.getMonths());     //prints: 1
System.out.println(period.getYears());      //prints: 0
System.out.println(period.toTotalMonths()); //prints: 1
period = Period.between(ld2, ld1);
System.out.println(period.getDays());       //prints: -2

Duration 对象可以类似地创建和使用,如下面的代码片段所示:

LocalTime lt1 =  LocalTime.parse("10:23:12");
LocalTime lt2 =  LocalTime.parse("20:23:14");
Duration = Duration.between(lt1, lt2);
System.out.println(duration.toDays());     //prints: 0
System.out.println(duration.toHours());    //prints: 10
System.out.println(duration.toMinutes());  //prints: 600
System.out.println(duration.toSeconds());  //prints: 36002
System.out.println(duration.getSeconds()); //prints: 36002
System.out.println(duration.toNanos());    
                                       //prints: 36002000000000
System.out.println(duration.getNano());    //prints: 0.

还有很多其他 a>PeriodDuration 类中的有用方法。如果您必须使用日期,我们建议您阅读该类的 API 以及 java.time 包及其子包的其他类。

Period of day

Java 16 包含一种 新时间格式,该格式将一天的时间段显示为AM 早上等。以下两个方法演示了 DateTimeFormatter.ofPattern() 方法与 LocalDateTime 的用法LocalTime 类:

void periodOfDayFromDateTime(String time, String pattern){
   LocalDateTime date = LocalDateTime.parse(time);
   DateTimeFormatter frm =
            DateTimeFormatter.ofPattern(pattern);
   System.out.print(date.format(frm));
} 
void periodOfDayFromTime(String time, String pattern){
   LocalTime date = LocalTime.parse(time);
   DateTimeFormatter frm =
           DateTimeFormatter.ofPattern(pattern);
   System.out.print(date.format(frm));
}

下面的代码演示了"ha""h B" 模式:

periodOfDayFromDateTime("2023-03-23T05:05:18.123456", 
           "MM-dd-yyyy h a"); //prints: 03-23-2023 5 AM
periodOfDayFromDateTime("2023-03-23T05:05:18.123456", 
       "MM-dd-yyyy h B"); //prints: 03-23-2023 5 at night
periodOfDayFromDateTime("2023-03-23T06:05:18.123456", 
                  "h B");   //prints: 6 in the morning
periodOfDayFromTime("11:05:18.123456", "h B"); 
                            //prints: 11 in the morning
periodOfDayFromTime("12:05:18.123456", "h B"); 
                          //prints: 12 in the afternoon
periodOfDayFromTime("17:05:18.123456", "h B"); 
                          //prints: 5 in the afternoon
periodOfDayFromTime("18:05:18.123456", "h B"); 
                          //prints: 6 in the evening
periodOfDayFromTime("20:05:18.123456", "h B"); 
                          //prints: 8 in the evening
periodOfDayFromTime("21:05:18.123456", "h B"); 
                         //prints: 9 at night

您可以 使用 "ha""h B" 模式来制作时间呈现更人性化。

Summary

本章向您介绍了 Java 集合框架及其三个主要接口:ListSet地图。讨论了每个接口,并用其中一个实现类演示了它的方法。泛型也得到了解释和演示。必须实现 equals()hashCode() 方法才能使对象能够被 Java 处理正确收藏。

CollectionsCollectionUtils 实用程序类具有许多有用的收集处理方法,并在示例中与 数组ArrayUtilsObjectsObjectUtils 类。

java.time 包的类方法允许管理时间/日期值,并在特定的实用代码片段中进行了演示。

您现在可以在程序中使用我们在本章中讨论的所有主要数据结构。

在下一章中,我们将概述 JCL 和一些外部库,包括那些支持测试的库。具体来说,我们将探索 org.junitorg.mockitoorg.apache。 log4jorg.slf4jorg.apache.commons 包及其子包。

Quiz

  1. 什么是 Java 集合框架?选择所有符合条件的:
    1. 一系列框架
    2. java.util包的类和接口
    3. ListSetMap接口
    4. 实现集合数据结构的类和接口
  2. 集合中的泛型是什么意思?选择所有符合条件的:
    1. 集合结构定义
    2. 元素类型声明
    3. 类型泛化
    4. 一种提供编译时安全性的机制
  3. of() 工厂方法的集合有什么限制?选择所有符合条件的:
    1. 它们不允许 null 元素。
    2. 它们不允许将元素添加到已初始化的集合中。
    3. 它们不允许修改与已初始化集合相关的元素。
  4. java.lang.Iterable 接口的实现允许什么?选择所有符合条件的:
    1. 它允许一个一个地访问集合的元素。
    2. 它允许在 FOR 语句中使用集合。
    3. 它允许在 WHILE 语句中使用集合。
    4. 它允许在 DO...WHILE 语句中使用集合。
  5. java.util.Collection 接口的实现允许什么?选择所有符合条件的:
    1. 添加到另一个集合中的元素集合
    2. 从属于另一个集合元素的对象集合中移除
    3. 仅修改集合中属于另一个集合的元素
    4. 从不属于另一个集合的对象集合中删除
  6. 选择与 List 接口方法有关的所有正确语句:
    1. z get(int index):返回列表中指定位置的元素。
    2. E remove(int index):删除列表中指定位置的元素;它返回被移除的元素。
    3. 静态列表<E> copyOf(Collection coll) :这将返回一个不可修改的 List 接口,其中包含给定 Collection 接口的元素并保持他们的秩序。
    4. int indexOf(Object o):返回指定元素在列表中的位置。
  7. 选择与 Set 接口方法有关的所有正确语句:
    1. E get(int index):返回列表中指定位置的元素。
    2. E remove(int index):删除列表中指定位置的元素;它返回被移除的元素。
    3. 静态集合<E> copyOf(Collection coll) :这将返回一个不可修改的 Set 接口,其中包含给定 Collection 接口的元素.
    4. int indexOf(Object o):返回指定元素在列表中的位置。
  8. 选择与 Map 接口方法相关的所有正确语句:
    1. int size():返回map中存储的键值对的数量;当 isEmpty() 方法返回 true 时,该方法返回 0
    2. V remove(Object key):这会从映射中移除键和值;如果没有这样的键或值为 null,则返回 valuenull >.
    3. default boolean remove(Object key, Object value):如果映射中存在这样的键值对,则删除该键值对;如果值被删除,则返回 true
    4. default boolean replace(K key, V oldValue, V newValue):这会将 oldValue 值替换为 oldValue 值,则提供 class="literal">newValue 值——它返回 true 如果 oldValue 值被替换;否则,它返回 false
  9. 选择与 Collectionsstatic void sort(List<T> list, Comparator<T>comparator) 方法有关的所有正确语句> 类:
    1. 如果列表元素实现了 Comparable 接口,它将对列表的自然顺序进行排序。
    2. 它根据提供的 Comparator 对象对列表的顺序进行排序。
    3. 如果列表元素实现 Comparable 接口,它会根据提供的 Comparator 对象对列表的顺序进行排序。
    4. 它根据提供的 Comparator 对象对列表的顺序进行排序,而不管列表元素是否实现 Comparable 接口。< /li>
  10. 执行以下代码的结果是什么?
    List<String> list1 = Arrays.asList("s1","s2", "s3"); 列表<字符串> list2 = Arrays.asList("s3", "s4"); Collections.copy(list1, list2); System.out.println(list1);    
    1. [s1, s2, s3, s4]
    2. [s3, s4, s3]
    3. [s1, s2, s3, s3, s4]
    4. [s3, s4]
  11. CollectionUtils 类方法的功能是什么?选择所有符合条件的:
    1. 它匹配 Collections 类方法的功能,但是通过处理 null
    2. 它补充了 Collections 类方法的功能
    3. 它以 Collections 类方法不具备的方式搜索、处理和比较 Java 集合
    4. 它复制了 Collections 类方法的功能
  12. 执行以下代码的结果是什么?
    Integer[][] ar1 = {{42}}; 整数[][] ar2 = {{42}}; System.out.print(Arrays.equals(ar1, ar2) + ""); System.out.println(Arrays.deepEquals(arr3, arr4)); 
    1. false true
    2. 真假
  13. 执行以下代码的结果是什么?
    String[] arr1 = { "s1", "s2" }; 字符串[] arr2 = { null }; 字符串[] arr3 = null; System.out.print(ArrayUtils.getLength(arr1) + ""); System.out.print(ArrayUtils.getLength(arr2) + ""); System.out.print(ArrayUtils.getLength(arr3) + ""); System.out.print(ArrayUtils.isEmpty(arr2) + ""); System.out.print(ArrayUtils.isEmpty(arr3));
    1. 1 2 0 false true
    2. 2 1 1 false true
    3. 2 1 0 false true
    4. 2 1 0 真假
  14. 执行以下代码的结果是什么?
     String str1 = ""; 字符串 str2 = null; System.out.print((Objects.hash(str1) ==                    Objects.hashCode(str2)) + ""); System.out.print(Objects.hash(str1) + ""); System.out.println(Objects.hashCode(str2) + ""); 
    1. 真 0 0
    2. 错误
    3. false -1 0
    4. false 31 0
  15. 执行以下代码的结果是什么?
    String[] arr = {"c", "x", "a"}; System.out.print(ObjectUtils.min(arr) + ""); System.out.print(ObjectUtils.median(arr) + ""); System.out.println(ObjectUtils.max(arr));
    1. c x a
    2. a c x
    3. x c a
    4. a x c
  16. 执行以下代码的结果是什么?
    LocalDate lc = LocalDate.parse("1900-02-23"); System.out.println(lc.withYear(21)); 
    1. 1921-02-23
    2. 21-02-23
    3. 0021-02-23
    4. 错误
  17. 执行以下代码的结果是什么?
    LocalTime lt2 = LocalTime.of(20, 23, 12); System.out.println(lt2.withNano(300));      
    1. 20:23:12.000000300
    2. 20:23:12.300
    3. 20:23:12:300
    4. 错误
  18. 执行以下代码的结果是什么?
    LocalDate ld = LocalDate.of(2020, 2, 23); LocalTime lt = LocalTime.of(20, 23, 12); LocalDateTime ldt = LocalDateTime.of(ld, lt); System.out.println(ldt);                
    1. 2020-02-23 20:23:12
    2. 2020-02-23T20:23:12
    3. 2020-02-23:20:23:12
    4. 错误
  19. 执行以下代码的结果是什么?
    LocalDateTime ldt =               LocalDateTime.parse("2020-02-23T20:23:12"); System.out.print(ldt.minus(Period.ofYears(2)) + ""); System.out.print(ldt.plus(Duration.ofMinutes(12)) + " "); System.out.println(ldt);
    1. 2020-02-23T20:23:12 2020-02-23T20:23:12
    2. 2020-02-23T20:23:12 2020-02-23T20:35:12
    3. 2018-02-23T20:23:12 2020-02-23T20:35:12 2020-02-23T20:23:12
    4. 2018-02-23T20:23:12 2020-02-23T20:35:12 2018-02-23T20:35:12