Java——容器类库框架浅析

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: Java——容器类库框架浅析 阅读目录 前言 Java容器类库概览 List Set Map Stack和Queue 优先级队列PriorityQueue 迭代器 ListIterator foreach与迭代器 小结   回到目录 前言 通常,我们总是在程序运行过程中才获得一些条件去创建对象,这些动态创建的对象就需要使用一些方式去保存。

Java——容器类库框架浅析

 

前言

通常,我们总是在程序运行过程中才获得一些条件去创建对象,这些动态创建的对象就需要使用一些方式去保存。我们可以使用数组去存储,但是需要注意数组的尺寸一旦定义便不可修改,而我们并不知道程序在运行过程中会产生多少对象,于是数组的尺寸便成了限制。Java实用类库还提供了一套的容器类来解决这个问题,基本类型为:List 、Set、Queue和Map。这些对象类型也称为集合类,但是由于Java类库使用了Collection这个名字来指代该类库中的一个特殊子集,所以使用术语“容器”来称呼它们。下面将简单介绍Java容器类库的基本概念和功能、每个容器的不同版本实现和和从整体分析容器之间的联系。

Java容器类库概览

Java容器类的框架图

Java容器类库的主要作用是“保存对象”,我们将其划分成以下两个不同的概念:

Collection

一个独立的元素序列(一种存放一组对象的方式),这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素;Set中不能有重复的元素;Queue按排队规则来确定对象产生的顺序。

Collection是一个高度抽象的容器接口,其中包含了容器的基本操作和属性。

Map

一组成对的“键值对”对象,允许你使用键来查找值。

框架类图中还包含了许多Abstract类,主要方便于我们创建容器的实例,Abstract类中已基本实现了接口中的方法,我们只需要选择我们需要的方法进行覆盖即可。

Iterator

我们再来看Iterator,我们通常是使用Iterator迭代器来遍历容器。上图存在的Collection依赖于Iterator是指:实现Collection需要实现iterator()函数,可以返回一个Iterator对象。ListIterator是专门用于遍历List的迭代器。

工具类Arrays和Collections为容器添加元素

java.util包中的Arrays和Collections类中包含了很多的实用方法。Arrays类中包含操作数组的各种方法,还包含一个静态的Arrays.asList()方法接受一个数组或是用逗号分隔的元素列表,将其转换成一个列表对象。Collection类包含对集合操作的各种方法。我们也可以使用Collections.addAll()想容器中添加一组元素。Collections.addAll()接受一个Collection对象以及一个数组或者用逗号分隔的元素列表,将元素添加到Collection对象中。

Arrays.asList()的底层实现是一个数组,即使用Arrays.asList()生成的List的尺寸是不可以修改的(添加或删除元素),否则将会抛出UnsupportedOperationException异常。

List

List接口继承自Collection接口,用于Collection中的所有方法,在Collection的基础上也添加了许多方法,使得可以在List中插入和删除元素。List有两种基本的实现:ArrayList和LinkedList

  • 基本的ArrayList,它适合于随机访问元素,但是在插入和删除元素时就比较慢
  • LinkedList适合于在元素插入和删除较频繁时使用,随机访问的速度比较慢

Set

Set中不保存重复的元素,含义同数学概念上的集合。 Set常用于测试归属性,即查询某个元素是否在某个Set中。正因为如此查找也就成了Set中重要的操作。通常会选择HashSet的实现,它对快速查找进行了优化。Set也有多种不同的实现,不同的Set实现不仅具有不同的行为,而且它们对于可以在特定的Set中防止元素的类型也有不同的要求。

  • Set(interface)

    存入Set的每个元素必须是唯一对的。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set接口不保证维护元素的次序。

  • HashSet

    为快速查找而设计的Set。存入HashSet的元素必须定义hashCode()。使用HashMap实现。

  • TreeSet

    保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。元素必须实现Comparable接口。使用TreeSet实现。

  • LinkedHashSet

    具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。在使用迭代器遍历该Set时,结果会按照元素插入的次序显示。元素也必须定义hashCode()方法

Map

