【Java心得总结五】Java容器上——容器初探这篇博文中,我对Java容器类库从一个整体的偏向于宏观的角度初步认识了Java容器类库。而在这篇博文中,我想着重对容器类库中的Collection容器做一个着重的探索与总结。

Collection:一个独立元素的序列,这些元素都服从一条或多条规则。(注:Collection其实就是将一组数据对象按照一维线性的方式组织起来)List必须按照插入的顺序保存元素,而set不能有重复元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。

废话不说先上图!

Java容器类库

——摘自《Thinking in java》

从上图中我们可以看出Collection是Java类库中的一个大的子模块,它主要包含了:List和Set两部分

一、List接口

Java中List可以将元素维护在特定的序列中(这里元素既可以是基本类型也可以自定义类【Java心得总结一】Java基本类型和包装类型解析

List接口主要有两种实现类型:

  • 基本的ArrayList,它长于随机访问元素,但是在List的中间插入和移除元素时较慢
  • LinkedList,它通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较慢。

(二者的关系基本就是数据机构中我们学过的顺序存储结构和链表的关系)

ArrayList:

声明:

 import java.util.ArrayList;
import java.util.List; class MyClass{} public class ArrayListTest{
// 声明存放整数的列表,这里必须用包装器类型
List<Integer> l1 = new ArrayList<Integer>();
// 声明存放自定义类的列表
List<MyClass> l2 = new ArrayList<MyClass>();
// 也可以像这样不给出类型参数,编译器会在向l3中加入元素的时候进行判定
List l3 = new ArrayList();
}

声明一个List容器的时候,用到了泛型的知识(【Java心得总结三】Java泛型上——初识泛型【Java心得总结四】Java泛型下——万恶的擦除

还有一点有趣的是,我们声明了ArrayList对象,却将引用赋值给了List引用,从上面图中可以看出ArrayList是List接口的实现,所以我们用List引用来持有ArrayList对象是完全可行的。

方法:

add() 将元素添加到列表中(这是最常用的方法之一,当然了添加的元素类型必须是同一类型或者说继承自相同基类的类型
contains() 确定某个对象是否在列表中
remove() 将某一个对象的引用传递给remove方法,即可移除列表中的一个对象
indexOf() 持有一个对象的引用,则利用该方法可以获得其在列表中的编号
equals() 该方法是Object基类中的一个方法remove方法在删除元素时要要用到这个方法进行匹配
subList() 该方法允许你从较大的列表中创出一个片段
retainAll()    该方法是一种有效的交集操作
removeAll() 移除List中的所有元素
get() 取得指定索引位置的元素

代码示例:

 //: holding/ListFeatures.java
import java.util.*; public class ArrayListTest {
public static void main(String[] args) {
Random rand = new Random(47);
List<String> ls = new ArrayList<String>();
ls.add("s1");
ls.add("s2");
ls.add("s3");
ls.add("s4");
ls.add("s5");
System.out.println("1: " + ls);
String s6 = new String("s6");
ls.add(s6); // Automatically resizes
System.out.println("2: " + ls);
System.out.println("3: " + ls.contains(s6));
ls.remove(s6); // Remove by object
String s3 = ls.get(2);
System.out.println("4: " + s3 + " " + ls.indexOf(s3));
String s7 = new String();
System.out.println("5: " + ls.indexOf(s7));
System.out.println("6: " + ls.remove(s7));
// Must be the exact object:
System.out.println("7: " + ls.remove(s3));
System.out.println("8: " + ls);
ls.add(3, new String("s8")); // Insert at an index
System.out.println("9: " + ls);
List<String> sub = ls.subList(1, 4);
System.out.println("subList: " + sub);
System.out.println("10: " + ls.containsAll(sub));
Collections.sort(sub); // In-place sort
System.out.println("sorted subList: " + sub);
// Order is not important in containsAll():
System.out.println("11: " + ls.containsAll(sub));
Collections.shuffle(sub, rand); // Mix it up
System.out.println("shuffled subList: " + sub);
System.out.println("12: " + ls.containsAll(sub));
List<String> copy = new ArrayList<String>(ls);
sub = Arrays.asList(ls.get(1), ls.get(4));
System.out.println("sub: " + sub);
copy.retainAll(sub);
System.out.println("13: " + copy);
copy = new ArrayList<String>(ls); // Get a fresh copy
copy.remove(2); // Remove by index
System.out.println("14: " + copy);
copy.removeAll(sub); // Only removes exact objects
System.out.println("15: " + copy);
copy.set(1, new String("s9")); // Replace an element
System.out.println("16: " + copy);
copy.addAll(2, sub); // Insert a list in the middle
System.out.println("17: " + copy);
System.out.println("18: " + ls.isEmpty());
ls.clear(); // Remove all elements
System.out.println("19: " + ls);
System.out.println("20: " + ls.isEmpty());
List newLs = new ArrayList<String>();
newLs.add("newS1");
newLs.add("newS2");
newLs.add("newS3");
newLs.add("newS4");
newLs.add("newS5");
ls.addAll(newLs);
System.out.println("21: " + ls);
Object[] o = ls.toArray();
System.out.println("22: " + o[3]);
String[] str = ls.toArray(new String[0]);
System.out.println("23: " + str[3]);
}
}
/*
1: [s1, s2, s3, s4, s5]
2: [s1, s2, s3, s4, s5, s6]
3: true
4: s3 2
5: -1
6: false
7: true
8: [s1, s2, s4, s5]
9: [s1, s2, s4, s8, s5]
subList: [s2, s4, s8]
10: true
sorted subList: [s2, s4, s8]
11: true
shuffled subList: [s4, s2, s8]
12: true
sub: [s4, s5]
13: [s4, s5]
14: [s1, s4, s8, s5]
15: [s1, s8]
16: [s1, s9]
17: [s1, s9, s4, s5]
18: false
19: []
20: true
21: [newS1, newS2, newS3, newS4, newS5]
22: newS4
23: newS4
*///:~

上面的代码我们以String作为List容器的存储对象,基本涵盖了所有基本的ArrayList操作。

我们在代码36行使用了Collections的shuffle方法,它的作用就是将容器中的元素打乱。

LinkedList

正如我们前面提到的,像ArrayList一样LinkedList也实现了基本的List接口,但是在某些方面它要比ArrayList要高效一些,如插入和移除操作,但是在随机访问方面要逊色一些。

另外LinkedList还有一个重要的作用是用来实现栈、队列以及双端队列等数据结构中的一些基本结构

声明:

 import java.util.*;
class MyClass{}
public class LinkedListTest {
// 声明持有String类型的列表,同样这里我们可以用List来持有LinkedList的引用
List<String> ls = new LinkedList<String>();
// 声明持有自定义类型的列表
List<MyClass> lsm = new LinkedList<MyClass>();
// 同样我们可以用LinkedList的引用来持有它
LinkedList<String> lss = new LinkedList<String>();
// 同ArrayList一样我们也可以并不在声明时赋以类型参数,而再赋值时再确定
List l = new LinkedList();
}

上面的声明方式同ArrayList是基本一样的,我们可以利用List接口来持有LinkedList对象的引用,同样也可以用LinkedList自己来持有这个引用

方法:

因为LinkedList也实现了List接口,当然上面ArrayList中的方法也都包含,除此之外它还包含如下方法

getFirst() 返回列表的头(第一个元素),而不移除它。如果List为空则抛出NoSuchElementException
element() 同getFirst()
peek() 与前两个方法的唯一区别是,如果List为空则返回null
removeFirst()   移除并返回列表头,如果List为空,同上抛出相同的异常
remove() 同removeFirst()
poll() 与前两个方法的唯一区别是,如果List为空则返回null
addFirst() 将某个元素插入到列表头部
addLast() 将某个元素插入到列表尾部
add() 同addLast()
removeLast 移除并返回列表的最后一个元素

代码示例

 import java.util.*;

 public class LinkedListTest {
public static void main(String[] args) {
LinkedList<String> ls = new LinkedList<String>();
ls.add("s1");
ls.addFirst("s2");
ls.addLast("s3");
System.out.println(ls);
// Identical:
System.out.println("ls.getFirst(): " + ls.getFirst());
System.out.println("ls.element(): " + ls.element());
// Only differs in empty-list behavior:
System.out.println("ls.peek(): " + ls.peek());
// Identical; remove and return the first element:
System.out.println("ls.remove(): " + ls.remove());
System.out.println("ls.removeFirst(): " + ls.removeFirst());
// Only differs in empty-list behavior:
System.out.println("ls.poll(): " + ls.poll());
System.out.println(ls);
ls.addFirst(new String("s4"));
System.out.println("After addFirst(): " + ls);
ls.offer(new String("s5"));
System.out.println("After offer(): " + ls);
ls.add(new String("s6"));
System.out.println("After add(): " + ls);
ls.addLast(new String("s7"));
System.out.println("After addLast(): " + ls);
System.out.println("ls.removeLast(): " + ls.removeLast());
}
}
/*
[s2, s1, s3]
ls.getFirst(): s2
ls.element(): s2
ls.peek(): s2
ls.remove(): s2
ls.removeFirst(): s1
ls.poll(): s3
[]
After addFirst(): [s4]
After offer(): [s4, s5]
After add(): [s4, s5, s6]
After addLast(): [s4, s5, s6, s7]
ls.removeLast(): s7
*///:~

说完了List,我们一定会想到数据结构中非常重要的两种,队列和栈,在Java中这两种数据结构我们应该怎么实现呢?这就要用到我们刚介绍的LinkedList

栈和队列

栈:

“栈”通常指后进先出(LIFO)的容器,在博文刚开始的图中,我们肯定会发现Java容器类库的结构图中已经包含了栈这个结构,但是基于在Java1.0中设计者的失误导致Stack在新版本的Java中是不推荐使用(这里原因不具体废话了,反正就是不用它便是),然而有了LinkedList,我们完全可以自己很快的写一个,因为说到底栈无非就是操作受限的链表(它只允许在链表的一端进行读写操作)。

代码:

 import java.util.LinkedList;

 public class Stack<T> {
private LinkedList<T> storage = new LinkedList<T>(); public void push(T v) {
storage.addFirst(v);
} public T peek() {
return storage.getFirst();
} public T pop() {
return storage.removeFirst();
} public boolean empty() {
return storage.isEmpty();
} public String toString() {
return storage.toString();
}
} // /:~

这里我们将LinkedList用组合的方式封装在我们的Stack类中,切记这里不能使用继承,因为如果我们的Stack类继承自LinkedList,那么从外部我们就可以获得所有LinkedList的public接口,那么栈就没意义了(其实Java1.0中的Stack的设计就犯了这个错误)

队列:

“队列”是一个典型的先进先出(FIFO)的容器,并且在【Java心得总结五】Java容器上——容器初探博文的图中我们可以看到LinkedList除了实现了List接口还实现了Queue接口,这也就是为什么我们在上面介绍LinkedList时会有那么多功能重复但是名字不同的方法的原因了。

代码:

 import java.util.*;

 public class QueueDemo {
public static void printQ(Queue queue) {
while (queue.peek() != null)
System.out.print(queue.remove() + " ");
System.out.println();
} public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<Integer>();
Random rand = new Random(47);
for (int i = 0; i < 10; i++)
queue.offer(rand.nextInt(i + 10));
printQ(queue);
Queue<Character> qc = new LinkedList<Character>();
for (char c : "Brontosaurus".toCharArray())
qc.offer(c);
printQ(qc);
}
} /*
* Output: 8 1 1 1 5 14 3 1 0 1 B r o n t o s a u r u s
*/// :~

其实从上面我们可以看出我们利用Queue来持有LinkedList对象时,Queue窄化了LinkedList的方法访问权限,以使得有恰当的方法才可以使用。(这很合理,因为同栈一样,队列说到底也是操作受限的链表,它只允许在一端写入另一端读)

List的遍历

像数组一样,我们对容器列表经常做得操作就是遍历,当然了我们可以向遍历数组一样用一个迭代变量进行叠加,然后利用get()方法来取出对应索引位置的元素来达到遍历的目的。但是还有一个更好的方法就是利用Iterator接口(参见【Java心得总结五】Java容器上——容器初探

二、Set接口

Set最最重要的特征就是不保存重复元素。从博文开始的图中我们可以看出Set接口主要有两种具体的实现:HashSet和TreeSet,而在HashSet的基础上还实现了一个LinkedHashSet。

  • HashSet:拥有最快的查询速度,存入HashSet的元素必须定义hashCode()方法
  • TreeSet:保持元素处于排序状态,底层为树结构。使用它可以从Set中提取有序的序列。元素必须实现Comparable接口
  • LinkedHashSet:以插入顺序保持元素,用迭代器进行遍历时会按照插入时的顺序显示,但也必须定义hashCode()方法

Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set是基于对象的值来确定对象的归属性的。

代码示例:

 import java.util.*;

 class SetType {
int i; public SetType(int n) {
i = n;
} public boolean equals(Object o) {
return o instanceof SetType && (i == ((SetType) o).i);
} public String toString() {
return Integer.toString(i);
}
} class HashType extends SetType {
public HashType(int n) {
super(n);
} public int hashCode() {
return i;
}
} class TreeType extends SetType implements Comparable<TreeType> {
public TreeType(int n) {
super(n);
} public int compareTo(TreeType arg) {
return (arg.i < i ? -1 : (arg.i == i ? 0 : 1));
}
} public class TypesForSets {
static <T> Set<T> fill(Set<T> set, Class<T> type) {
try {
for (int i = 0; i < 10; i++)
set.add(type.getConstructor(int.class).newInstance(i));
}
catch (Exception e) {
throw new RuntimeException(e);
}
return set;
} static <T> void test(Set<T> set, Class<T> type) {
fill(set, type);
fill(set, type); // Try to add duplicates
fill(set, type);
System.out.println(set);
} public static void main(String[] args) {
test(new HashSet<HashType>(), HashType.class);
test(new LinkedHashSet<HashType>(), HashType.class);
test(new TreeSet<TreeType>(), TreeType.class);
// Things that don’t work:
test(new HashSet<SetType>(), SetType.class);
test(new HashSet<TreeType>(), TreeType.class);
test(new LinkedHashSet<SetType>(), SetType.class);
test(new LinkedHashSet<TreeType>(), TreeType.class);
try {
test(new TreeSet<SetType>(), SetType.class);
}
catch (Exception e) {
System.out.println(e.getMessage());
}
try {
test(new TreeSet<HashType>(), HashType.class);
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
/*
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[8, 9, 2, 1, 6, 7, 1, 5, 8, 7, 8, 0, 5, 5, 2, 0, 1, 6, 4, 7, 3, 2, 9, 0, 6, 9, 4, 4, 3, 3]
[0, 9, 4, 0, 8, 5, 6, 7, 7, 9, 8, 6, 1, 4, 1, 3, 3, 7, 6, 2, 0, 4, 3, 5, 9, 2, 8, 5, 1, 2]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
java.lang.ClassCastException: SetType cannot be cast to java.lang.Comparable
java.lang.ClassCastException: HashType cannot be cast to java.lang.Comparable
*///:~

这里不得不提的是hashCode()和Comparable接口这会在下篇博文中进行详细解释(【Java心得总结七】Java容器下——Map

代码中SetType作为基类,而HashType和TreeType分别对HashSet和TreeSet做了展示(二者分别实现了hashCode()和Comparable接口)

总结:

这篇博文对Java容器类库中的Collection部分做了详细的阐述,在Set这里还留了个尾巴即hashCode()和Comparable接口的问题(要不篇幅太长了),将在下篇博文进行总结。

【Java心得总结六】Java容器中——Collection的更多相关文章

  1. 7 -- Spring的基本用法 -- 8... 抽象Bean与子Bean;Bean继承与Java继承的区别;容器中的工厂Bean;获得Bean本身的id;强制初始化Bean

    7.8 深入理解容器中的Bean 7.8.1 抽象Bean与子Bean 把多个<bean.../>配置中相同的信息提取出来,集中成配置模版------这个配置模版并不是真正的Bean,因此 ...

  2. Java集合篇六:Map中key值不可重复的测试

    package com.test.collection; import java.util.HashMap; import java.util.Map; //Map中key值不可重复的测试 publi ...

  3. java并发系列(六)-----Java并发:volatile关键字解析

    在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...

  4. Java学习笔记六:Java的流程控制语句之if语句

    Java的流程控制语句之if语句 一:Java条件语句之if: 我们经常需要先做判断,然后才决定是否要做某件事情.例如,如果考试成绩大于 90 分,则奖励一朵小红花 .对于这种“需要先判断条件,条件满 ...

  5. 菜鸡的Java笔记 第六 - java 方法

    前提:现在所讲解的方法定义格式,只属于JAVA 方法定义的其中一种组成方式.而完整的组成方式将随着学习逐步渗透. 1.方法的基本定义 方法(Method)在一些书中也会有人将其说是  函数(Funct ...

  6. Java 学习笔记 (六) Java 定义变量

    这个问题来自于head first一书page68. package com.idea.study; public class Books { //headfirst page68 String ti ...

  7. 【Java心得总结七】Java容器下——Map

    我将容器类库自己平时编程及看书的感受总结成了三篇博文,前两篇分别是:[Java心得总结五]Java容器上——容器初探和[Java心得总结六]Java容器中——Collection,第一篇从宏观整体的角 ...

  8. 【Java心得总结五】Java容器上——容器初探

    在数学中我们有集合的概念,所谓的一个集合,就是将数个对象归类而分成为一个或数个形态各异的大小整体. 一般来讲,集合是具有某种特性的事物的整体,或是一些确认对象的汇集.构成集合的事物或对象称作元素或是成 ...

  9. 获取Spring容器中的Bean

    摘要 SpringMVC框架开发中可能会在Filter或Servlet中用到spring容器中注册的java bean 对象,获得容器中的java bean对象有如下方法 Spring中的Applic ...

随机推荐

  1. 对Java中正则表达式的一些理解

    2016-12-11 22:26:18 正则表达式的意义:正则表达式是一个字符串处理工具,可以对字符串进行查找/提取/分割/替换等等,也可以对用户输入的内容进行匹配. 常用的预定义字符就不解释了,网上 ...

  2. Think in 递归

    网上写递归的文章可以用汗牛充栋来形容了,大多数都非常清晰而又细致的角度上讲解了递归的概念,原理等等.以前学生的时候,递归可以说一直是我的某种死穴,原理,细节我都懂,但是不管是在如何运用或者如何试试算法 ...

  3. html 5 canvas画布整理

    1. 创建canvas画布<canvas id="myCanvas" width="800" height="800" >< ...

  4. androidannotations 简单复制与点击事件(1)

    现在最火的android开发框架 简单描述一下 这一篇简单描述寻找控件以及事件的使用 1.该方法可以不用写setconteview @EActivity(R.layout.activity_main) ...

  5. 网站压力负载测试工具:Web版已逝,只好送大伙单机版

    前言: 最近老板资金周转出了点问题,工资好久没发了,于是暂闲置在家,静待老板弄到钱东山再起的消息: QQ群里,偶尔不时的会有人问,原来的分布式网站负载工具怎么下载不了啦?或者不能用啦?之类的问题. 我 ...

  6. HTML5- Canvas入门(三)

    前两章我们掌握了线段.矩形和多边形的绘制方法,今天我们主要是学习如何绘制圆弧和贝塞尔曲线. 圆弧的绘制 圆弧可以理解为一个圆上的某部分线段,在canvas中,绘制一条圆弧的语法如下: ctx.arc( ...

  7. .NET开发笔记(二十三) 谷歌地图下载

    关于如何将地球经纬度坐标系统转换成程序中常用到的平面2D坐标系统,网上的文章很多,参考http://www.cnblogs.com/beniao/archive/2010/04/18/1714544. ...

  8. 前nginx后Apache+Node反向代理

    前几天一直在被一个问题困扰,机器上跑的站点太多了,Apache上面有十几个,NodeJS的也有一堆,记端口号都要烦死,于是萌生了使用反向代理的想法.出发点貌似太low了,完全不是出于负载均衡.高并发什 ...

  9. Qt5 Crash When Open File With QFileDialog

    问题描述 在使用Qt的QFileDialog这个类,来进行文件的打开和选择的时候, 就在调用的时候, 总是发生崩溃. 而且没有任何的提示性的信息. 而且崩溃的概率很高. 也有不崩溃的情况. 这个问题, ...

  10. jsp的 javascript中 嵌套 html 注释

    看到公司的代码,我也是蛋疼了,各种乱. 还发现有很多的jsp的 javascript中 嵌套 html 注释, 这个可行? 我之前可是没用过. 后面查找各种资料发现,这个也是可行的,主要是为了兼容不支 ...