概念
类集是一种动态的对象数组,属于各个数据结构的实现类,整个类集的主要组成是一些核心的操作接口:Collection,List,Set,Map,Iterator和Enumeration.以下是部分类集框架的继承结构图:
注: Collection接口设置完内容的目的是为了输出,Map接口设置完内容的目的是为了查找
单值保存
Collection接口
所谓单值保存指的是每一次操作只会保存一个对象.单值保存的最大父类是Collection.在Collection接口中定义了如下的一些常用方法:
No. | 方法 | 描述 |
---|---|---|
1 | public boolean add(E e) | 添加一个数据 |
2 | public void clear() | 清除集合中所有数据 |
3 | public boolean contains(Object o) | 查找数据是否存在 |
4 | public boolean isEmpty() | 判断集合是否为空 |
5 | public Iterator iterator() | 为Iterator接口实例化 |
6 | public boolean remove(Object o) | 删除指定数据 |
7 | public int size() | 取得集合中数据的个数 |
8 | public Object [] toArray() | 将集合变为对象数组 |
一般在开发中很少会直接使用Collection接口,一般会使用它的两个子接口:List和Set.
List接口
List接口是Collection的一个最为常用的允许重复的子接口,此接口的定义如下:
public interface List<E>extends Collection<E>
虽然List接口直接继承了Collection接口,但是List接口对Collection接口进行了大量的扩充,扩充后的主要方法如下:
No. | 方法 | 描述 |
---|---|---|
1 | public E get(int index) | 取得索引位置上的数据 |
2 | public E set(int index, E element) | 修改指定索引位置上的数据 |
3 | public ListIterator listIterator() | 为ListIterator接口实例化 |
List接口有两个常用的子类:
ArrayList
ArrayList是List接口中使用最多的一个子类.按照面向对象的概念,使用ArrayList的主要目的是为List接口实例化,而所有的操作方法都以List接口中定义的为主.
代码示例
import java.util.List;
import java.util.ArrayList;
public class TestDemo{
public static void main(String [] args){
// 实例化List接口
List<String> list = new ArrayList<String>();
// 往集合中添加数据
list.add("Hello");
list.add("World");
list.add("Hello");
System.out.println(list);
}
}
程序运行结果
[Hello, World, Hello]
通过本程序可以发现,List集合中即使存在了重复数据,也可以正常保存,而且数据保存的顺序是存入数据的顺序.
Vector
Vector类是在JDK1.0时就推出的一个实现动态数组的操作类,相对于ArrayList是一个旧的子类.与ArrayList的使用类似.
代码示例
import java.util.List;
import java.util.ArrayList;
public class TestDemo{
public static void main(String [] args){
// 实例化List接口
List<String> list = new Vector<String>();
// 往集合中添加数据
list.add("Hello");
list.add("World");
list.add("Hello");
System.out.println(list);
}
}
程序运行结果
[Hello, World, Hello]
本程序的运行结果和使用ArratLis子类的程序没有任何区别,唯一不同的地方就是使用Vector子类完成List接口的实例化,但是由于所有的操作都是针对接口完成的,只要接口定义的方法不变,子类可以随意更改.
ArrayList和Vector的区别
No. | 区别 | ArrayList | Vector |
---|---|---|---|
1 | 推出时间 | JDK1.2 | JDK1.0 |
2 | 性能 | 采用异步处理方式,性能更高 | 采用同步处理方式,性能相对较低 |
3 | 安全性 | 非线程安全 | 线程安全 |
4 | 输出方式 | Iterator,ListIterator,foreach | Iterator,ListIterator,foreach,Enumeration |
Set接口
Set接口也是Collection接口常用的不允许重复的子接口,此接口的定义如下:
public interface Set<E> extends Collection<E>
Set接口也是直接继承了Collection接口,但是与List接口不同.Set接口只是完整的继承了Collection接口,而没有进行任何方法的扩充.所以Set子类接口中肯定无法使用get()方法取得指定索引的数据.Set接口常用的两个子类:
散列排放的子类
HashSet使用一种散列(无序)的方式保存集合数据.
代码示例
import java.util.HashSet;
import java.util.Set;
public class TestDemo{
public static void main(String [] args){
// 实例化Set接口
Set<String> set = new HashSet<String>();
// 往set中添加数据
set.add("January");
set.add("February");
set.add("March");
set.add("February");
System.out.println(set);
}
}
程序运行结果
[March, January, February]
通过本程序可以发现:使用Set集合保存数据时,集合还总重复的数据并没有被保存,并且保存的数据也是无序的(不是按输入顺序保存)
排序排放的子类
HashSet使用一种排序的方式保存集合数据.
代码示例
import java.util.Set;
import java.util.TreeSet;
public class TestDemo{
public static void main(String [] args){
// 实例化Set接口
Set<String> set = new TreeSet<String>();
// 往set中添加数据
set.add("January");
set.add("February");
set.add("March");
set.add("February");
System.out.println(set);
}
}
程序运行结果
[February, January, March]
此时程序只是更换一个Set接口的子类,运行之后,集合中没有重复数据,并且按照数据的大小排序.
重复元素和TreeSet排序的说明
- 在使用TreeSet实例化Set接口时保存自定义类的对象数据时,要正确排序自定义对象的大小,那么对象所在的类必须实现Comparable接口,设置比较规则.需要注意的是:
一旦使用了Comparable后,类中的所有属性都必须写进排序规则
代码示例
import java.util.Set;
import java.util.TreeSet;
class Person implements Comparable<Person>{
private String name;
private int age;
public Person(){}
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return "姓名:" + this.name + ",年龄:" + this.age + "\n";
}
@Override
public int compareTo(Person per){
if(this.age != per.age){
return per.age > this.age?1:0;
}
else{
if(!per.name.equals(this.name)){
return per.name.compareTo(this.name);
}
else{
return 0;
}
}
}
}
public class TestDemo{
public static void main(String [] args){
Set<Person> set = new TreeSet<Person>();
set.add(new Person("张三", 20));
set.add(new Person("王五", 19));
set.add(new Person("李四", 20));
set.add(new Person("赵六", 18));
set.add(new Person("张三", 20));
System.out.println(set);
}
}
程序运行结果
[姓名:李四,年龄:20
, 姓名:张三,年龄:20
, 姓名:王五,年龄:19
, 姓名:赵六,年龄:18
]
在此程序中,Person类实现了Comparable接口,所以Set集合中可以正确的进行排序(由年龄从大到小排序,年龄相同再根据姓名排序),而对于重复的数据,由于通过compareTo()方法比较后的结果为0,所以就不再进行保存.所以有一个结论: TreeSet子类依靠Comparable中的compareTo()方法的返回值是否为0来判断是否为重复元素
- 虽然TreeSet可以依靠Comparable进行重复元素判断,但是HashSet子类却无法依靠Comparable接口进行重复元素判断.实际上所有重复元素的判断依赖于Object类的两个方法.
1. hash码:public int hashCode();
2. 对象比较:public boolean equals(Object obj).
这两个方法可以由Eclipse自动生成.
在进行对象的比较过程中,首先会使用hasCode()与已经保存在集合中的对象的hashCode()进行比较,如果代码相同,则在与equals()方法进行属性的依次判断,如果全部相同,则为相同元素.
代码示例
没有实现equals()和hashCode()方法的覆写
import java.util.HashSet;
import java.util.Set;
class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return "姓名:" + this.name + ", 年龄:" + this.age + "\n";
}
}
public class TestDemo{
public static void main(String [] args){
Set<Person> set = new HashSet<Person>();
set.add(new Person("张三", 20));
set.add(new Person("李四", 21));
set.add(new Person("王五", 18));
set.add(new Person("张三", 20));
System.out.println(set);
}
}
程序运行结果
[姓名:王五, 年龄:18
, 姓名:张三, 年龄:20
, 姓名:李四, 年龄:21
, 姓名:张三, 年龄:20
]
此程序没有实现equals()和hashCode()方法的覆写,在HashSet集合中出现重复数据.
实现equals()和hashCode()方法的覆写
import java.util.HashSet;
import java.util.Set;
class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return "姓名:" + this.name + ", 年龄:" + this.age + "\n";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
public class TestDemo{
public static void main(String [] args){
Set<Person> set = new HashSet<Person>();
set.add(new Person("张三", 20));
set.add(new Person("李四", 21));
set.add(new Person("王五", 18));
set.add(new Person("张三", 20));
System.out.println(set);
}
}
程序运行结果
[姓名:李四, 年龄:21
, 姓名:王五, 年龄:18
, 姓名:张三, 年龄:20
]
此程序利用了Object类的equals()和hashCode()方法真正实现了重复元素的判断.
偶对象保存
偶对象指的是一对对象,即两个对象同时保存.这两个对象是按照key = value的形式进行定义的,即可以通过key找到对应的value数据.Map接口就实现这样一个操作的数据结构.
Map接口
Map接口中定义的常用方法
No. | 方法 | 描述 |
---|---|---|
1 | public V put(K key, V value) | 向集合中保存数据 |
2 | public V get(Object o) | 通过指定的key取得对应的value |
3 | public SetkeySet() | 将Map中的所有key以Set集合的方式返回 |
4 | public Set |
新的子类
代码示例
import java.util.Map;
import java.util.Set;
import java.util.HashMap;
public class TestDemo{
public static void main(String [] args){
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("张三", 19); // 保存数据
map.put(null, null); //key为null
map.put("张三", 18); // key重复,value会被新值覆盖
map.put("李四", 19); // 保存数据
map.put("王五", 16); // 保存数据
map.put("赵六", 13); // 保存数据
System.out.println(map.get("张三")); // 取得指定key的数据
Set<String> set = map.keySet(); // 将Map中的所有key以Set集合的方式返回
System.out.println(set); // 输出Set中所有的key值
}
}
程序运行接结果
18
[null, 李四, 张三, 王五, 赵六]
本程序使用Map保存数据时设了两个内容(key, value),然后使用get()方法根据指定的key取得对应的value,而且可以发现Map集合中的key不允许重复,若key有重复,其对应的value值会被新值覆盖.接着使用keySet()方法将Map中的所有key以Set的方式返回,并打印.
旧的子类:Hashtable
HashTable是JDK1.0时推出的一种数据结构,相对于HashMap来说是一个比较旧的子类,使用上极为相似,但也存在着区别.
代码示例
import java.util.Map;
import java.util.Set;
import java.util.Hashtable;
public class TestDemo{
public static void main(String [] args){
Map<String, Integer> map = new Hashtable<String, Integer>();
map.put("张三", 19); // 保存数据
map.put("张三", 18); // key重复,value会被新值覆盖
map.put("李四", 19); // 保存数据
map.put("王五", 16); // 保存数据
map.put("赵六", 13); // 保存数据
System.out.println(map.get("张三")); // 取得指定key的数据
Set<String> set = map.keySet(); // 将Map中的所有key以Set集合的方式返回
System.out.println(set); // 输出Set中所有的key值
}
}
程序运行结果
18
[赵六, 王五, 张三, 李四]
本程序只是将实例化Map接口的子类替换成了Hashtable类,和将Key值为null 的添加语句删除(Hashtable不能设置key为null)否则运行时会出现”NullPointorException”
HashMap和Hashtable的区别
No. | 区别 | HashMap | Hashtable |
---|---|---|---|
1 | 推出时间 | JDK1.2 | JDK1.0 |
2 | 性能 | 采用异步处理方式,性能更高 | 采用同步处理方式,性能相对较低 |
3 | 安全性 | 非线程安全 | 线程安全 |
4 | 设置null | 允许将key或value设置为null | 不允许出现null,否则会出现空指针异常 |
集合的输出
Java的类集框架中给出了4中输出方式:
- Iterator在Collection接口中定义
- ListIterator在List接口中定义
- Enumeration在Vector子类中定义
- foreachJDK1.5的支持
迭代输出
Iterator是专门迭代输出的接口,所谓的迭代输出就是对元素逐个进行输出,判断其是否有内容,如果有内容则把内容取出.
取得Iterator接口的实例化对象的方法:这一操作在Collection接口中已经明确定义,因为Collection继承了一个Iterator接口,在这个Iterator接口中定义了一个方法”Iterator<E> iterator()
“,所以一般情况下会很少关注Iterator接口,直接使用Collection接口定义的Iterator<E> iterator()
方法即可.
Iterator接口中常用方法
No. | 方法 | 描述 |
---|---|---|
1 | public boolean hasNext() | 判断是否有下一个值 |
2 | public E next() | 取出当前元素 |
3 | public void remove() | 移除当前元素 |
代码示例
import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;
public class TestDemo{
public static void main(String [] args){
List<String> list = new ArrayList<String>();
list.add("Hello"); // 添加数据
list.add("World");
list.add("Hello");
// 使用Iterator输出是数据
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
程序运行结果
Hello
World
Hello
此程序利用了Iterator接口进行了输出,而对于Collection的所有子接口,都会存在iterator()方法,即Collection接口的所有子接口都支持Iterator接口输出.
双向迭代输出:ListIterator
Iterator接口可以完成由前向后的单向输出操作,要想完成由前向后和由后向前输出需要由Iterator的子接口ListIterator,ListIterator接口主要扩充了以下两个方法:
- public boolean hasPrevious() : 判断是否由前一个元素
- public E previous() : 取出前一个元素
获得ListIterator实例化对象的方法:使用List接口中定义的方法:
public ListIterator<E> listIterator()
代码示例
import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
public class TestDemo{
public static void main(String [] args){
// 使用ArrayList子类实例化List接口
List<String> list = new ArrayList<String>();
// 添加数据
list.add("Hello");
list.add("World");
list.add("nihao");
list.add("shijie");
// 取得ListIterator实例化对象
ListIterator<String> iterator = list.listIterator();
// 由前向后输出数据
System.out.println("由前向后迭代输出数据:");
while(iterator.hasNext()){
System.out.println(iterator.next());
}
// 由后向前输出数据
System.out.println("由后向前输出数据");
while(iterator.hasPrevious()){
System.out.println(iterator.previous());
}
}
}
程序运行结果
由前向后迭代输出数据:
Hello
World
nihao
shijie
由后向前输出数据
shijie
nihao
World
Hello
本程序使用ListIterator接口完成数据由前向后迭代输出和由后向前迭代输出.但是需要注意的是: 在进行由后向前迭代输出之前必须先完成由前向后的迭代输出,否则将不会输出任何信息
废弃的接口:Enumeration
Enumeration是一个最早的输出接口,成为枚举输出,在JDK1.0推出,在JDK1.5进行扩充,主要是添加了泛型.获得Enumeration接口的实例化对象:依靠Vector子类中定义的方法完成:
public Enumeration<E> elements()
Enumeration接口中定义的两个方法:
1. public boolean hasMoreElements() : 判断是否有下一个元素
2. public E nextElement() : 取出当前元素
代码示例
import java.util.List;
import java.util.Vector;
import java.util.Enumeration;
public class TestDemo{
public static void main(String [] args){
// 使用Vector子类实例化vector接口
Vector<String> vector = new Vector<String>();
// 添加数据
vector.add("Hello");
vector.add("World");
vector.add("nihao");
vector.add("shijie");
// 取得Enumeration实例化对象
Enumeration<String> iterator = vector.elements();
while(iterator.hasMoreElements()){
System.out.println(iterator.nextElement());
}
}
}
程序运行结果
Hello
World
nihao
shijie
本程序使用Enumeration接口完成Vector集合数据的迭代输出.但由于Enumeration本身只能通过Vector类对象实例化,所以 在开发中很少使用Enumeration进行开发,优先考虑的是Iterator接口.
JDK1.5的支持:foreach
对于foreach输出,除了可以进行数据数组的内容输出外,也可以针对集合类完成输出.
代码示例
import java.util.List;
import java.util.ArrayList;
public class TestDemo{
public static void main(String [] args){
// 使用Vector子类实例化list接口
List<String> list = new ArrayList<String>();
// 添加数据
list.add("Hello");
list.add("World");
list.add("nihao");
list.add("shijie");
// 使用foreach完成集合内容的输出
for(String str : list){
System.out.println(str);
}
}
}
程序运行结果
Hello
World
nihao
shijie
本程序使用foreach完成集合内容的输出,代码看起来比较简单,但是还是推荐使用Iterator接口完成集合内容的输出.
Map集合的输出
之前一直在强调:
只要是集合数据的输出都推荐使用Iterator接口完成
,但是Map接口中并没有提供Collection接口中iterator()方法.要实现Map接口通过Iterator输出,需要Map.Entry接口的支持.它的定义如下:
public static interface Map.Entry<K, V>
很明显,这是一个在Map接口中使用static定义的一个内部接口.Map.Entry接口中定义的两个常用方法:
1. public K getKey() : 取得当前的key
2. public V getValue() : 取得当前的value
下面通过一个图形来对比一下Collection和Map接口保存的数据形式.
通过上图的对比可以发现在Map集合和Collection集合中保存的最大区别是:Collection直接保存的是操作对象,而Map集合是将保存的key和value变成一个Map.Entry对象,通过这个对象包装了key和value.根据这一特性,给出Map使用Iterator输出的操作步骤:
1. 使用Map接口中的entrySet()方法,将Map集合变为Set集合
2. 取得了Set接口实例化后就可以使用iterator()方法取得Iterator的实例化对象
3. 使用Iterator迭代找到每一个Map.Entry对象,并进行key和value的分离.
代码示例
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
public class TestDemo{
public static void main(String [] args){
// 实例化Map接口
Map<String, Integer> map = new HashMap<String, Integer>();
// 添加数据
map.put("张三", 20);
map.put("李四", 23);
map.put("王五", 19);
map.put("赵六", 21);
// 将Map集合变为Set集合
Set<Map.Entry<String, Integer>> set = map.entrySet();
// 使用iterator()方法取得Iterator的实例化对象
Iterator<Map.Entry<String, Integer>> iterator = set.iterator();
// 进行key和value分离
while(iterator.hasNext()){
Map.Entry<String, Integer> me = iterator.next();
System.out.println(me.getKey() + "-->" + me.getValue());
}
}
}
程序运行结果
李四–>23
张三–>20
王五–>19
赵六–>21
本程序按照之前给出的输出步骤进行代码,完成Map集合数据的输出.
类集转换
- List和Set的相互转换
使用public boolean addAll(Collection<? extends E> c)
方法完成.
Set和Map的相互转换
Map ——> Set
使用
public Set<Map.Entry<K, V>> entrySet()
方法完成将Map转换成Set集合使用
public Set<K> keySet()
方法完成Map集合中的key转换成Set集合
Stack类
栈是采用先进后出的数据存储方式,每一个栈都包含一个栈顶,每次出栈是将栈顶数据取出.
Stack类的常用方法
No. | 方法 | 描述 |
---|---|---|
1 | public E push(E item) | 入栈操作 |
2 | public E pop() | 出栈操作 |
代码示例
import java.util.Stack;
public class TestDemo{
public static void main(String [] args){
Stack<String> s = new Stack<String>();
// 入栈操作
s.push("Hello");
s.push("World");
// 出栈操作
System.out.println(s.pop());
}
}
程序运行结果
World
通过程序的运行结果可以发现,出栈的顺序和入栈的顺序正好相反(先进后出).
属性操作类:Properties
属性一般针对于字符串数据,并且所有的字符数据都会按照”key = value”的形式保存,属性操作类主要针对于属性文件完成.
Properties类常用方法
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public Properties() | 构造 | 构造一个空的属性类 |
2 | public Properties(Properties defaults) | 构造 | 构造一个指定属性内容的属性类 |
3 | public String getProperty(Stirng key) | 普通 | 根据属性的key取得属性的value |
4 | public Object setProperty(String key, String value) | 普通 | 设置属性 |
5 | public void load(InputStream inStream) | 普通 | 从输入流中取出全部属性内容 |
6 | public void store(OutputStream out, Stirng coments) | 普通 | 将属性内容通过输出流输出,同时声明属性的注释 |
代码示例
- 属性的设置与取得
import java.util.Properties;
public class TestDemo{
public static void main(String [] args){
// 构造空的属性类
Properties p = new Properties();
// 保存属性
p.setProperty("张三", "20");
p.setProperty("李四", "21");
p.setProperty("王五", "19");
// 取得属性
System.out.println(p.getProperty("张三"));
System.out.println(p.getProperty("李四"));
System.out.println(p.getProperty("赵六"));
}
}
程序运行结果
20
21
null
通过本程序,设置完属性之后,可以利用key查找属性,如果属性不存在便返回null.
- 将属性保存到文件中
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;
public class TestDemo{
public static void main(String [] args) throws IOException{
// 构造空的属性类
Properties p = new Properties();
// 保存属性
p.setProperty("zhangsan", "20");
p.setProperty("lisi", "21");
p.setProperty("wangwu", "19");
// 将属性保存到指定属性文件中
OutputStream out = new FileOutputStream(new File("/home/linyimin/DaSi/java/test.properties"));
p.store(out, "Test Info");
}
}
程序运行结果
- 通过属性文件读取内容
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class TestDemo{
public static void main(String [] args) throws IOException{
// 构造空的属性类
Properties p = new Properties();
// 从指定属性文件中读取属性内容
InputStream in = new FileInputStream(new File("/home/linyimin/DaSi/java/test.properties"));
p.load(in);
// 读取属性
System.out.println(p.getProperty("zhangsan"));
System.out.println(p.getProperty("lisi"));
System.out.println(p.getProperty("wangwu"));
}
}
程序运行结果
20
21
19
本程序从一个已经保存好的资源文件中加载所有的属性内容.除此之外,属性的来源还可能是其他的输入流.