用框架思维学Java:集合概览

集合这个词,耳熟能详,从小学一年级开始,每天早上做操时都会听到这两个字:
在这里插入图片描述
高中数学又学习到了新的集合:
在这里插入图片描述
那么Java中的集合是什么呢?

一,前言

1,什么是Java集合

数学集合是Java集合的数学基础,所以有一致之处:

  • 都是由一组集在一起的对象构成
  • Java集合也可以应用数学集合的运算法则,如交集、并集、差集

但是,Java集合作为一个编程工具,更加突出了作为容器的作用:
在这里插入图片描述
就像一个果篮一样,Java容器具备存储、管理对象的功能,比如:

  • 存储水果
  • 向果篮中放入一个或者一批水果,动作是add、addAll、put、putAll
  • 把果篮中一个或者一批腐败的水果丢掉,动作是delete、remove
  • 从果篮中取出一个水果,动作是get
  • 判断果篮中是否包含苹果,动作是contains
  • 计算果篮中水果的个数,动作是size

Java集合是存储、管理对象的容器

2,Java集合的作用

Java集合到底有什么作用呢?这个问题值得好好思考一下,只有这样,才能更好的使用Java集合。

以开发一个简单的订单管理系统为例,我们需要存储并管理一系列订单信息,包括订单号、客户姓名、商品列表、总价等。

让我们通过对比使用Java集合不使用Java集合的情况,分析在实现该系统时可能遇到的困难。

使用Java集合的示例代码
import java.util.*;

class Product {
    String name;
    double price;

    Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
}

class Order {
    String orderId;
    String customerName;
    ArrayList<Product> productList;
    double totalAmount;

    Order(String orderId, String customerName) {
        this.orderId = orderId;
        this.customerName = customerName;
        this.productList = new ArrayList<>();
    }

    void addProduct(Product product) {
        // 不需要考虑集合容器的大小,容器会自动扩容
        productList.add(product);
        totalAmount += product.price;
    }
}

public class OrderManagement {
    public static void main(String[] args) {
        Order order = new Order("ORD123", "Alice");
        order.addProduct(new Product("Book", 19.99));
        order.addProduct(new Product("Pen", 4.99));
        
        System.out.println("Total Amount: " + order.totalAmount);
    }
}
不使用Java集合实现

如果Java集合框架不可用,我们需要手动实现数据结构来存储订单中的商品列表。这将涉及创建一个产品数组,并手动管理数组的大小调整、添加和删除产品等操作。

class OrderWithoutCollection {
    String orderId;
    String customerName;
    Product[] productList; // 假设产品数组
    int productCount;
    double totalAmount;

    OrderWithoutCollection(String orderId, String customerName) {
        this.orderId = orderId;
        this.customerName = customerName;
        this.productList = new Product[10]; // 初始化固定大小的数组
        this.productCount = 0;
    }

    boolean addProduct(Product product) {
        // 需要考虑数组的大小
        if (productCount == productList.length) {
            // 数组已满,需要手动扩展,这里简化处理,实际应复制数组并创建更大的数组
            System.out.println("Product list is full, cannot add more products.");
            return false;
        }
        productList[productCount++] = product;
        totalAmount += product.price;
        return true;
    }
    
    // 其他方法省略...
}

public class OrderManagementWithoutCollection {
    public static void main(String[] args) {
        OrderWithoutCollection order = new OrderWithoutCollection("ORD123", "Alice");
        order.addProduct(new Product("Book", 19.99));
        order.addProduct(new Product("Pen", 4.99));
        
        System.out.println("Total Amount: " + order.totalAmount);
    }
}
两种方式的差异分析
  • 复杂度增加:不使用集合框架,我们必须手动管理数组的大小,如上例中的OrderWithoutCollection类,当商品列表超出初始分配的空间时,需要手动扩展数组,这增加了代码的复杂度

  • 效率低下:手动数组管理缺乏动态调整大小的能力,可能导致空间浪费或频繁的数据复制(如果每次添加商品都动态扩展数组)。此外,查找和删除操作可能需要遍历整个数组,效率低下

  • 类型安全风险:没有泛型,我们可能会在操作数组时忘记检查类型,导致运行时错误

  • 功能缺失:没有现成的排序、过滤等操作,需要自己编写逻辑,如若想要按商品价格排序,需要实现复杂的排序算法

  • 代码可读性和维护性降低:代码中充斥着底层数据结构管理逻辑,而非业务逻辑,降低了代码的可读性和未来的可维护性

