JAVA从零开始10_ArrayList

本文深入探讨了Java集合框架,重点关注ArrayList和泛型。文章介绍了ArrayList的基本概念、常用方法以及如何利用泛型提高代码安全性与重用性。此外,还讨论了包装类的作用以及在何时使用System.exit(0)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Java中的集合

在 Java 中,集合(Collection)是一种用于存储和操作数据的数据结构。集合框架(Collection Framework)为 Java 提供了一套丰富的预定义类和接口,使得开发人员能够方便地处理各种数据集合。Java 集合框架主要包括以下几个部分:

  1. 接口:集合框架定义了一些重要的接口,如 Collection、List、Set、Queue、Deque、Map 等。这些接口规定了各种集合类型的基本行为和操作。

  2. 实现类:集合框架提供了各种接口的具体实现类。这些实现类包括:ArrayList、LinkedList、HashSet、LinkedHashSet、TreeSet、PriorityQueue、ArrayDeque、HashMap、LinkedHashMap、TreeMap 等。开发人员可以根据具体需求选择合适的实现类。

  3. 算法:集合框架还提供了一些通用的算法,如排序、查找、比较等。这些算法主要通过 Collections 类提供,可以对各种集合进行操作。

以下是一些常见的集合类型:

  1. List:列表是一种有序的集合,可以包含重复的元素。列表的元素可以通过索引访问。常用的实现类有 ArrayList(基于动态数组)和 LinkedList(基于双向链表)。

  2. Set:集合是一种无序的集合,不包含重复的元素。常用的实现类有 HashSet(基于哈希表,无序)、LinkedHashSet(基于哈希表和链表,保持插入顺序)和 TreeSet(基于红黑树,有序)。

  3. Queue:队列是一种特殊的线性表,遵循先进先出(FIFO)原则。常用的实现类有 LinkedList(双向链表实现,也可作为双端队列使用)和 PriorityQueue(基于优先级的队列)。

  4. Deque:双端队列(Double Ended Queue)是一种具有队列和栈性质的数据结构。它允许在队列的两端插入和删除元素。常用的实现类有 ArrayDeque(基于动态数组)和 LinkedList(基于双向链表)。

  5. Map:映射是一种将键映射到值的数据结构。一个映射不能包含重复的键,每个键最多只能映射到一个值。常用的实现类有 HashMap(基于哈希表,无序)、LinkedHashMap(基于哈希表和链表,保持插入顺序)和 TreeMap(基于红黑树,按键的自然顺序或者自定义的比较器顺序进行排序)。

在 Java 集合框架中,有一些集合类可以保持插入顺序:

  1. LinkedHashSet:这是一个 Set 接口的实现类,基于哈希表和链表。它维护了一个运行于所有条目的双向链表,从而保持了插入顺序。在遍历 LinkedHashSet 时,元素将按照它们插入的顺序返回。

  2. LinkedHashMap:这是一个 Map 接口的实现类,同样基于哈希表和链表。它也维护了一个运行于所有条目的双向链表。当遍历 LinkedHashMap 时,键值对将按照它们插入的顺序返回。

这些集合类在需要保持元素顺序的情况下非常有用。例如,在实现 LRU(最近最少使用)缓存策略时,可以使用 LinkedHashMap 来有效地保持访问顺序。


二、集合和数组

集合(Collections)和数组(Arrays)都是用于存储和操作数据的数据结构,但它们之间存在一些显著的区别:

  1. 大小:数组在创建时大小是固定的,无法在运行时改变。相比之下,集合是动态的,可以在运行时根据需要增加或减少元素。

  2. 类型:数组可以存储基本数据类型(如 int、float、double 等)和对象。而集合只能存储对象(实际上是对象的引用)。对于基本数据类型,需要使用相应的包装类(如 Integer、Float、Double 等)。

  3. 功能:数组提供的操作相对较少,主要包括访问、修改和遍历元素。集合框架提供了丰富的预定义类和接口,以及一系列的算法和操作,如添加、删除、查找、排序等。

  4. 性能:数组在内存中是连续的,访问速度快。而集合在内存中的存储方式取决于具体实现类(如 ArrayList、LinkedList、HashSet 等),性能可能有所不同。在某些情况下,数组可能更适合于性能敏感的操作。

  5. 灵活性:集合提供了多种数据结构(如 List、Set、Map、Queue 等),每种数据结构具有不同的特点和用途。数组只提供了一种线性的、固定大小的数据结构。

