参考文章:
安静的技术控— 深入理解Java中的容器
朝向远方–java容器详细解析
Java容器
容器的概念
在Java当中,如果有一个类专门用来存放其它类的对象,这个类就叫做容器,或者就叫做集合,集合就是将若干性质相同或相近的类对象组合在一起而形成的一个整体
容器API的类图结构如下图
再附一张图(其中淡绿色的表示接口,红色的表示我们经常使用的类):
Collection接口定义了存取一组对象的方法,其子接口Set和List分别定义了存储方式
1. Set中的数据对象没有顺序且不可以重复
2. List中的数据对象有顺序且可以重复
Map接口定义了存储“键(key)-值(value)映射对的方法”
Map中的数据对象也是无序
注意:对于Set和Map来说,元素放进去之后是没有顺序的,如果希望元素放进去之后是有顺序的,可以用treeSet和treeMap存储数据。
Collection接口
Collection接口中定义的方法
int size()
boolean isEmpty()
void clear() 清空
boolean contains(Object element) 是否包含某个对象
boolean add(Object element) 添加元素
boolean remove(Object element) 去除元素 其中又调用了equals方法
Iterator iterator()
boolean containsAll(Collection c) 是否包含c的所有元素
boolean addAll(Collection c)
boolean removeAll(Collection c)
boolean retainAll(Collection c) 求和c集合的交集
Object[] toArray() 把各个对象全部转为一个对象类型的数组
Collection方法举例
public class TestCollection1 {
public static void main(String[] args) {
Collection c = new ArrayList();
//可以放入不同的对象
c.add("hello");
c.add(new Integer(100));
//c.remove("hello");
//c.remove(new Integer(100));//Integer类重写了equals方法
System.out.println(c.size());
System.out.println(c);
}
}
运行结果:
2
[hello, 100]
当去掉注释的时候运行结果为:
0
[]
但如果一个类中没有重写equals方法,这个类的两个对象使用remove不会去除,因为指向的不是同一对象
容器类在调用remove、contains等方法时需要比较对象是否相等,会涉及到随想类型的equals方法和hashCode方法,碎玉自定义的类型,需要重写equals和hashCode方法以实现自定义的对象相等规则
注:相等的对象应该具有相等的hash codes
Iterator接口
所有实现了Collection接口的容器类都有一个iterator方法用以返回一个实现了Iterator接口的对象,Iterator对象称作迭代器,用以方便的实现对容器内元素遍历操作
Iterator接口定义的方法
1. boolean hasNext() 判断游标右边是否有元素,如果存在返回真,否则返回假
2. Object next() 返回游标右边的元素并将游标移动到下一个位置
3. void remove() 删除游标左面的元素,在执行完next之后,该操作只能执行一次(不推荐使用iterator的remove()方法,而是推荐使用容器自带的remove方法)
Iterator方法举例
Iterator对象的remove方法是在迭代过程中删除元素为一安全的方法
public class TestIterator {
public static void main(String[] args) {
Collection c = new HashSet();
c.add("java");
c.add("sun");
c.add("IBM");
Iterator i = c.iterator();
while(i.hasNext()){
String s = (String) i.next();
if(s.length()>3)
i.remove();//c.remove(s)可能会出现例外
System.out.print(s+" ");
}
System.out.println();
System.out.print(c);
}
}
运行结果
IBM java sun (无序)
[IBM, sun]
增强的for循环(foreach循环)
增强的for循环对于遍历array或Collection的时候相当简便
任何的foreach语句都能转换成for循环语句,但是不是所有的for语句都能转换成foreach语句
foreach语句的格式:
for(元素类型t 元素变量x : 遍历对象obj){
引用了x的java语句;
}
例子:
//对数组的遍历
int values [] = new int[]{18,62,68,82,65,9};
for (int each : values) {
System.out.println(each);
}
//对集合的遍历
Collection c = new ArrayList();
c.add(new String("aaa"));
c.add(new String("bbb"));
c.add(new String("ccc"));
for(Object o : c){
System.out.println(o);
}
缺陷:
1、对于数组不能方便的访问下标值
2、与使用Iterator相比,不能方便的删除集合中的内容
所以,除了简单的遍历并读出其中的内容外,不建议使用增强for循环
Set接口
Set接口是Collection的子接口,Set接口没有提供额外的方法,但实现Set接口的容器类中的元素是没有顺序的,而且不可以重复(与数学中的集合概念相对应)
Set容器类有HashSet、TreeSet
Set代码举例:
public class TestSet {
public static void main(String[] args) {
Set s =new HashSet();
s.add("hello");
s.add("world");
s.add(new Integer(100));
s.add("hello");//相同的元素不会被加入
System.out.println(s);
Set s1 = new HashSet();
Set s2 = new HashSet();
s1.add("a");s1.add("b");s1.add("c");
s2.add("a");s2.add("b");s2.add("d");//Set和List容器类都具有相同的Constructor(Collection c)
Set sn = new HashSet(s1); //构造方法用以初始化类器
sn.retainAll(s2);
Set su = new HashSet(s1);
su.addAll(s2);
System.out.println(sn);
System.out.println(su);
}
}
运行结果
[hello, 100, world]
[b, a]
[d, b, c, a]
List接口
List接口是Collection的子接口,实现List接口的容器类中的元素是有序的,而且可以重复
List容器中的元素都对应一个整数型的序号记载骑在容器中的位置,可以根据序号存取容器中的元素
List容器类有ArrayList、LinkedList等
List常用方法
object get(int index)
object set(int index,object element)
void add(int index,object element)
int indexOf(Object o)
int lastIndexOf(Object o)
方法举例:
public class TestList {
public static void main(String[] args) {
List l1 = new LinkedList();
for(int i = 0;i<=5;i++){
l1.add("a"+i);
}
System.out.println(l1);
l1.add(3,"a100");
System.out.println(l1);
l1.set(6, "a200");
System.out.println(l1);
System.out.print((String)l1.get(2)+" ");
System.out.println(l1.indexOf("a3"));
l1.remove(1);
System.out.println(l1);
}
}
运行结果:
[a0, a1, a2, a3, a4, a5]
[a0, a1, a2, a100, a3, a4, a5]
[a0, a1, a2, a100, a3, a4, a200]
a2 4
[a0, a2, a100, a3, a4, a200]
List常用算法
类Java.util.Collections提供了一些静态方法实现了基于List容器的一些常用算法
void sort(List) 对List容器内的元素排序
void sort(List) 对List容器内的对象进行随机排列
void reverse(List) 对List容器内的对象进行逆序排列
void dill(List,object) 用一个特定的对象重写整个List容器
void copy(List dest,List src) 将src List容器内容copy到dest List容器
int binarySearch(List,object) 对于顺序的List容器,采用折半查找的方法查找特定对象
算法举例:
import java.util.*;
public class TestList2 {
public static void main(String[] args) {
List l1 = new LinkedList();
for(int i = 0;i<=9;i++){
l1.add("a"+i);
}
System.out.println(l1);
Collections.shuffle(l1);//随机排列
System.out.println(l1);
Collections.reverse(l1);//逆序
System.out.println(l1);
Collections.sort(l1);//排序
System.out.println(l1);
System.out.println(Collections.binarySearch(l1, "a5"));//折半查找
}
}
运行结果
[a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]
[a9, a1, a4, a6, a3, a0, a2, a8, a7, a5]
[a5, a7, a8, a2, a0, a3, a6, a4, a1, a9]
[a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]
5
Comparable接口
所有可以排序的类都实现了java.lang.Comparable接口,Comparable接口中只有一个方法public int compareTo(Object obj)
该方法
返回 0 表示 this == obj
返回正数表示 this > obj
返回负数表示 this < obj
实现了 Comparable 接口的类通过实现comparaTo方法从而确定该类对象的排序方式
如何选择数据结构
衡量标准:读的效率和写的效率
Array对快写慢
Linked改快读慢
Hash两者之间
Map接口
实现Map接口的类用来存储键-值对
Map接口的实现类有HashMap和TreeMap
Map类中存储的键-值对通过键来标识,所以键值不能重复
object put(object key,object value)(key相同会替换,并返回 )
object get(object key)
object remove(object key)
boolean containsKey(object key)
boolean containsValue(object key)
int size()
boolean isEmpty()
void putAll(Map t)
void clear()
Map方法举例
public class TestMap {
public static void main(String[] args) {
Map m1 = new HashMap();
Map m2 = new HashMap();
m1.put("one", new Integer(1));
m1.put("two",new Integer(2));
m1.put("three",new Integer(3));
m2.put("A", new Integer(1));
m2.put("B", new Integer(2));
System.out.println(m1.size());
System.out.println(m1.containsKey("one"));
System.out.println(m2.containsValue(new Integer(1)));
if(m1.containsKey("two")){
int i = ((Integer)m1.get("two")).intValue();
System.out.println(i);
}
Map m3 = new HashMap(m1);
m3.putAll(m2);
System.out.println(m3);
}
}
运行结果
3
true
true
2
{two=2, A=1, B=2, one=1, three=3}
Auto boxing/Auto unboxing
在合适的时机自动打包、解包
自动将基础类型转化为对象
自动将对象转换为基础类型
如上例中的
m1.put("one", new Integer(1));
int i = ((Integer)m1.get("two")).intValue();
也可以写为
m1.put("one", 1);
int i = (Integer)m1.get("two");
注意以下几点:
- 凡是把类对象放到容器中,相应的类都应该实现Object类中的toString()方法;
- 凡是需要进行比较排序的类都应该实现Comparable接口中的compareTo()方法;凡是把类对象放到以树为内部结构的容器中都应该实现Comparable接口中的compareTo()方法
- 凡是把类对象放到以哈希表为内部存储结构的容器中,相应的类必须要实现equals方法和hashCode方法,这样才符合哈希表真实的逻辑功能
- 逻辑上来讲,只要两个对象的内容相同,其地址(hashCode()返回值)以及这两个对象就应该相同(equals())。
泛型
起因:类型不明确
装入集合的类型都被当作Object对待,从而失去自己的时机类型
从集合中取出时往往需要转型,效率低,容易产生错误
解决办法:
在定义集合的时候同时定义集合中对象的类型
可以在定义Collection的时候定义,也可以在循环时用Iterator指定
好处是增强了程序的可读性和稳定性
泛型举例
public class BasicGeneric {
public static void main(String[] args) {
List<String> c = new ArrayList<String>();
c.add("aaa");
c.add("bbb");
c.add("ccc");
for(int i = 0;i<c.size();i++){
String s = c.get(i);//不用再进行强制转换
System.out.println(s);
}
System.out.println();
Collection<String> c2 = new HashSet<String>();
c2.add("aaa");
c2.add("bbb");
c2.add("ccc");
for(Iterator<String> it = c2.iterator();it.hasNext();){
String s = it.next();
System.out.println(s);
}
}
}
在看一个例子,结合Map中例子
public class TestMap2 {
public static void main(String[] args) {
Map<String,Integer> m1 = new HashMap<String,Integer>();
m1.put("one", 1);
m1.put("two", 2);
m1.put("three", 3);
System.out.println(m1.size());
System.out.println(m1.containsKey("one"));
if(m1.containsKey("two")){
//int i = ((Integer)m1.get("two")).intValue();
//利用自动打包解包和泛型
int i = m1.get("two");
System.out.println(i);
}
}
}