这篇文章主要是想记录一下Java中集合的使用,平时写程序的过程中集合也是用的比较多的,一般不是数组就是集合,两者一起使用的也蛮多,集合的话,因为其中元素的个数是可变的,集合的长度也就可以变,因此比数组会更加灵活。
1.集合的分类
觉得可以将集合分为两类:单列集合和双列集合。单列集合又可以分为两类:List集合和Set集合。其中的双列集合也就是Map集合,之所以是双列集合,是因为Map集合中的每一个元素都是由键(key)和值(value)这两部分组成的,而单列集合中的每一个元素都只是一个独立的个体,如果是基本数据类型,那就是一个值,如果是一个引用类型数据,那就是一个类的对象。
单列集合
List集合
Set集合
双列集合(Map集合)
之所以单列集合要分List集合和Set集合,是因为它们有很大的区别:
1.List集合是有序的,Set集合是无序的
所谓有序就是元素存入集合的顺序和元素在集合中的位置是一致的,而无序就是元素存入集合的顺序和元素在集合中的位置是没有关联的
2.List集合是可重复的,而Set集合是不可重复的
关于重复的话就是在集合中可以有多个同样的元素,比如一个集合保存数字的话,那List集合中是可以有两个1的,而Set集合不行,关于可重复这一点,很关键的一点就是List集合中的元素是有索引的,而Set集合中的元素没有,因为有索引,即使是存在两个1,集合也能通过索引分别找到它们,而且不会混淆,而Set集合就不行了,因为没有索引,根本无法区分两个1,因此Set集合是不允许存入两个1的。
上面两点也就是List集合和Set集合的区别,也是它们各自的特点:List集合有序可重复,Set集合无序不可重复
说完了List集合和Set集合的特点,也看一下Map集合的特点:
Map集合中的元素都是一个一个键值对
Map中的元素都是由键值对组成,并且每个元素都是靠键(key)来区分彼此,因此Map集合中的键(key)是不可重复的,而值(value)是可以的,并且每个元素中的值只能由相应的键来取得。
2.List集合
List集合最常见的两个类就是ArrayList和LinkedList,其中对于元素的操作大致都是一样的,提供的方法也都差不多,不过这两个类很大的区别就在于:ArrayList底层是根据数组实现的,而LinkedList底层是根据链表实现的,数组的特点就是查找快,增删漫,而链表的特点就是查找慢,增删快
因为两个类提供的方法都差不多,下面的一些示例就直接使用ArrayList实现了
2.1 增加元素
添加元素使用的就是add(String e)方法
ArrayList<String> arr = new ArrayList<String>();
arr.add("卡卡西");
arr.add("鸣人");
arr.add("佐助");
arr.add("小樱");
arr.add("大和");
arr.add("佐井");
System.out.println(arr);
得到的结果如下:
[卡卡西, 鸣人, 佐助, 小樱, 大和, 佐井]
2.2 删除元素
删除元素一般用的就是remove(int index)方法
arr.remove(0);
System.out.println(arr);
得到的结果如下:
[鸣人, 佐助, 小樱, 大和, 佐井]
2.3 修改元素
修改元素使用的一般是set(int index, String element)方法
arr.set(0, "自来也");
System.out.println(arr);
得到的结果如下:
[自来也, 鸣人, 佐助, 小樱, 大和, 佐井]
2.4 查看方法
一般对应集合元素的查看,也就是对于集合的遍历,而对于集合的遍历,是有很多方法的
2.4.1 增强for循环
for (String s : arr) {
System.out.println(s);
}
得到的结果如下:
卡卡西
鸣人
佐助
小樱
大和
佐井
2.4.2 迭代器循环
Iterator<String> it = arr.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
结果都是一样的,也就不复制了
2.4.3 转数组循环
String[] strArr = new String[arr.size()];
strArr = arr.toArray(strArr);
for (int i = 0; i < strArr.length; i++) {
System.out.println(strArr[i]);
}
2.4.4 直接for循环
for (int i = 0; i < arr.size(); i++) {
System.out.println(arr.get(i));
}
2.5 说明
在使用增强for循环和迭代器循环遍历数组时,不要在循环中使用集合去操作集合元素,因为此时集合已经将控制权交给了迭代器(增强for循环底层其实也是使用的迭代器),而集合再去操作集合中数组的时候,就会引发一个异常 java.util.ConcurrentModificationException
,也就是并发修改异常,因此,在使用迭代器遍历集合时,不要使用集合去操作数组元素。
3.Set集合
Set集合使用的比较多的就是HashSet类,因此下面的一些代码示例也就使用这个类了
3.1 增加元素
增加元素使用add(String e)方法
HashSet<String> hs = new HashSet<String>();
hs.add("卡卡西");
hs.add("带土");
hs.add("琳");
System.out.println(hs);
得到的结果如下:
[带土, 卡卡西, 琳]
很明显可以看到的就是,元素在集合中的位置与集合添加元素的顺序并不一致
再看一下下面的代码
HashSet<String> hs = new HashSet<String>();
hs.add("卡卡西");
hs.add("带土");
hs.add("琳");
System.out.println(hs.add("卡卡西"));
System.out.println(hs);
得到的结果是:
false
[带土, 卡卡西, 琳]
很明显,最后一个元素并没有添加成功,而且在添加时还打印了false,这就是因为Set集合认为最后一个元素在集合中已经有相同的元素存在了,那Set集合是通过什么判断两个元素是一样的呢?答案是hashCode()和equals()方法,如果这两个方法返回的值都是一样的,那就认为这两个元素是一样的,因此在我们开发的过程中,比如要使用一个Set集合添加Student这个类的实例对象,那就一定要重写这两个方法,让Student属性值一样的对象被认为是一样的,属性值不一样的对象就是不一样的,其实实际开发过程中也不用我们自己写,Eclipse会根据我们类的属性值相应地生成这两个方法。
3.2 删除元素
删除元素使用remove(Object o)方法
hs.remove("卡卡西");
System.out.println(hs);
得到的结果如下:
[带土, 琳]
3.3 查看元素
Set集合查看元素也提供了很多方法
3.3.1 增强for循环
for (String s : hs) {
System.out.println(s);
}
得到的结果如下:
带土
卡卡西
琳
3.3.2 迭代器循环
Iterator<String> it = hs.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
结果都是一样的,就不复制了
3.3.3 转数组遍历
String[] strArr = new String[hs.size()];
strArr = hs.toArray(strArr);
for (int i = 0; i < strArr.length; i++) {
System.out.println(strArr[i]);
}
4.Map集合
Map集合使用比较多的就是HashMap了,下面的示例也就用这个类:
4.1 添加元素
添加元素是使用的put(String key, String value)方法
HashMap<String, String> hm = new HashMap<String, String>();
hm.put("卡卡西", "木叶");
hm.put("带土", "木叶");
hm.put("水门", "木叶");
System.out.println(hm);
得到的结果如下:
{水门=木叶, 带土=木叶, 卡卡西=木叶}
接下来再看一下下面的代码
hm.put("带土", "晓");
System.out.println(hm);
结果是这样的:
{水门=木叶, 带土=晓, 卡卡西=木叶}
这样就实现了修改。
4.2 删除元素
删除是使用的remove(Object key)方法
hm.remove("水门");
System.out.println(hm);
得到的结果如下:
{带土=木叶, 卡卡西=木叶}
4.3 查看元素
Map集合遍历集合有两种方式:
1.先得到集合中所有键(key)的集合,然后依次遍历键的集合,并同时获得每个键对应的值(value)
HashMap<String, String> hm = new HashMap<String, String>();
hm.put("卡卡西", "木叶");
hm.put("带土", "木叶");
hm.put("水门", "木叶");
Set<String> keySet = hm.keySet();
for (String key : keySet) {
String value = hm.get(key);
System.out.println(key + "---" + value);
}
得到的结果如下:
水门---木叶
带土---木叶
卡卡西---木叶
2.Map集合中的每一个元素都作为一个Entry对象,循环遍历Entry对象,并同时获取其中的键(key)和值(value)
HashMap<String, String> hm = new HashMap<String, String>();
hm.put("卡卡西", "木叶");
hm.put("带土", "木叶");
hm.put("水门", "木叶");
Set<Entry<String, String>> entrySet = hm.entrySet();
for (Entry<String, String> entry : entrySet) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "---" + value);
}
得到的结果如下:
水门---木叶
带土---木叶
卡卡西---木叶
5.泛型
泛型的出现很大程度上就是为了确定集合中元素是什么类型,在没有泛型之前,集合一般是这样的
HashSet set = new HashSet();
ArrayList list = new ArrayList();
HashMap map = new HashMap();
并且在集合添加元素的时候,任何类型的元素都可以添加进去,基本数值类型和引用数据类型都可以,因为集合的增加元素方法传入的参数是 Object类型的,这样用是非常方便的,但是,在取出元素的时候,可就没那么容易判断该元素究竟是什么类型了,也就比较容易出现类型转换异常,当然可以在取出时使用强制类型转换,但是也是比较麻烦的,为了解决这个问题,主角登场了:泛型
有了泛型之后,集合的声明就是这样的:
HashSet<String> set = new HashSet();
ArrayList<String> list = new ArrayList<String>();
HashMap<String,String> map = new HashMap<String,String>();
就是在集合名的后面加一对尖括号,里面再放该集合准备接受元素的类型,这里面只能是引用类型,所有基本数据类型的话就应该放对应的包装类。
这样写的作用是什么呢?就是规定这个集合只能放该类型的元素数据,如果想放别的类型的数据的话,在编译时期就会给出错误提示,这样就会很方便了,而且,在取出元素时,也不用做强制类型转换,取出的元素类型就是尖括号中写的那个引用类型,其实也是因为,在加入元素的时候就被限制只能放入一种类型的数据了。
6.数据结构(栈和队列)
栈和队列是两种常见的数据结构,它们的特点分别是:
栈:先进后出
队列:先进先出
其实是可以简单地模拟一下栈和队列的基本操作的,因为集合中提供了很多像这样的功能
6.1 栈
import java.util.LinkedList;
public class GenStack<E> {
private LinkedList<E> list = new LinkedList<E>();
public void push(E item) {
list.addFirst(item);
}
public E pop() {
return list.poll();
}
public E peek() {
return list.peek();
}
public boolean hashItems() {
return !list.isEmpty();
}
public int size() {
return list.size();
}
}
测试类如下:
public class GenStackTest {
public static void main(String[] args) {
GenStack<String> gs = new GenStack<String>();
System.out.println("Pushing four items onto the stack.");
gs.push("one");
gs.push("two");
gs.push("three");
gs.push("four");
System.out.println("There are " + gs.size() + " items in the stack.\n");
System.out.println("The top item is " + gs.peek() + "\n");
System.out.println("There are still " + gs.size()
+ " items in the stact.");
System.out.println("Poppint everything...");
while (gs.hashItems()) {
System.out.println(gs.pop());
}
System.out.println("There are now " + gs.size() + " in the stack.");
System.out.println("The top item is " + gs.peek() + "\n");
}
}
6.2 队列
import java.util.LinkedList;
public class GenQueue<E> {
private LinkedList<E> list = new LinkedList<E>();
public void enqueue(E item) {
list.addLast(item);
}
public E dequeue() {
return list.poll();
}
public boolean hasItems() {
return !list.isEmpty();
}
public int size() {
return list.size();
}
public void addItems(GenQueue<? extends E> q) {
while (q.hasItems()) {
list.addLast(q.dequeue());
}
}
}
测试类如下:
public class GenQueueTest {
public static void main(String[] args) {
GenQueue<Employee> empList = new GenQueue<Employee>();
GenQueue<HourlyEmployee> hList = new GenQueue<HourlyEmployee>();
hList.enqueue(new HourlyEmployee("Trump", "Donald"));
hList.enqueue(new HourlyEmployee("Gates", "Bill"));
hList.enqueue(new HourlyEmployee("Forbes", "Steve"));
empList.addItems(hList);
while (empList.hasItems()) {
Employee emp = empList.dequeue();
System.out.println(emp.firstName + " " + emp.lastName);
}
}
}
class Employee {
public String lastName;
public String firstName;
public Employee() {
}
public Employee(String last, String first) {
this.lastName = last;
this.firstName = first;
}
public String toString() {
return firstName + " " + lastName;
}
}
class HourlyEmployee extends Employee {
public double hourlyRate;
public HourlyEmployee(String last, String first) {
super(last, first);
}
}
7.总结
集合因为长度可变,而且可以转为数组,因此使用起来非常灵活,实际运用的时候解决方法也就会比较多,因此碰到问题时还是要多想一下,平时多使用集合的话,一些常用的方法也会很熟悉,运用的时候也能很快就联想到。