综上所述,没有Java集合框架的支持,开发任务将变得繁琐且低效,不仅影响开发进度,也可能影响最终产品的稳定性和性能。

3,集合和数组的区别

Java中的集合和数组都是用于存储数据的容器,但它们在多个方面存在显著区别:

  • 长度(容量)

    • 数组:数组的长度是固定的,一旦在创建时指定了大小,之后就不能改变。
    • 集合:集合的大小是动态的,可以根据需要自动调整容量。
  • 存储类型

    • 数组:既可以存储基本数据类型(如int, double),也可以存储引用数据类型(对象)。但是,当存储基本数据类型时,数组直接存储这些值;存储对象时,存储的是对象的引用。
    • 集合仅能存储引用数据类型,包括自定义对象和包装类(如Integer包装int)。集合不会直接存储基本数据类型,但可以通过自动装箱(autoboxing)和拆箱(unboxing)来处理基本数据类型。
  • 类型限制

    • 数组:在声明时就需要指定元素的具体类型,并且数组中的所有元素都必须是同一类型。
    • 集合:虽然在使用时通常倾向于存储同类型对象以保持集合的一致性,但实际上集合可以存储不同类型的对象(因为它们都是Object的子类),特别是使用泛型之前的老集合框架。
  • 操作功能

    • 数组:提供了基本的索引访问和修改功能,操作相对简单直接。
    • 集合:提供了丰富的API,包括添加(add)、删除(remove)、查找(contains)、遍历(iterator)等操作,还支持排序、过滤等多种高级功能。
  • 性能

    • 数组:由于其索引访问的特性,直接通过下标访问元素速度非常快。
    • 集合:某些操作可能涉及到额外的类型检查或转换,可能导致性能略低于直接数组访问,但具体差异取决于集合的具体实现和所执行的操作。
  • 灵活性和扩展性

    • 数组:不够灵活,一旦创建,其大小和类型不可变。
    • 集合:高度灵活,可以根据需要选择不同类型集合(如List, Set, Queue等),并且可以轻松地在集合间转换。
  • 转换

    • 可以通过Arrays.asList()方法将数组转换为集合,或者使用集合的toArray()方法将集合转换为数组,但需要注意类型匹配和潜在的类型转换问题。

二,集合概览

集合是Java中极其高频使用的工具类,从接口到实现,形成了一个庞杂的体系,要掌握这个庞杂的体系,要避免盲人摸象的学习方法,先看一看大象完整的照片,知大象有四条腿、一个长鼻子、两只眼睛、两根象牙和一个庞大的身躯,然后再靠近观察研究细节!

那么集合这只大象的样子是什么呢?

1,最简化的集合体系

在这里插入图片描述

集合分为两种:

  • 一种是单列集合,称之为Collection,如下图,集合每个位置的元素是一个单值

在这里插入图片描述

  • 一种是双列集合,称之为Map,如下图,集合每个位置上的元素是一个键值对,注意一对相关联的key、value合称一个元素
    在这里插入图片描述

2,完整的集合体系

在这里插入图片描述

Java集合框架分为两大类:CollectionMap

其中Collection又细分为ListSetQueue

  • List是有序的集合,即元素在List中的顺序和插入集合的先后顺序一致
  • List中的元素可以重复,比如把数字1先后多次插入List集合中,插入多少次就保存多少个
  • Set集合中的元素是无序的
  • Set集合不可以重复,比如把数字1先后多次插入Set集合中,后插入的会覆盖先插入的,实际效果相当于仅插入一次
  • Queue主要用于实现队列功能,遵循先进先出(FIFO)原则