Map有以下特点:

  • Map是将键映射到值的键值对(key-value)接口

  • 映射中不能包含重复的键,每个键最多可以映射到一个值,但是一个值可以被多个键映射

  • Map提供了三个Set视图供我们访问:键的Set、值的Set和键值对的Set

  • 映射的顺序定义为访问的映射Set上的迭代器返回元素的顺序。TreeMa类,可以对映射的顺序做出特定保证;其他的,则不能保证

  • 可变对象作为映射键需要非常小心

  • Map的实现类应该提供两个“标准“构造函数

    第一个,void(无参数)构造方法,用于创建空映射

    第二个,带有单个 Map 类型参数的构造方法,用于创建一个与其参数具有相同键-值映射关系的新映射。带有单个 Map 类型参数的构造方法,用于创建一个与其参数具有相同键-值映射关系的新映射

Map的几种基本实现:

  • HashMap

    Map是基于散列表的实现(取代了HashTable)。HashMap使用散列码(对象hashCode()生成的值)来进行快速搜索。

  • LinkedHashMap

    类似于HashMap,但是迭代的时候,取得键值对的顺序是起插入的顺序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点,而迭代访问的时候更快,因为使用链表维护了内部次序。

  • TreeMap

    基于红黑树的实现。查看“键”或者“键值对”时,它们会被排序(次序由Comparable或Comparator决定)。TreeMap的特点在于所得到的结果是经过排序的。TreeMap是唯一的带有subMap()的Map,可以返回一个子树。

  • WeakHashMap

    弱键(weak key)映射,允许释放映射所指向的对象,这是为了解决某类特殊问题而设计的。如果映射之外没有引用指向某个“键”,则此“键”可以被垃圾回收。

  • ConcurrentHashMap

    一种线程安全的Map,不涉及同步加锁。在并发中还会介绍。

Stack和Queue

Stack是一个先进后出(LIFO)的容器。往盒子中放书,先放进去的最后才拿得出来,最后放进去的第一个就可以取出,这种模型就是栈(Stack)可以描述的。LinkedList中有可以实现栈所有功能的方法,有时也可以直接将LinkedList作为栈使用。

队列是一个典型的先进先出(FIFO)的容器。事物放进容器的顺序和取出的顺序是相同的(优先级队列根据事物优先级出队事物)。队列常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要。同样,LinkedList也提供了方法支持队列的行为,并且它实现了Queue接口。

优先级队列PriorityQueue

先进先出描述了典型的队列规则。队列规则是指在给定一组队列的元素情况下,确定下一个弹出队列的元素的规则。优先级队列声明的下一个弹出的元素是最需要的元素(具有最高优先级的元素)。

我们可以在PriorityQueue上调用offer()方法来插入一个对象,这个对象就会在队列中被排序,默认排序为自然排序,即按插入的先后进行排序,但是我们可以通过提供自己的Comparator来修改这个排序。当调用peek()、poll()和remove()方法时,将获取队列优先级最高的元素。

优先级队列算法实现的数据结构通常是一个堆。

迭代器

对于访问容器而言,有没有一种方式使得同一份遍历的代码可以适用于不同类型的容器?要实现这样的目的就可以使用迭代器。使用迭代器对象,遍历并选择序列中的对象,而客户端不必知道或关心该序列底层的结构。Java中对迭代器有一些限制,比如Java的Iterator只能单向移动,这个Iterator只能用来:

  • 使用next()方法获得序列的下一个元素
  • 使用hasNext()方法检查序列中是否还有元素
  • 使用remove()方法将迭代器新近返回的元素删除,意味着在调用remove()之前必须先调用next()

API中的Iterator接口中方法如上,实现Iterator对象需要实现hashNext()方法和next()方法,remove方法是一个可选操作。forEachRemaining是Java 1.8(Java SE8)中加入的方法,用于Lambda表达式。

举一个简单的使用迭代器访问容器的例子:

class Cat{
    private static int counter = 0;
    private int id = counter++;
    @Override
    public String toString() {
        return "Cat: " + id;
    }
}

public class IteratorAccessContainer {
    //不包含任何容器类型信息的遍历容器方法
    public static void showElement(Iterator<Cat> it) {
        while (it.hasNext()) {      //hasNext()检查序列中是否还有元素
            Cat cat = it.next();    //next()返回序列中的元素
            System.out.print(cat + "\t");
        }
        System.out.println();
    }
    
