java 之容器

在Java中,我们想要保存对象可以使用很多种手段。我们之前了解过的数组就是其中之一。但是数组具有固定的尺寸,而通常来说,程序总是在运行时根据条件来创建对象,我们无法预知将要创建对象的个数以及类型,所以Java推出了容器类来解决这一问题。

Java的容器类分为List,Set,QueueMap。我们也称它们为集合类(Collection)。

Java使用泛型来实现容器类,例如我们要使用顺序表这一数据结构,Java提供了ArrayList和LinkedList两种实现类,ArrayList的实现就是基于数组的。比如我们要存储一组用户,在Java8之前的版本,我们就可以这样声明对象:List<User> users = new ArrayList<User>();。然后通过add方法来添加变量。

Java7及Java8的容器

如果你是一个喜欢新事物,也不妨尝试下Java7,它可以对泛型的目标类型进行推断。我们就可以这样声明这个对象List<User> users = new ArrayList<>();

Java7中,编译器会根据变量声明时的泛型类型自动推断出实例化所用的泛型类型。但是它在创建泛型实例时的类型推断是有限制的:只有构造器的参数化类型在上下文中被显著的声明了,才可以使用类型推断,否则不行。比如:

List<String> list = new ArrayList<>();
list.add("A");// 由于addAll期望获得Collection<? extends String>类型的参数,因此下面的语句无法通过
list.addAll(new ArrayList<>());

而在Java8中,它支持两种泛型的目标类型推断:

1.支持通过方法上下文推断泛型目标类型

2.支持在方法调用链路当中,泛型类型推断传递到最后一个方法

上述程序可以更改如下:

//通过方法赋值的目标参数来自动推断泛型的类型
List<String> list = List.nil();
//通过前面方法参数类型推断泛型的类型
List.cons(42, List.nil());

Java容器的基本概念

Java容器类库是用来保存对象的,他有两种不同的概念:

  1. Collection。独立元素的序列,这些元素都服从一条或多条规则。ListSet以及Queue都是Collection的一种,List必须按照顺序保存元素,而Set不能有重复元素,Queue需要按照排队规则来确定对象的顺序。
  2. MapMap是键值对类型,允许用户通过键来查找对象。ArrayList允许使用数字来查找值,Hash表允许我们使用另一个对象来查找某个对象。

尽管存在这两种概念,我们在工程中,大部分代码还是和接口打交道。Collection接口概括了序列的概念,即存放一组对象的方式。ArrayList,HashSet等具体类均实现了Collection接口或Collection接口的子接口(List接口和Set接口等)。

Collection接口的定义如下:

public interface Collection<E> extends Iterable<E> {

    int size();

    boolean isEmpty();

    boolean contains(Object o);

    Iterator<E> iterator();

    Object[] toArray();

    <T> T[] toArray(T[] a);

    boolean add(E e);

    boolean remove(Object o);

    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<? extends E> c);

    boolean removeAll(Collection<?> c);

    boolean retainAll(Collection<?> c);

    void clear();

    boolean equals(Object o);

    int hashCode();
}

我们可以看出Collection接口实际上继承了Iterable接口,实现这个接口的类可以使用迭代器以及foreach语法进行遍历。

size, isEmpty, contains, iterator, toArray, add, remove, containAll, addAll, removeAll, clear方法分别表示获取这个Collection类型的对象的元素个数,是否为空,是否包含某个元素,获取迭代器,转换为数组,增加元素,删除元素,某个Collection对象是否为它的子集以及进行取差集和清空操作。

除了上述成员方法,java.utils包中的ArraysCollections类中还提供了很多实用的方法,如:

  • Arrays.asList()方法可以接受数组或逗号分隔的元素列表,并将其转化为一个List对象。
  • Collections.addAll()方法接受一个Collection对象和一个数组或以逗号分隔的列表将其加入到集合当中。
  • 等等

我们可以这样使用:

//使用asList方法生成list
List<String> keywords = Arrays.asList("hello", "thank", "you");
//我们要将其他元素加入到keywords容器中
Collections.addAll(keywords, "very", "much");