继承体系简图
Collection
├── List
│   ├── ArrayList
│   ├── LinkedList
│   └── Vector
├── Set
│   ├── HashSet
│   ├── TreeSet
│   └── LinkedHashSet
└── Queue
    ├── PriorityQueue
    └── Deque
        ├── ArrayDeque
        └── LinkedList
Map
├── HashMap
├── TreeMap
└── LinkedHashMap

3,集合的主要操作

  • 增删查改add(E element)remove(Object o)contains(Object o)get(int index)/put(K key, V value)
  • 遍历:增强型for循环、迭代器(Iterator)、Lambda表达式等
  • 容量管理:如ArrayListensureCapacity(int minCapacity)trimToSize()

4,创建集合实现类示例

① LinkedList
import java.util.LinkedList;

public class LinkedListDemo {
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("Apple");
        linkedList.add("Banana");
        linkedList.addFirst("Cherry");
        System.out.println(linkedList); // 输出: [Cherry, Apple, Banana]
        linkedList.removeFirst();
        System.out.println(linkedList); // 输出: [Apple, Banana]
    }
}

LinkedList基于双向链表实现,每个节点包含前驱和后继节点的引用,这使得插入和删除操作非常高效(O(1)),特别是在列表的开始和结束。然而,由于需要遍历链表来访问元素,随机访问操作(如通过索引访问)效率较低(O(n))。与ArrayList相比,LinkedList更适合频繁的插入和删除操作,但在随机访问方面不如ArrayList高效。

② ArrayList
import java.util.ArrayList;

public class ArrayListDemo {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("Apple");
        arrayList.add("Banana");
        arrayList.add(1, "Cherry");
        System.out.println(arrayList); // 输出: [Apple, Cherry, Banana]
        arrayList.set(0, "Orange");
        System.out.println(arrayList); // 输出: [Orange, Cherry, Banana]
    }
}

ArrayList基于动态数组实现,内部维护了一个可自动增长的数组来存储元素。这意味着它提供了快速的随机访问(通过索引,O(1)),但插入和删除操作(特别是位于数组中间的操作)可能较慢(O(n)),因为可能需要移动后续元素。与LinkedList相比,它在索引访问和遍历顺序元素方面更高效,但在频繁插入和删除的场景下不如LinkedList灵活。

③ Vector
import java.util.Vector;

public class VectorDemo {
    public static void main(String[] args) {
        Vector<String> vector = new Vector<>(3);
        vector.add("Apple");
        vector.add("Banana");
        System.out.println(vector); // 输出: [Apple, Banana]
    }
}

Vector也是基于动态数组实现,与ArrayList相似,但其方法默认是线程安全的,通过同步机制确保了多线程环境下的安全访问。这增加了额外的性能开销,使得在单线程环境下,ArrayList通常比Vector更优。Vector在容量自动增长时,会增加当前容量的两倍,而ArrayList通常是1.5倍。一般情况下,不用Vector,因为其线程安全导致效率低。

④ HashSet
import java.util.HashSet;

public class HashSetDemo {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("Apple");
        set.add("Banana");
        set.add("Apple"); // 重复元素,不会添加
        System.out.println(set); // 输出可能是 [Apple, Banana],顺序不确定
    }
}

: HashSet基于哈希表实现,使用hashCode()和equals()方法来确定元素的唯一性。它不保证元素的顺序,插入和查询操作平均时间复杂度为O(1),但最坏情况下(哈希冲突严重时)可能退化为O(n)。与TreeSet相比,它牺牲了排序特性,换取了更高的插入和查询性能。

⑤ TreeSet
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<String> set = new TreeSet<>();
        set.add("Apple");
        set.add("Banana");
        set.add("Cherry");
        System.out.println(set); // 输出: [Apple, Banana, Cherry],自动排序
    }
}

TreeSet基于红黑树实现,因此它能够对元素进行自然排序(根据元素的Comparable实现)或定制排序(通过Comparator)。插入、删除和查找操作的平均时间复杂度为O(log n),并保证了元素的有序性。与HashSet相比,它提供了排序功能,但牺牲了在无序集合中可能获得的更快的平均性能。