总之,集合和数组在使用和功能上有很大差别。在选择使用哪种数据结构时,需要根据具体需求和场景进行权衡。例如,对于需要动态调整大小的数据集,使用集合会更方便;而对于需要快速访问元素的场景,数组可能是更好的选择。


三、ArrayList

ArrayList 是一个实现了 List 接口的可调整大小的数组。它允许动态地添加和删除元素,并提供了随机访问元素的能力。ArrayList 的内部实现基于一个动态增长的对象数组。

下面是 ArrayList 的一些常用方法:

  1. add(E element):向 ArrayList 的末尾添加一个元素。
  2. add(int index, E element):在指定的索引位置插入一个元素,原索引位置及之后的元素右移。
  3. remove(int index):删除指定索引位置的元素,并返回该元素。原索引位置之后的元素左移。
  4. remove(Object obj):删除 ArrayList 中第一个匹配到的指定元素。
  5. get(int index):返回指定索引位置的元素。
  6. set(int index, E element):替换指定索引位置的元素。
  7. size():返回 ArrayList 中的元素数量。
  8. isEmpty():检查 ArrayList 是否为空,即元素数量为0。
  9. contains(Object obj):检查 ArrayList 是否包含指定的元素。
  10. indexOf(Object obj):返回指定元素在 ArrayList 中第一次出现的索引,如果不存在则返回 -1。
  11. lastIndexOf(Object obj):返回指定元素在 ArrayList 中最后一次出现的索引,如果不存在则返回 -1。
  12. clear():移除 ArrayList 中的所有元素。
  13. toArray():将 ArrayList 转换为一个对象数组。
  14. subList(int fromIndex, int toIndex):返回指定索引范围内的子列表(不包含 toIndex)。
  15. ensureCapacity(int minCapacity):确保 ArrayList 的内部数组具有至少指定的最小容量。此操作可以在添加大量元素之前调用以减少扩容操作次数,从而提高性能。
  16. trimToSize():将 ArrayList 的容量调整为当前元素的数量。此操作可以在移除大量元素后调用以释放不再使用的内存。
  17. iterator():返回一个 Iterator 对象,用于遍历 ArrayList 中的元素。
  18. listIterator():返回一个 ListIterator 对象,用于双向遍历 ArrayList 中的元素。
  19. addAll(Collection<? extends E> c):将一个集合中的所有元素添加到 ArrayList 的末尾。
  20. addAll(int index, Collection<? extends E> c):将一个集合中的所有元素插入到 ArrayList 的指定索引位置。
  21. removeAll(Collection<?> c):从 ArrayList 中移除与指定集合中的元素相匹配的所有元素。
  22. retainAll(Collection<?> c):仅保留 ArrayList 中与指定集合中的元素相匹配的元素。
  23. containsAll(Collection<?> c):检查 ArrayList 是否包含指定集合中的所有元素。

需要注意的是,ArrayList 的许多操作在增加或删除元素时,可能需要对数组进行扩容或收缩,这会影响性能。在已知容量的情况下,可以使用带有初始容量参数的构造方法创建 ArrayList 以提高性能。

ArrayList<String> list = new ArrayList<>(initialCapacity);

ArrayList 是非线程安全的。如果需要在多线程环境中使用 ArrayList,可以使用 Collections.synchronizedList 方法创建一个同步的列表。

List<String> synchronizedList = Collections.synchronizedList(new ArrayList<String>());

四、泛型