    public static void main(String[] args) {
        ArrayList<Cat> cats1 = new ArrayList<Cat>();
        LinkedList<Cat> cats2 = new LinkedList<>(); //可以省略类型参数 编译器可自动推断出
        HashSet<Cat> cats3 = new HashSet<>();
        for(int i=0;i<3; i++) {
            cats1.add(new Cat());
            cats2.add(new Cat());
            cats3.add(new Cat());
        }
        showElement(cats1.iterator());
        showElement(cats2.iterator());
        showElement(cats3.iterator());
    }
}
/*
output:
Cat: 0  Cat: 3  Cat: 6  
Cat: 1  Cat: 4  Cat: 7  
Cat: 2  Cat: 8  Cat: 5  
*/

showElement()方法不包含任何有关它遍历的序列类型信息,这就展示了Iterator的好处:能够将遍历序列的操作与序列底层结构分离。也可以说,迭代器统一了对容器的访问方式

从容器框架图中我们可以看出,Collection是描述所有序列容器的共性的根接口。但是在C++中,标准的C++类库中没有其他容器的任何公共基类,容器之间的共性都是通过迭代器达成的。在Java中,则将两种方法绑定到了一起,实现Collection的同时也要实现iterator()方法(返回该容器的迭代器)。

ListIterator

ListIterator是一个更加强大的Iterator子类型,但是它只能用于各种List的访问。Iterator只能前向移动,但ListIterator允许我们可以前后移动。它还可以产生相对于迭代器在列表中指向当前位置的前一个和后一个索引,并且可以使用set()方法替换它访问过的最后一个元素。remove()方法可以删除它访问过的最后一个元素。需要注意,这两处的最后一个元素只的都是调用next()或者previous返回的元素,也就意味着调用set()、remove()这两个方法之前,要先调用next()或者previous()。

需要注意ListIterator在序列中的游标位置与Iterator不同,Iterator的游标位置始终位于调用previous()将返回的元素和调用next()将返回的元素之间。长度为n的列表的迭代器的游标位置有n+1个。

使用ListIterator对列表进行正向和返回迭代,以及使用set()替换列表元素的例子:

public class ListIteration {
    public static void main(String[] args) {
        List<Cat> catList = new ArrayList<>();
        for(int i=0; i<5; i++) {
            catList.add(new Cat());
        }
        
        ListIterator<Cat> it = catList.listIterator();
        System.out.println("CatNo.\t nextIndex\t previousIndex");
        
        //正向遍历
        System.out.println("正向遍历:");
        while (it.hasNext()) {
            Cat cat = it.next();
            System.out.println(cat+"\t\t"+it.nextIndex()+"\t\t"+it.previousIndex());
        }
        System.out.println();
        
        System.out.println("当迭代器游标处于最后一个元素末尾时:");
        ListIterator<Cat> it2 = catList.listIterator();
        while (it2.hasNext()) {
            Cat cat = it2.next();
            System.out.println(cat+"\t\t"+it.nextIndex()+"\t\t"+it.previousIndex());
        }
        System.out.println();
        
        //反向遍历
        System.out.println("反向遍历");
        while(it.hasPrevious()) {
            Cat cat = it.previous();
            System.out.println(cat+"\t\t"+it.nextIndex()+"\t\t"+it.previousIndex());
        }
        System.out.println();
        
        //产生指定游标位置的迭代器 从第二个位置开始向前替换列表中的Cat对象
        System.out.println("从第二个位置开始向前替换列表中的Cat对象");
        it = catList.listIterator(2);
        while(it.hasNext()) {
            it.next();
            it.set(new Cat());
        }
        System.out.println(catList);
    }
}
/*
CatNo.   nextIndex   previousIndex
正向遍历:
Cat: 0      1       0
Cat: 1      2       1
Cat: 2      3       2
Cat: 3      4       3
Cat: 4      5       4

当迭代器游标处于最后一个元素末尾时:
Cat: 0      5       4
Cat: 1      5       4
Cat: 2      5       4
Cat: 3      5       4
Cat: 4      5       4

反向遍历
Cat: 4      4       3
Cat: 3      3       2
Cat: 2      2       1
Cat: 1      1       0
Cat: 0      0       -1

从第二个位置开始向前替换列表中的Cat对象
[Cat: 0, Cat: 1, Cat: 5, Cat: 6, Cat: 7]
*/

