本篇参考书籍为JaveEE零基础入门/史胜辉,王春明,沈学华编著.—北京:清华大学出版社,2021.1(2022.8重印) ISBN 978-7-302-56938-1
文章目录
前言
Java的集合类
Java的集合类是一个容器,用来存放Java类的对象,代表一组对象的对象。集合中的这组对象称为集合的元素。集合中的每一个元素都是对象,任何数据类型的对象都可以存放在集合中。本篇主要讲解HashSet、ArrayList以及HashMap类
最基本的接口是Collection接口,常用的接口还有List、Set和Map,其中List和Set都继承自Collection接口
一、集合类
1、Set接口与HashSet类
首先说一下Set接口,它扩展了Collection接口,但它没有定义新的方法。它不允许集合中存在重复的元素,如果用户试图添加重复的元素,该方法将返回false。同时,无法通过索引或插入顺序来确定元素的位置,集合并不保证元素的迭代顺序与其添加顺序相同。
而HashSet类作为Set接口的实现类,将元素存放在散列表中,利用了哈希表(实际上底层由HashMap支持)进行高效的数据存储和检索,它通过每个元素的hashCode()方法来快速定位元素在表中的位置。但是,由于集合中不能存在重复元素,导致在进行添加操作时执行的效率会比较低。同时,HashSet在迭代其元素时,不保证任何特定的顺序,每次遍历可能会得到不同的结果。
简单使用示例:
注意:<String>涉及到后面的泛型,暂时看不懂没关系
// 导入所需的包
import java.util.HashSet;
import java.util.Set;
public class HashSetExample {
public static void main(String[] args) {
// 创建一个HashSet实例,用于存储字符串对象
Set<String> fruits = new HashSet<>();
// 添加元素到HashSet
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
fruits.add("Apple"); // 尝试再次添加相同的元素,由于HashSet不允许重复,所以此操作不会增加元素数量
System.out.println("HashSet size after adding elements: " + fruits.size());
// 使用contains()方法检查元素是否存在
if (fruits.contains("Banana")) {
System.out.println("Banana is in the set.");
} else {
System.out.println("Banana is not in the set.");
}
// 遍历HashSet
System.out.println("\nThe fruits in the set:");
for (String fruit : fruits) {
System.out.println(fruit);
}
}
}
这段代码运行将会:
1、创建一个空的HashSet对象。
2、向集合中添加三个不同的水果名称。
3、尝试添加一个已经存在的水果名称,但由于HashSet的唯一性特征,不会增加集合大小。
4、检查集合中是否包含“Banana”。
5、使用增强for循环遍历并打印集合中的所有元素。由于HashSet是无序的,打印的元素顺序可能与添加顺序不同。
2、List接口与ArrayList类
List接口与Set接口不同的是,它不仅扩展了Collection接口,同时又定义了一些自己的方法,这些方法可归纳为三类:定位方法、搜索方法和ListIterator方法(按索引位置插入和移除元素、通过索引获取元素、查询元素的索引位置、列表的排序以及其他一些针对有序集合的操作。),并且与Set不同的是,List是有序集合,允许有相同的元素。并且,在进行插入操作时,可以控制每个元素的插入位置,还可以使用索引访问List中的元素。
ArrayLsit类是List接口的实现类,采用数组结构存放对象。它的优点是能快速地对集合元素进行随机访问,如果需要经常根据索引位置访问集合中的元素,此时的效率比较高。但是,如果频繁地执行插入或删除操作,会影响效率。因为ArrayList类类似于动态数组,可存放的元素数量会随着插入和删除操作不断地进行调整。此外,它还有一个特点是动态扩容,即当添加的元素超过当前容量时,会自动调整内部数组的大小。
简单使用示例:
import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 添加元素
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 访问元素
String firstFruit = list.get(0); // 获取第一个元素
System.out.println("First fruit: " + firstFruit);
// 修改元素
list.set(1, "Mango");
System.out.println("After modification: " + list);
// 删除元素
list.remove("Cherry");
System.out.println("After removal: " + list);
// 遍历元素
for (String fruit : list) {
System.out.println(fruit);
}
}
}
3、Map接口与HashMap类
前面两个接口都继承自Collection接口,它们都表示单一对象数据集合,对于“关键字-值”这种形式的数据集合,Java提供了另一个接口——Map接口。Map接口在Java集合框架中扮演着至关重要的角色,它代表了一个关联映射的数据结构,其中每个元素都是一对键值对(key-value pair)。Map中的每个键都是唯一的,它映射到一个相关的值,允许通过键来高效地查找值。并且,键和值都可以是对象。
Map接口的关键特性包括:
1、映射关系:存储的是键值对的集合,键不可重复(根据equals()方法判定),值可以重复。
2、数据存取:通过键(而不是索引)来存取对应的值,提供put(key, value)方法添加或更新映射,get(key)方法获取对应键的值。
3、删除操作:通过键删除映射,使用remove(key)方法。
4、判断存在:containsKey(Object key)方法用于检查Map中是否存在指定的键,containsValue(Object value)方法用于检查Map中是否存在指定的值。
5、规模:通过size()方法返回映射的数量,isEmpty()方法检查Map是否为空。
6、遍历:可以通过entrySet()方法获取所有映射项(Map.Entry对象)的集合,然后遍历这些映射项来访问所有的键值对。
而HashMap类是Map接口的一个重要实现,它基于哈希表实现,提供了常数时间复杂度(O(1)平均情况下)的添加、删除和查找操作。其特点如下:
1、效率高:采用哈希算法,通过对键的哈希码计算出相应的桶位置来快速存取元素。
2、无序性:HashMap中的键值对没有固定的顺序,即迭代输出的顺序可能随时间和插入顺序的不同而变化。
3、允许空键和空值:HashMap可以有一个或零个键为null,也可以有任意数量的值为null。
4、自动扩容:当HashMap中的元素数量超出负载因子所决定的阈值时,会自动扩容并重新哈希。
HashMap类的常用方法:
方法 | 功能 |
---|---|
void clear() | 清空集合里的所有元素 |
Object put(Object key,Object value) | 以“键-值对”方式向集合中存入数据 |
Object get(Object key) | 根据键对象获得相关联的值 |
int size() | 获得集合中“键-值对”的个数 |
Set keySet() | 返回键的集合 |
Collection values() | 返回值的集合 |
Object remove(Object key) | 删除指定的键映射的“键-值对” |
下面是一个简单的HashMap使用示例:
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
// 创建一个HashMap实例
Map<String, Integer> map = new HashMap<>();
// 添加键值对
map.put("Apple", 1);
map.put("Banana", 2);
map.put("Cherry", 3);
// 获取值
int appleCount = map.get("Apple");
System.out.println("Number of Apples: " + appleCount);
// 检查键是否存在
boolean hasOrange = map.containsKey("Orange");
System.out.println("Does it contain Orange? " + hasOrange);
// 更新映射
map.put("Banana", 4);
System.out.println("Updated Banana count: " + map.get("Banana"));
// 遍历HashMap
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}
二、泛型
1.什么是泛型?
泛型是Java SE 5.0引入的一个新特性,它允许在定义类、接口或方法时声明类型参数,从而在编译时期就能检查类型安全,并且所有的强制转换都在编译期间完成,提高了代码的重用率和安全性。
在Java集合框架中,泛型的使用尤为常见。例如,在声明Map和HashMap时,我们可以指定键(Key)和值(Value)的具体类型,避免了类型转换的问题:
Map<String, Integer> map = new HashMap<String, Integer>();
在这个例子中,Map<String, Integer>就是一个带有泛型的引用类型,它表明这是一个键为String类型,值为Integer类型的Map。在实际使用过程中,我们只能向这个Map中添加String类型的键和Integer类型的值,否则会在编译阶段报错。
泛型的基本语法是在类型名称后面放置一对尖括号 ,其中 Type 是类型参数。在类或者接口定义时声明泛型,可以使得类或者接口在实例化时接受具体的类型参数,提高代码的复用性,减少类型转换的麻烦,并且能在编译时就发现潜在的类型错误。
2.泛型类和泛型方法
泛型类和泛型方法是Java语言中两种利用泛型特性的编程手段,它们有助于编写类型安全且可重用的代码。
泛型类
泛型类允许你在类声明时定义一个或多个类型参数,这些参数可以在类中作为一个占位符,代表某种未知的类型。在实例化泛型类时,需要指定具体的类型参数。例如:
public class GenericClass<T> {
private T item;
public void addItem(T newItem) {
this.item = newItem;
}
public T getItem() {
return item;
}
}
// 实例化泛型类,指定T为String类型
GenericClass<String> stringContainer = new GenericClass<>();
stringContainer.addItem("Hello");
String retrievedItem = stringContainer.getItem();
在上面的例子中,T 是类型参数,当创建 GenericClass 的实例时,可以指定 T 为任意类型,如 String。
泛型方法
泛型方法是在方法签名中定义类型参数,允许方法独立于类而拥有自己的类型参数。泛型方法能够在不需要改变整个类为泛型的情况下,只让特定方法拥有类型参数化的能力。
public class Main {
// 定义一个泛型方法,用于打印传入对象的信息
public <T> void printAny(T obj) {
System.out.println("Object of type: " + obj.getClass().getSimpleName());
}
public static void main(String[] args) {
Main main = new Main();
// 创建不同类型对象
String str = "Hello";
Integer num = 123;
// 调用泛型方法打印对象信息
main.printAny(str);
main.printAny(num);
}
}
在这个例子中,printAny 方法接收一个泛型参数 T,无论传入什么类型的对象,都能正确打印出其类型。
总的来说,泛型类适用于整个类的设计要求统一处理某种类型的实例,而泛型方法则更加灵活,适用于在单一方法级别上处理多种可能的类型。两者均在编译时确保类型安全,避免了运行时的ClassCastException异常。
3、泛型和集合的结合
(1)List< E >接口与ArrayList< E >类
import java.util.ArrayList;
import java.util.List;
public class ListAndArrayListExample {
public static void main(String[] args) {
// 创建 ArrayList 实例,它实现了 List 接口
List<String> list = new ArrayList<>();
// 使用 List 接口的方法添加元素
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 使用 List 接口的索引访问元素
String firstFruit = list.get(0);
System.out.println("First fruit: " + firstFruit);
// 使用 List 接口的方法检查列表大小
System.out.println("List size: " + list.size());
// 使用 List 接口的方法删除元素
list.remove("Banana");
System.out.println("List after removing Banana: " + list);
// 使用 List 接口的迭代器遍历列表
for (String fruit : list) {
System.out.println("Fruit: " + fruit);
}
// 添加另一个元素到列表
list.add("Durian");
// 查找 Durian 在列表中的索引
int index = list.indexOf("Durian");
System.out.println("Index of Durian: " + index);
}
}
在这个示例中,我们首先创建了一个 ArrayList 类型的 list 变量,尽管我们使用的是 ArrayList 类,但我们将其声明为 List 类型,这是因为我们在代码中仅依赖于 List 接口提供的方法。这样做的好处在于,如果我们将来决定换用其他实现了 List 接口的类(如 LinkedList),只需要更改一行代码即可,而其余使用 List 接口方法的代码则无需改动。
(2)Map< k,v >接口与HashMap< k,v >类
import java.util.HashMap;
import java.util.Map;
public class MapAndHashMapExample {
public static void main(String[] args) {
// 创建 HashMap 实例,它实现了 Map 接口
Map<String, Integer> map = new HashMap<>();
// 使用 Map 接口的方法添加键值对
map.put("Apple", 1);
map.put("Banana", 2);
map.put("Cherry", 3);
// 使用 Map 接口的方法通过键获取值
int appleCount = map.get("Apple");
System.out.println("Number of Apples: " + appleCount);
// 使用 Map 接口的方法检查键是否存在
if (map.containsKey("Apple")) {
System.out.println("Apple is in the map.");
}
// 使用 Map 接口的方法删除键值对
map.remove("Banana");
System.out.println("Map after removing Banana: " + map);
// 获取所有键的集合
Set<String> keys = map.keySet();
System.out.println("Keys in the map: " + keys);
// 获取所有值的集合
Collection<Integer> values = map.values();
System.out.println("Values in the map: " + values);
}
}
在这个示例中,我们创建了一个 HashMap<String,Integer> 类型的 map 变量,并使用 Map 接口的方法进行一系列操作。虽然我们使用的是 HashMap 类,但在代码中我们仅依赖于 Map 接口提供的方法,这样在未来如果需要切换到其他实现 Map 接口的类(如 TreeMap 或 LinkedHashMap),只需要更改创建实例的那一行代码即可。
总结
原创不易,请多支持,谢谢!!