泛型(Generics)是 Java 语言中的一种编程特性,它允许在类、接口和方法中使用类型参数。泛型的主要目的是提供类型安全和代码重用。使用泛型,可以编写更通用、可复用的代码,同时避免类型转换带来的运行时错误。

泛型的作用:

  1. 类型安全:泛型在编译时检查类型,确保程序在运行时不会出现类型转换错误。
  2. 代码重用:通过泛型,可以编写一段适用于多种类型的代码,而无需为每个类型重复编写相同逻辑。
  3. 消除类型强制转换:使用泛型后,不需要显式地进行类型强制转换,代码更简洁易读。

使用场景:

  1. 泛型类和接口:泛型可以用于定义通用的类和接口,如 ArrayList,其中 E 是类型参数。这样一来,ArrayList 可以用于存储任意类型的对象,同时保持类型安全。
  2. 泛型方法:泛型可以应用于方法,使方法具有更广泛的适用性。例如,可以使用泛型方法来实现一个通用的比较器,适用于任何实现了 Comparable 接口的类。
  3. 泛型限定(有界类型参数):通过为泛型参数指定上限,可以限制泛型的类型范围。例如,<T extends Comparable> 表示类型参数 T 必须实现 Comparable 接口。
  4. 通配符(?):泛型通配符允许在某些情况下使用未知类型的泛型。例如,List<?> 表示一个未知类型的 List。通过使用通配符,可以编写更灵活的代码。

总之,泛型提供了一种在编译时确保类型安全的机制,同时允许编写可复用的代码。在 Java 中,泛型广泛应用于集合框架、多线程工具类以及许多其他常用库和框架中。


五、包装类

包装类(Wrapper Class)是 Java 中的一种特殊类,它用于将基本数据类型(如 int、float、double 等)封装为对象。Java 提供了一组预定义的包装类,分别对应于八个基本数据类型。这些包装类是:

Byte(对应 byte)
Short(对应 short)
Integer(对应 int)
Long(对应 long)
Float(对应 float)
Double(对应 double)
Character(对应 char)
Boolean(对应 boolean)

包装类的作用:

  1. 将基本类型转换为对象:在某些情况下,如集合框架、泛型等,需要使用对象而不是基本类型。包装类允许将基本类型包装为对象,使其可以在这些情况下使用。
  2. 类型转换和进制转换:包装类提供了一些实用方法,可以将基本类型和字符串之间进行转换,以及进行不同进制之间的转换。
  3. 常量和实用方法:包装类还包含一些与对应基本类型相关的常量和实用方法,如最大值、最小值等。

使用场景:

  1. 集合框架:集合框架(如 ArrayList、HashMap 等)只能存储对象,不能直接存储基本类型。通过使用包装类,可以将基本类型封装为对象,从而将其存储在集合中。
  2. 泛型:由于泛型不支持基本类型,需要使用包装类将基本类型转换为对象,以便在泛型类和方法中使用。
  3. 处理基本类型数据:在某些情况下,可能需要对基本类型数据进行操作,如类型转换、进制转换等。包装类提供了一系列实用方法,方便处理这些任务。

自 Java 5 以来,引入了自动装箱(Autoboxing)和自动拆箱(Unboxing)机制,使得在基本类型和包装类之间的转换变得更加方便。当需要将基本类型转换为对应的包装类时,会自动进行装箱操作;当需要将包装类转换为基本类型时,会自动进行拆箱操作。这使得在实际编程中使用包装类更加简便。


六、标签

标签(Label)是 Java 语言中的一个特性,它允许为循环或块语句指定一个标识符,以便与 break 和 continue 语句一起使用。标签是 Java 语言的一部分,因此是由 JDK 提供的。

标签主要用于以下场景:

跳出多层嵌套循环:在嵌套循环中,当满足某个条件时,我们可能希望跳出外层循环。此时,我们可以使用标签与 break 语句一起使用,直接跳出指定的循环。

例如:

