java 之容器
在Java中,我们想要保存对象可以使用很多种手段。我们之前了解过的数组就是其中之一。但是数组具有固定的尺寸,而通常来说,程序总是在运行时根据条件来创建对象,我们无法预知将要创建对象的个数以及类型,所以Java推出了容器类来解决这一问题。
Java的容器类分为List
,Set
,Queue
和Map
。我们也称它们为集合类(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容器类库是用来保存对象的,他有两种不同的概念:
Collection
。独立元素的序列,这些元素都服从一条或多条规则。List
、Set
以及Queue
都是Collection
的一种,List
必须按照顺序保存元素,而Set
不能有重复元素,Queue
需要按照排队规则来确定对象的顺序。Map
。Map
是键值对类型,允许用户通过键来查找对象。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
包中的Arrays
和Collections
类中还提供了很多实用的方法,如:
- Arrays.asList()方法可以接受数组或逗号分隔的元素列表,并将其转化为一个
List
对象。 - Collections.addAll()方法接受一个
Collection
对象和一个数组或以逗号分隔的列表将其加入到集合当中。 - 等等
我们可以这样使用:
- //使用asList方法生成list
- List<String> keywords = Arrays.asList("hello", "thank", "you");
- //我们要将其他元素加入到keywords容器中
- Collections.addAll(keywords, "very", "much");
使用asList()方法输出产生的对象需要注意一些问题,因为在这种情况下,它的底层表示仍然是数组,因此我们是不能够该表它的尺寸的。这时使用add
和delete
方法可能会引发改变数组尺寸的尝试,会在运行时得到Unsupported Operation
错误。
如果要使用可以改变尺寸的List,我推荐大家在获取到asList()方法的输出后,再构造一个ArrayList。
迭代器
从之前的Collection接口中可以看出,任何容器类,都可以以某种方式插入、获取和删除元素。add()作为最基本的插入元素方法而get()则是基本取元素的方法。
但是如果我们仅仅使用get和add方法来进行元素操作,如果将一个类的方法实现了,如果想要将相同的代码用在其他容器类中就会遇到问题,那么我们如何解决这一问题呢?
在这里我们就引入了面向对象的设计模式迭代器
模式。迭代器是一个对象,它的工作是遍历并选择序列中的对象。客户端不需要知道序列的底层架构。
Java的Iterator的定义如下:
- public interface Iterator<E> {
- boolean hasNext();
- E next();
- void remove();
- }
我们可以使用:
next()方法来获取序列的下一个元素。
hasNext()检查序列中是否还有元素。
使用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有两种类型分别为:
- ArrayList,擅长随机访问元素,但是插入、删除元素较慢
- 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有多种实现:
- HashSet,使用了散列方式进行存储。
- TreeSet,将元素存储在红黑数当中。它会对集合元素进行排序。
- 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()));
- }
java 之容器的更多相关文章
- JAVA的容器---List,Map,Set (转)
JAVA的容器---List,Map,Set Collection├List│├LinkedList│├ArrayList│└Vector│ └Stack└SetMap├Hashtable├HashM ...
- Java集合容器简介
Java集合容器主要有以下几类: 1,内置容器:数组 2,list容器:Vetor,Stack,ArrayList,LinkedList, CopyOnWriteArrayList(1.5),Attr ...
- [转载]四大Java EE容器
转载自: https://my.oschina.net/diedai/blog/271367 现在流行的Java EE容器有很多:Tomcat.JBoss.Resin.Glassfish等等.下面对这 ...
- 转 四大Java EE容器(Tomcat、JBoss、Resin、Glassfish)之简单比较
现在流行的Java EE容器有很多:Tomcat.JBoss.Resin.Glassfish等等.下面对这四种Java EE容器进行 ...
- java并发容器(Map、List、BlockingQueue)
转发: 大海巨浪 Java库本身就有多种线程安全的容器和同步工具,其中同步容器包括两部分:一个是Vector和Hashtable.另外还有JDK1.2中加入的同步包装类,这些类都是由Collectio ...
- Spring @Bean注解 (基于java的容器注解)
基于java的容器注解,意思就是使用Java代码以及一些注解,就可以取代spring 的 xml配置文件. 1-@Configuration & @Bean的配合 @Configuration ...
- 各种容器与服务器的区别与联系:Servlet容器、WEB容器、Java EE容器、应用服务器、WEB服务器、Java EE服务器
1.容器与服务器的联系 如上图,我们先来看下容器与服务器的联系:容器是位于应用程序/组件和服务器平台之间的接口集合,使得应用程序/组件可以方便部署到服务器上运行. 2.各种容器的区别/联系 2-1.容 ...
- 四大Java EE容器(Tomcat、JBoss、Resin、Glassfish)之简单比较
转自:http://www.cxybl.com/html/bcyy/java/201106241007.html 现在流行的Java EE容器有很多:Tomcat.JBoss.Resin.Glassf ...
- 各种容器与服务器的区别与联系 Servlet容器 WEB容器 Java EE容器 应用服务器 WEB服务器 Java EE服务器
转自:https://blog.csdn.net/tjiyu/article/details/53148174 各种容器与服务器的区别与联系 Servlet容器 WEB容器 Java EE容器 应用服 ...
- 【Java web 容器resin的安装】
#resin的安装 #启动resin #访问resin监听的java web容器端口 resin修改端口监听号
随机推荐
- if(/专线$/.test(name))讲解
如图: 这条语句的意思是:匹配以"专线"结尾的name字符串,满足条件的返回true,否则返回false
- mysql服务无法正常启动
这个时候多半是ini文件出了问题. 1.去检查你的my.ini的保存编码格式是不是ANSI,如果不是将其改为ANSI (一般我们修改my.ini时,都无法直接保存,而是选择另存为在其他目录下,再去替换 ...
- Python3中无法导入ssl模块的解决办法
这个问题,已经困扰我好几天了,本萌新刚开始接触python,想爬取几个网页试试,发现urllib无法识别https,百度后才知道要导入ssl模块,可是发现又报错了. 本人实在无法理解为什么会报错,因为 ...
- Oracle10g以上sysaux表空间的维护和清理
SYSAUX表空间在Oracle 10g中引入,其作为SYSTEM表空间的辅助表空间.之前,一些使用独立表空间或系统表空间的数据库组件,现在SYSAUX表空间中存在.通过分离这些组件,减轻了SYSTE ...
- MySQL中UTF8编码的数据在cmd下乱码
MySQL中UTF8编码的数据在cmd下乱,在数据库ide中看到的却是中文. 其实,原因是cmd用gbk的格式来显示数据,那么我们只需要将utf-8存储的数据用gbk的格式输出到cmd即可. 解决方法 ...
- Java 的异常处理机制
异常是日常开发中大家都「敬而远之」的一个东西,但实际上几乎每种高级程序设计语言都有自己的异常处理机制,因为无论你是多么厉害的程序员,都不可避免的出错,换句话说:你再牛逼,你也有写出 Bug 的时候. ...
- EF实体的部分更新
实现实体的部分更新假设实体InfoHotel如下: public class InfoHotel { public int Id{get;set;} public string Name{get;se ...
- C# 基础问答
1.静态变量和非静态变量的区别? 2.const 和 static readonly 区别? 3.extern 是什么意思? 4.abstract 是什么意思? 5.internal 修饰符起什么作用 ...
- redis在java客户端的操作
redis高性能,速度快,效率高的特点,用来做缓存服务器是很不错的选择.(和memcache相似)redis在客户端的操作步骤: 1.redis单机版操作 1.1通过Jedis对象操作 (1)将安装r ...
- Oracle中表字段相关操作举例
--创建测试表 create or replace table student ( xh ), --学号 xm ), --姓名 sex ), --性别 birthday date, --日期 sal ...