⑥ LinkedHashSet
import java.util.LinkedHashSet;

public class LinkedHashSetDemo {
    public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet<>();
        set.add("Apple");
        set.add("Banana");
        set.add("Cherry");
        System.out.println(set); // 输出: [Apple, Banana, Cherry],保持插入顺序
    }
}

LinkedHashSetHashSet的一个子类,它同时维护了一个双向链表来保持元素的插入顺序。因此,它既有HashSet的快速查找特性(基于哈希表),又保持了元素的插入顺序,类似于LinkedHashMap。相比于普通的HashSet,它提供了更好的迭代性能(特别是当频繁迭代且需要保持顺序时),但消耗了额外的内存。

⑦ PriorityQueue
import java.util.PriorityQueue;

public class PriorityQueueDemo {
    public static void main(String[] args) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        queue.add(5);
        queue.add(1);
        queue.add(3);
        System.out.println(queue.poll()); // 输出并移除最小元素: 1
    }
}

PriorityQueue是一个无界优先队列,它使用了堆数据结构(通常是二叉堆)来实现。这使得它能够高效地(O(log n))插入元素并返回队列中最小(或最大,取决于优先级比较器)的元素。与普通队列(FIFO)不同,PriorityQueue保证了队首元素始终是最优先的。它的实现不适合于随机访问,主要用于需要排序的插入和删除操作。

⑧ Deque
import java.util.ArrayDeque;

public class ArrayDequeDemo {
    public static void main(String[] args) {
        ArrayDeque<String> deque = new ArrayDeque<>();
        deque.addFirst("Apple");
        deque.addLast("Banana");
        System.out.println(deque.pop()); // 输出并移除头部元素: Apple
    }
}

ArrayDeque(双端队列)基于可调整大小的环形数组实现,提供了两端高效的插入和删除操作(O(1)),同时支持作为栈(push/pop)和队列(addFirst/removeFirst)使用。它在大多数场景下比传统的LinkedList表现更优,特别是在频繁的两端操作时,因为它减少了内存分配的开销并优化了缓存局部性。

⑨ HashMap
import java.util.HashMap;

public class HashMapDemo {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        map.put("Apple", 1);
        map.put("Banana", 2);
        System.out.println(map.get("Apple")); // 输出: 1
    }
}

HashMap基于哈希表实现,通过散列函数将键映射到数组的某个位置,以实现快速访问。它允许null键和null值,但不保证元素的顺序。插入、删除和查找操作的平均时间复杂度为O(1),在哈希冲突较少的情况下。与TreeMap相比,它放弃了排序功能,以换取更高的性能。

⑩ TreeMap
import java.util.TreeMap;

public class TreeMapDemo {
    public static void main(String[] args) {
        TreeMap<String, Integer> map = new TreeMap<>();
        map.put("Apple", 1);
        map.put("Banana", 2);
        map.put("Cherry", 3);
        System.out.println(map.keySet()); // 输出: [Apple, Banana, Cherry],自动排序
    }
}

TreeMap基于红黑树实现,保证了键的自然排序或定制排序。它提供了对键的有序访问,并且所有键值对都按照键的排序顺序排列。插入、删除和查找操作的平均时间复杂度为O(log n)。与HashMap相比,它牺牲了快速的无序访问,但提供了排序和区间访问的能力。

⑪ LinkedHashMap
import java.util.LinkedHashMap;

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
        map.put("Apple", 1);
        map.put("Banana", 2);
        map.put("Cherry", 3);
        System.out.println(map.entrySet()); // 输出: 保持插入顺序
    }
}

LinkedHashMap继承自HashMap,并在其基础上增加了双向链表来维护元素的插入顺序或访问顺序(取决于构造参数)。这使得它既能提供快速的访问性能,又能维持元素的插入或访问顺序。与普通HashMap相比,它更适合那些需要按插入或访问顺序遍历键值对的场景,但会消耗更多内存。与TreeMap相比,它不提供键的自然排序。

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小手追梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值