outerLoop: for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        if (i + j == 5) {
            System.out.println("Breaking the outer loop.");
            break outerLoop;
        }
    }
}

跳过多层循环的当前迭代:在嵌套循环中,当满足某个条件时,我们可能希望跳过外层循环的当前迭代。此时,我们可以使用标签与 continue 语句一起使用,直接进入指定循环的下一次迭代。

例如:

outerLoop: for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        if (i + j == 5) {
            System.out.println("Skipping the current iteration of the outer loop.");
            continue outerLoop;
        }
    }
}

需要注意的是,标签通常只在复杂的循环结构中使用,以提高代码的可读性。在简单的循环结构中,通常不需要使用标签。过度使用标签可能会导致代码变得难以理解和维护。

System.exit(0)

在 Java 中,System.exit(0) 是一个用于终止当前运行的 Java 虚拟机(JVM)的方法。它是 java.lang.System 类的一个静态方法,接受一个整数参数,表示程序的退出状态。传递给 exit() 方法的整数值通常用于表示程序是正常退出(如 0)还是因为错误而退出(如非 0 值)。

当调用 System.exit(0) 时,JVM 将立即停止执行,即使还有其他活动的线程或未完成的任务。JVM 会在退出前调用所有已注册的关闭钩子(Shutdown Hooks),这些钩子通常用于在程序退出时执行清理操作,例如释放资源或保存数据。

需要注意的是,System.exit(0) 是一种强制退出的方式,应在确保不会影响程序正常执行的情况下谨慎使用。在正常的应用程序中,通常不需要调用 System.exit(0),因为当所有非守护线程执行完毕时,JVM 会自动退出。

System.exit(0) 在某些特殊场景中可能会被使用,例如:

  1. 命令行工具:当编写一个命令行工具时,可以使用 System.exit(0) 在完成任务后立即退出程序。此外,还可以根据执行结果使用不同的退出状态码,以便调用者了解程序是否成功执行。

  2. 异常处理:在某些情况下,当程序遇到无法处理或无法恢复的异常时,可能希望使用 System.exit(0) 退出程序。这可以确保不会继续执行可能导致错误的代码。

  3. 安全应用程序:在涉及安全或加密的应用程序中,如果检测到潜在的安全漏洞或攻击,可以使用 System.exit(0) 立即终止程序,以防止数据泄露或其他安全问题。

  4. 执行时限制:在编写一个有执行时限制的应用程序时,可以使用 System.exit(0) 在超过时限后强制退出程序。

  5. 测试和调试:在测试和调试阶段,可以使用 System.exit(0) 在特定条件下快速退出程序,以检查程序在某些特定情况下的表现。

尽管 System.exit(0) 在某些特殊场景中有用,但在普通应用程序中应避免使用它。正常情况下,应通过合适的程序结构和异常处理来控制程序的执行流程。

退出状态码是一个整数值,表示程序运行结束时的状态。通常情况下,退出状态码遵循一些约定,以便调用者了解程序的执行结果。这些约定可能因操作系统和编程语言的不同而有所差异。在 Java 中,以下是一些常见的退出状态码:

  1. 0:表示程序成功执行并正常结束。这是最常见的退出状态码,表示程序没有遇到任何错误。

  2. 非零正整数:表示程序遇到错误。通常情况下,不同的正整数值表示不同类型的错误。例如,1 可能表示输入错误,2 可能表示文件操作错误等。程序员可以根据实际需求自定义这些错误代码。

  3. 非零负整数:通常用于表示操作系统或虚拟机级别的错误。这些错误通常不是由程序本身引起的,而是由于系统环境或资源问题导致的。例如,内存不足或无法加载某个库文件。

虽然 Java 本身没有强制规定退出状态码的具体值,但遵循一定的约定有助于提高程序的可读性和可维护性。在实际应用中,程序员可以根据需要自定义退出状态码,但应确保代码中的注释或文档清楚地说明每个状态码的含义。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值