集合类与泛型一篇就搞懂!!(简单易懂,包含细节特点)

本文详细介绍了Java集合类,包括Set接口的HashSet,List接口的ArrayList,以及Map接口的HashMap,展示了它们的特性、使用方法和泛型的应用。同时,对比了这些接口在数据结构和操作上的区别。
摘要由CSDN通过智能技术生成

本篇参考书籍为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),只需要更改创建实例的那一行代码即可。


总结

原创不易,请多支持,谢谢!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值