foreach与迭代器

foreach语法不仅可以用在数组,也可以用在任何Collection对象。之所以可以用在Collection对象,是因为Java SE5引入了Iterable接口,该接口包含一个能够产生Iterator的iterator()方法,并且Iterable接口被foreach用来在序列中移动。因此,如果创建了任何实现Iterable的类,都可以将它用于foreach当中。需要注意,数组虽然可以使用foreach语法遍历,但不意味着数组是Iterable的

实现一个可迭代的类,使用foreach方法遍历

public class IterableClass implements Iterable<String>{
    private String[] words = ("This is happy day.").split(" ");
    @Override
    public Iterator<String> iterator() {
        return new Iterator<String>() {
            private int index = 0;
            //判断是否存在下一个元素
            public boolean hasNext() {
                return index < words.length;
            }
            //返回下一个元素
            public String next() {
                return words[index++];
            }
            public void remove() {  //remove可以不用实现
                throw new UnsupportedOperationException();
            }
        };
    }
    
    public static void main(String[] args) {
        //foreach语法遍历实现了Iterable接口的类
        for(String s : new IterableClass()) {
            System.out.println(s);
        }
    }
}
/*
This
is
happy
day.
*/

小结

对Java容器类库做了大致的介绍,具体的容器使用方法以及实现会在后面的博客中继续介绍。本文重点介绍了Iterator,它统一了对容器的访问方式,但是仍有一点心存疑惑:foreach语法可以遍历容器是因为容器实现了Iterable的原因,但是也可以遍历数组,数组并不是Iterable的。那么foreach可以遍历数组的依据是什么呢?这个问题暂时还没有看到合适的解答,各位看官若有想法可留言告知,感激不尽!

参考:

Java 集合系列目录(Catedory): https://www.cnblogs.com/skywang12345/p/3323085.html

《Java编程思想》第四版

原文地址https://www.cnblogs.com/myworld7/p/10456821.html

相关文章
|
16天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
2月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
39 3
|
25天前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
25天前
|
消息中间件 Java 数据库连接
Java 反射最全详解 ,框架设计必掌握!
本文详细解析Java反射机制,包括反射的概念、用途、实现原理及应用场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Java 反射最全详解 ,框架设计必掌握!
|
2月前
|
前端开发 Java 数据库连接
Spring 框架:Java 开发者的春天
Spring 框架是一个功能强大的开源框架,主要用于简化 Java 企业级应用的开发,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,并由Pivotal团队维护。
53 1
Spring 框架:Java 开发者的春天
|
23天前
|
Java API Apache
|
2月前
|
SQL Java 关系型数据库
java连接mysql查询数据(基础版,无框架)
【10月更文挑战第12天】该示例展示了如何使用Java通过JDBC连接MySQL数据库并查询数据。首先在项目中引入`mysql-connector-java`依赖,然后通过`JdbcUtil`类中的`main`方法实现数据库连接、执行SQL查询及结果处理,最后关闭相关资源。
|
28天前
|
缓存 Java 数据库连接
Hibernate:Java持久层框架的高效应用
通过上述步骤,可以在Java项目中高效应用Hibernate框架,实现对关系数据库的透明持久化管理。Hibernate提供的强大功能和灵活配置,使得开发者能够专注于业务逻辑的实现,而不必过多关注底层数据库操作。
13 1
|
2月前
|
Java 数据库连接 开发者
Spring 框架:Java 开发者的春天
【10月更文挑战第27天】Spring 框架由 Rod Johnson 在 2002 年创建,旨在解决 Java 企业级开发中的复杂性问题。它通过控制反转(IOC)和面向切面的编程(AOP)等核心机制,提供了轻量级的容器和丰富的功能,支持 Web 开发、数据访问等领域,显著提高了开发效率和应用的可维护性。Spring 拥有强大的社区支持和丰富的生态系统,是 Java 开发不可或缺的工具。
|
2月前
|
安全 Java 程序员
深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制
本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。
60 5