使用asList()方法输出产生的对象需要注意一些问题,因为在这种情况下,它的底层表示仍然是数组,因此我们是不能够该表它的尺寸的。这时使用adddelete方法可能会引发改变数组尺寸的尝试,会在运行时得到Unsupported Operation错误。

如果要使用可以改变尺寸的List,我推荐大家在获取到asList()方法的输出后,再构造一个ArrayList。

迭代器

从之前的Collection接口中可以看出,任何容器类,都可以以某种方式插入、获取和删除元素。add()作为最基本的插入元素方法而get()则是基本取元素的方法。

但是如果我们仅仅使用get和add方法来进行元素操作,如果将一个类的方法实现了,如果想要将相同的代码用在其他容器类中就会遇到问题,那么我们如何解决这一问题呢?

在这里我们就引入了面向对象的设计模式迭代器模式。迭代器是一个对象,它的工作是遍历并选择序列中的对象。客户端不需要知道序列的底层架构。

Java的Iterator的定义如下:

public interface Iterator<E> {

    boolean hasNext();

    E next();

    void remove();
}

我们可以使用:

  1. next()方法来获取序列的下一个元素。
  2. hasNext()检查序列中是否还有元素。
  3. 使用remove()将迭代器新近返回的元素删除。比如我们要遍历一个容器:
List<String> keywords = new ArrayList<>();
keywords.add("hello");
keywords.add(0, "thank");
Iterator<String> iterator = keywords.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

List<String> keywords = new ArrayList<>();
keywords.add("hello");
keywords.add(0, "thank");
for (String keyword : keywords) {
    System.out.println(iterator.next());
}

Iterator还有一些功能更为强大的子类型,我会在下文予以介绍。在接下来的几节我会依次和大家介绍Java容器类中的几种接口。

List

List可以将元素维护在特定的序列中。List接口继承于Collection接口,并在此基础上添加了大量的方法,使得我们可以在List中间进行元素的插入和移动。

List有两种类型分别为:

  1. ArrayList,擅长随机访问元素,但是插入、删除元素较慢
  2. LinkedList,擅长插入、删除和移动元素,但是随机访问元素性能较低。

提示

学过数据结构的朋友们应该都知道,ArrayList是我们平时所使用的数组,而LinkedList就是链表。

数组的存储在内存空间中是连续的。所以在底层,我们可以通过每个元素所占的内存大小以及偏移量计算出每个元素所在的起始地址。但是在删除、插入元素时,由于需要保证数据存储位置的连续性,我们需要对它周围的元素进行搬移,而周围元素的搬移又会引起后续其他元素的搬移需求,所以最终所导致的移动操作很多。

而链表在内存中并不是连续存储的。它是一种逻辑顺序结构,每个链表存储的对象,都会存储下一个元素以及上一个元素的引用,通过引用来进行迭代。在删除、移动和插入时,我们不需要对元素的实际位置进行搬移,仅仅需要改变引用就可以了。但是由于它是逻辑上的顺序表,我们不能够静态的计算它的位置,只能一个一个的寻找,所以它的随机存取性能较低。

List接口的实例化对象可以使用Collection的所有方法:

List<String> keywords = new ArrayList<>();
List<String> oldKeywords = new LinkedList<>();
keywords.add("hello");
keywords.add(0, "thank");
oldKeywords.add("you");
oldKeywords.add(0, "very");
keywords.addAll(oldKeywords);
keywords.addAll(2, oldKeywords);
keywords.remove(3);
keywords.removeAll(oldKeywords);
List<String> subKeywords = keywords.subList(0, 1);
keywords.clear();

在使用时,我们会发现ArrayList类型的对象和LinkedList类型对象性能的不同。其中需要注意的是倒数第二行我们使用的subList函数是List接口独有的,它可以获取顺序表的一部分生成一个新的List。

ListIterator

ListIterator是更为强大的Iterator的子类型,但是它仅仅针对List的类进行访问。ListIterator可以进行双向移动、获取迭代器所处元素的前后元素的索引,还可以使用set()方法替换它访问过的最后一个元素。如:

List<String> keywords = new ArrayList<>();
keywords.add("hello");
keywords.add(0, "thank");
ListIterator<String> iterator = keywords.listIterator();
while (iterator.hasPrevious()) {
    System.out.println(iterator.previous());
}
while (iterator.hasNext()) {
    iterator.next();
    iterator.set("you");
}

Stack

Stack实现了栈数据结构,它是一种LIFO(后进先出)的容器。也就是我们先放进栈的元素,在使用时会先获取到最后放入的元素。

Stack<String> stack = new Stack<>();
stack.push("hello");
stack.push("thank");
while (!stack.empty()) {
    System.out.println(stack.pop());
}

Set

Set是一种不保存重复元素的数据结构。如果我们将多个相同元素放入Set中,它仅仅会保存一个。使用Set很适合进行查找操作,Java中提供了一个HashSet类,它的查找速度很快,适合用作快速查找。

在使用时与其他Collection的使用类似:

Set<String> keywords = new HashSet<>();
keywords.add("hello");
keywords.add("thank");
keywords.add("u");
keywords.add("thank");
keywords.add("u");
keywords.add("very");
keywords.add("much");
System.out.println(keywords);

我们在set中加入了一系列的词汇,其中有一些重复词汇,但是在实际输出时我们会发现,并不存在那么多的元素,而仅仅打印不重复元素。

Set有多种实现:

  1. HashSet,使用了散列方式进行存储。
  2. TreeSet,将元素存储在红黑数当中。它会对集合元素进行排序。
  3. LinkedHashSet,使用链表和哈希表来实现Set。

提示

具体的实现我们可以在数据结构的教程中深入了解,在这里我只与大家分享该如何在工程中选取数据结构。比如我们需要获取一个排好序的数列集合。我们就可以使用TreeSet,插入元素后,元素就会按照顺序存储。我们可以很方便的插入或删除元素同时保证排序质量。如果我们不需要排序,只需要保证插入和查找效率,那我们就可以仅仅使用HashSet来进行工作,我们可以很方便的通过它来测试元素的归属性,以及进行一系列的集合操作。

Map

Map可以将一个对象映射到另一个对象。在工程上,它是十分重要的数据结构。比如我们有一系列用户分组对象它保存了用户分组的信息,我们经常需要通过用户分组对象获取这个分组的所有用户。如果我们仅仅通过List进行存储,在查找时的工作量是很大的。因为我们需要从头开始遍历List,判断每个元素是否属于这一分组,但是引入Map后就简单许多了,我们可以将一个对象映射到另一个对象上,所以可以这样实现:

Map<Department, List<User>> departmentUsersMap = new HashMap<>();
departmentUsersMap.put(department1, users1);
departmentUsersMap.put(department2, users2);
//在获取时
List<User> departmentUser = departmentUsersMap.get(department);

提示

这次我们第一次用到了多维的实现,Map中嵌套List,事实上容器的嵌套层次是可以很深的。我们甚至将在Map中的List再嵌套一个Set。但是我们使用何种数据结构,要取决于我们程序的需求,我们数据结构的组合选择需要最大程度的满足我们的需求并尽可能地提高程序的效率。

Map数据结构除了上述映射获取功能以外,还可以获取键、值或键值对的集合,分别使用keySet, value以及entrySet。比如我们要遍历map:

Map<Department, List<User>> departmentUsersMap = new HashMap<>();
departmentUsersMap.put(department1, users1);
departmentUsersMap.put(department2, users2);
for (Map.Entry<String, String> departmentEntry : departmentUsersMap.entrySet()) {
    System.out.println(String.format("key:%s value:%s", departmentEntry.getKey().toString(), departmentEntry.getValue()
            .toString()));
}
时间: 2024-08-01 13:45:41

java 之容器的相关文章

java Map容器怎么定量输出,即当容器到达一定量后输出数据到文件

问题描述 java Map容器怎么定量输出,即当容器到达一定量后输出数据到文件 由于放进Map容器数据量很大,一次性放入的话会导致堆溢出,假如有1010万条记录,如何限制Map的输出记录数,当Map里存放有100万条记录时就输出到文件,依此类推,第11次把最后剩余的10万条也输出到文件. 解决方案 map本身似乎没有这种功能,它就一个容器,怎么会考虑你这特殊的需求.你自己用代码实现吧.内存溢出的话,你可以试试给虚拟机加一下参数,指定最小内存.另外你那个功能的实现我觉得用map实现不见得很好.可以

