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

相关文章
|
2月前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
7天前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
30 3
|
2月前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
2月前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
179 3
|
21天前
|
消息中间件 人工智能 Kubernetes
解密开源Serverless容器框架:事件驱动篇
Knative是一款基于Kubernetes的开源Serverless框架,提供了云原生、跨平台的Serverless编排标准。作为Serverless中必不可少的事件驱动能力,Knative Eventing提供了云原生的事件驱动能力。
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
46 4
|
2月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
2月前
|
Java BI API
Java Excel报表生成:JXLS库的高效应用
在Java应用开发中,经常需要将数据导出到Excel文件中,以便于数据的分析和共享。JXLS库是一个强大的工具,它基于Apache POI,提供了一种简单而高效的方式来生成Excel报表。本文将详细介绍JXLS库的使用方法和技巧,帮助你快速掌握Java中的Excel导出功能。
75 6
|
2月前
|
开发框架 Java 关系型数据库
Java哪个框架适合开发API接口?
在快速发展的软件开发领域,API接口连接了不同的系统和服务。Java作为成熟的编程语言,其生态系统中出现了许多API开发框架。Magic-API因其独特优势和强大功能,成为Java开发者优选的API开发框架。本文将从核心优势、实际应用价值及未来展望等方面,深入探讨Magic-API为何值得选择。
70 2
|
2月前
|
前端开发 Java 数据库连接
你不可不知道的JAVA EE 框架有哪些?
本文介绍了框架的基本概念及其在编程领域的应用,强调了软件框架作为通用、可复用的软件环境的重要性。文章分析了早期Java EE开发中使用JSP+Servlet技术的弊端,包括可维护性差和代码重用性低等问题,并阐述了使用框架的优势,如提高开发效率、增强代码规范性和可维护性及提升软件性能。最后,文中详细描述了几种主流的Java EE框架,包括Spring、Spring MVC、MyBatis、Hibernate和Struts 2,这些框架通过提供强大的功能和支持,显著提升了Java EE应用的开发效率和稳定性。
130 1