java-小白问题:JAVA自定义容器类时,一段构造方法不太看得懂,求详细解释

问题描述 小白问题:JAVA自定义容器类时,一段构造方法不太看得懂,求详细解释 我先解释一下题意,现在要设计一个容器类(intArray2),这个容器的本质就是一段数组(暂且只装int值),这个数组可以的索引可以根据使用者传入的数据多少而自动增长,而自动产生新数组覆盖以前的,那么我的疑问就是如下这个....为什么构造方法要像第二图里那么写,而不能照我想的第一张图里的写 我觉的应该如此写(编译通过,运行报错,失败): 正确的写法,为什么要这么写两个: 测试代码,实参传或不传,传什么值,都不影响结果

配置-Java Web容器启动时,SessionFactory注入失败的问题

问题描述 Java Web容器启动时,SessionFactory注入失败的问题 包结构如下:Spring配置如下:UserDAO代码如下:Tomcat一启动,报错如下:org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userDao': Injection of resource dependencies failed; nested exception is org.

JAVA并发容器代码随读

1. java.util.concurrent所提供的并发容器 java.util.concurrent提供了多种并发容器,总体上来说有4类,队列类型的BlockingQueue和 ConcurrentLinkedQueue,Map类型的ConcurrentMap,Set类型的ConcurrentSkipListSet和CopyOnWriteArraySet,List类型的CopyOnWriteArrayList. 这些并发容器都采用了多种手段控制并发的存取操作,并且尽可能减小控制并发所带来的性

浅析Java EE容器重部署时间的调查数据

以下是关于JavaEE容器重部署时间的调查,通过调查结果显示,能得出一些结论,并使广大开发人员从中获益. 近日,Jevgeni Kabanov公布了一份几个月前做的关于JavaEE开发.容器和部署时间的调查结果.结果显示有超过1100人参加了这次问卷调查.这里是下载版的问卷结果.而下面是Jevgeni作出的针对每个问题答案分析的摘要. 问卷的第一个问题是:"你在目前参与的最大型项目中使用的是什么容器?" 图表1:哪个容器是最常使用的? 这里没有包括那些得分不到10分的容器.毫无悬念,A

阿里中间件技术专家魏鹏:基于Java容器的多应用部署技术实践

首届阿里巴巴在线技术峰会(Alibaba Online Technology Summit),将于7月19日-21日 20:00-21:30 在线举办.本次峰会邀请到阿里集团9位技术大V,分享电商架构.安全.数据处理.数据库.多应用部署.互动技术.Docker持续交付与微服务等一线实战经验,解读最新技术在阿里集团的应用实践. 阿里巴巴在线技术峰会专题:https://yq.aliyun.com/activity/97峰会统一报名链接:https://yq.aliyun.com/webinar/j

java容器 collection-JAVA中什么是容器?什么是集合?求大神指导

问题描述 JAVA中什么是容器?什么是集合?求大神指导 容器是什么?跟接口是什么关系?集合又是什么?求大神帮忙?????? 解决方案 Java集合容器主要有以下几类: 1,内置容器:数组 2,list容器:Vetor,Stack,ArrayList,LinkedList, CopyOnWriteArrayList(1.5),AttributeList(1.5),RoleList(1.5),RoleUnresolvedList(1.5), ConcurrentLinkedQueue(1.5),Ar

java 容器 集合 用法

Set,List,Map,Vector,ArrayList的区别 JAVA的容器---List,Map,Set Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set Map ├Hashtable ├HashMap └WeakHashMap Collection接口 Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements).一些 Collecti

java菜鸟求助~collection容器问题

问题描述 java菜鸟求助~collection容器问题 import java.util.*;public class Test { public static void main(String[] args){ Collection c = new ArrayList(); c.add(""hello""); c.add(new Name (""f1""l1"")); c.add(new Integer