Java中的集合主要由两个接口派生,即Collection和Map。其中Collection接口派生出了List和Set集合,各个接口还提供了不同的实现类。如下图所示
Collection接口
此接口是集合类的基本接口,它位于集合的顶层。Collection接口的定义如下
public interface Collection<E> extends Iterable<E>
我们可以发现Collection使用泛型技术,所以在操作时必须制定具体的操作类型,有利于集合的安全性。Collection是List和Set接口的父接口。它声明了多个集合类的核心方法,让我们看一下API
虽然Collection是集合类的基本接口,但是一般不会直接使用Collection接口进行操作,而是使用其子接口。常用的Collection主要子接口如下:
- List<E>:集合中的元素按照索引值来排序,允许存放重复的元素。List集合与数组相似
- Set<E>:集合中的元素不按特定方式排列,不能存在重复的对象,但它的有些实现类能对集合中的元素按照特定方式排序
- Sorted<E>:可以对集合中的元素进行排序
- Queue<E>:队列接口,通常以现金先出的方式排序各个元素
List接口
List是一个有序集合,其元素以线性方式储存,集合中允许存放重复的元素,有两个主要的实现类ArrayList和LinkedList
使用List接口可以精确的控制每个元素插入的位置。也可以通过索引访问List中的元素。它不但继承了Collection接口,还进行了扩展增加了更多方法
void | add(String item, int index) 向滚动列表中索引指示的位置添加指定的项。 |
boolean | addAll(int index, Collection<? extends E> c) 将指定 collection 中的所有元素都插入到列表中的指定位置(可选操作)。 |
E | get(int index) 返回列表中指定位置的元 |
int | indexOf(Object o) 返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。 |
int | lastIndexOf(Object o) 返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。 |
ListIterator<E> | listIterator() 返回此列表元素的列表迭代器(按适当顺序)。 |
E | remove(int index) 移除列表中指定位置的元素(可选操作)。 |
E | set(int index, E element) 用指定元素替换列表中指定位置的元素(可选操作)。 |
List<E> | subList(int fromIndex, int toIndex) 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。 区间是左闭右开的【) |
ListIterator<E> | listIterator(int index) 返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始。 |
数组列表类:ArrayList
public class ArrayList<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, Serializable
通过定义我们可以发现,ArrayList实现了List接口,可以直接通过ArrayList为List接口实例化。ArrayList类是数组列表类,实现了可变长度的数组,允许对集合中的元素进行快速的访问,但是插入和删除速度比较慢
ArrayList集合中,插入一个元素时,列表需要将插入点后面的所有元素向后移动,而在删除一个元素时,需要将被删除元素后面的所有元素向前移动
向集合中添加元素
在Collection接口中就已经定义了向集合添加元素的两个方法 add()和addAll()方法
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class ArrayListDemo01 {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<String>(); //通过ArrayList实例化Collection
List<String> list = new ArrayList<String>(); //通过ArrayList实例化List
collection.add("1"); //添加元素
collection.add("2"); //添加元素
collection.add("3"); //添加元素
System.out.println("collection集合:"+collection);//打印集合中的元素
list.add("A"); //添加元素
list.add("C"); //添加元素
list.add(1,"B"); //向指定位置添加元素
list.addAll(0,collection); //向指定位置添加一个集合的所有元素
System.out.println("list集合:"+list); //打印集合中的元素
}
}
collection集合:[1, 2, 3]
list集合:[1, 2, 3, A, B, C]
删除集合中的元素
ArrayList集合提供了专门用于删除元素的方法,Collection接口提供了remove()和removeAll()方法。在List接口中还提供了可以删除指定位置元素的方法remove(int index)。
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<String>(); //通过ArrayList实例化Collection
List<String> list = new ArrayList<String>(); //通过ArrayList实例化List
collection.add("1"); //添加元素
collection.add("2"); //添加元素
collection.add("3"); //添加元素
System.out.println("collection集合:"+collection);//打印集合中的元素
list.add("A"); //添加元素
list.add("C"); //添加元素
list.add(1,"B"); //向指定位置添加元素
list.addAll(0,collection); //向指定位置添加一个集合的所有元素
System.out.println("list集合删除前:"+list); //打印集合中的元素
list.remove("B"); //删除元素B
list.remove(3); //删除指定位置的元素
list.removeAll(collection); //删除指定集合的元素
System.out.println("list集合删除后:"+list); //打印集合中的元素
}
}
collection集合:[1, 2, 3]
list集合删除前:[1, 2, 3, A, B, C]
list集合删除后:[C]
链表类:LinkedList类
LinkedList是链表类,采用链表结构保存元素。链表结构的优点是便于向集合插入和删除元素,因为在插入或删除元素时,不需要移动任何元素。在程序中,若经常需要想集合插入或删除元素时,使用LinkedList实例化List接口是较好的选择。但若需要随机访问集合中的元素时,LinkedList集合就显得相对较慢了。
public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, Serializable
从定义我们可以发现LinkedList实现了List接口,可以存放null元素,LinkedList类还提供了更对的方法,这些方法可以使LinkedList作为堆栈,队列或双向队列
Set接口
Set是一个不包含重复元素的Collection,Set允许包含null元素,但只允许同一个Set集合只含有一个null元素,也就是说Set集合不能包含重复的元素。
下面通过两个例子比较一下HashSet类
- 没有重写equals()和hashCode()方法的HashSet集合
import java.util.*;
class Person {
private String name; //定义name属性
private int age; //定义age属性
public Person(){
} //无参构造方法
public Person(String name,int age){ //有参构造方法,初始化成员属性
this.name=name;
this.age=age;
}
public String toString(){ //重写toString()方法
return ("姓名:"+name+",年龄:"+age+"\n");
}
}
public class Demo {
public static void main(String[] args) {
Set<Person> set = new HashSet<Person>(); //通过HashSet实例化Set
set.add(new Person("张三",21)); //添加元素
set.add(new Person("李四",23)); //添加元素
set.add(new Person("张三",21)); //添加重复元素
set.add(new Person("张三",21)); //添加重复元素
set.add(new Person("王五",20)); //添加元素
set.add(new Person("赵六",20)); //添加元素
System.out.println(set); //输出集合中所有的元素
}
}
[姓名:张三,年龄:21
, 姓名:赵六,年龄:20
, 姓名:张三,年龄:21
, 姓名:李四,年龄:23
, 姓名:张三,年龄:21
, 姓名:王五,年龄:20
]
从程序的运行结果来看,HashSet集合中存在3个张三,这与上面所说的Set集合不允许存在两个相同的元素明显矛盾。原因是这三个"张三"是不再同一内存地址的对象!使用Person类中默认的equals()方法比较结果是flase,所以HashSet集合认为他们是不同的元素。
在Person类中并没有重写equals()方法,所以调用的是Object类的equals()方法。为了更好的比较两个元素是否相等,最好是重写equals()和hashCode()方法
- 重写equals()和hashCode()方法的HashSet集合
import java.util.*;
class Person {
private String name; //定义name属性
private int age; //定义age属性
public Person(){
} //无参构造方法
public Person(String name,int age){ //有参构造方法,初始化成员属性
this.name=name;
this.age=age;
}
public String toString(){ //重写toString()方法
return ("姓名:"+name+",年龄:"+age+"\n");
}
public boolean equals(Object o){ //重写equals()方法
if (this==o) { //判断当前对象与指定对象是否相等
return true;}
if (o==null) { //判断指定对象是否为空
return false;}
if (!(o instanceof Person)) { //判断指定对象是否为Person的实例
return false;}
Person per=(Person)o; //将指定对象转为Person实例
if (this.name.equals(per.name)&&this.age==per.age) { //比较对象的属性是否相等
return true;
}else{
return false;
}
}
public int hashCode(){ //重写hashCode()方法
final int prime=13;
int result=13;
result=prime*result+((name==null)?0:name.hashCode());
result=prime*result+age;
return result;
}
}
public class Demo {
public static void main(String[] args) {
Set<Person> set = new HashSet<Person>(); //通过HashSet实例化Set
set.add(new Person("张三",21)); //添加元素
set.add(new Person("李四",23)); //添加元素
set.add(new Person("张三",21)); //添加重复元素
set.add(new Person("张三",21)); //添加重复元素
set.add(new Person("王五",20)); //添加元素
set.add(new Person("赵六",20)); //添加元素
System.out.println(set); //输出集合中所有的元素
}
}
[姓名:李四,年龄:23
, 姓名:赵六,年龄:20
, 姓名:张三,年龄:21
, 姓名:王五,年龄:20
]
重写equals()和hashCode()方法后,集合中的重复元素就没有了,说明 重写equals()和hashCode()方法后可以防止元素的重复。 当我们在程序设计中,需要将某个类的对象保存到HashSet集合中时,该类最好重写equals()和hashCode()方法
TreeSet
public class TreeSet<E>extends AbstractSet<E>implements NavigableSet<E>, Cloneable, Serializable
TreeSet类实现了java.util包中的Set接口和SortedSet接口。 TreeSet集合中的元素在默认情况下是升序排序(自然排序)。若想自定义自己的排序方式,可以使用TreeSet类的构造方法TreeSet(Comparator comparator)
- 验证下TreeSet集合的默认排序
public class Demo1 {
public static <E> void main(String[] args) {
TreeSet<Integer> treeSet = new TreeSet<Integer>(); //通过TreeSet实例化Set
treeSet.add(4);
treeSet.add(2);
treeSet.add(3);
treeSet.add(6);
treeSet.add(3); //添加重复元素
treeSet.add(6); //添加重复元素
treeSet.add(9);
System.out.println(treeSet);
}
}
[2, 3, 4, 6, 9]
从结果来看,
TreeSet集合不允许添加重复元素,而且向TreeSet集合添加元素的顺序是无序的,但是输出却是有序的,默认为升序排序,也就是自然排序
- 自定义的类排序
import java.util.*;
import com.sun.org.apache.regexp.internal.recompile;
class Person implements Comparable<Person>{ //创建对勾对象进行自定义排序必须实现Comparable接口
private String name; //定义name属性
private int age; //定义age属性
public Person(){
} //无参构造方法
public Person(String name,int age){ //有参构造方法,初始化成员属性
this.name=name;
this.age=age;
}
public String toString(){ //重写toString()方法
return ("姓名:"+name+",年龄:"+age+"\n");
}
@Override
public int compareTo(Person p) { //重写Comparable接口的compareTo()方法
if(this.age>p.age){
return 1;
}else if(this.age<p.age){
return -1;
}else{
return this.name.compareTo(p.name);
}
}
}
public class Demo {
public static void main(String[] args) {
TreeSet<Person> set = new TreeSet<Person>(); //通过HashSet实例化Set
set.add(new Person("张三",21)); //添加元素
set.add(new Person("李四",23)); //添加元素
set.add(new Person("张三",21)); //添加重复元素
set.add(new Person("张三",21)); //添加重复元素
set.add(new Person("王五",20)); //添加元素
set.add(new Person("赵六",20)); //添加元素
System.out.println(set); //输出集合中所有的元素
}
}
[姓名:王五,年龄:20
, 姓名:赵六,年龄:20
, 姓名:张三,年龄:21
, 姓名:李四,年龄:23
]
如果想让自定义类创建的多个对象添加到TreeSet中,就必须让该自定义类实现Comparable接口。没有实现这个接口的类只能向TreeSet集合中添加一个对象。若强行添加多个对象,会抛出ClassCastException异常
集合的输出
在Java中,主要提供了以下几种输出方式
- Iterator:迭代器输出
- foreach:增强for循环,这是JDK1.5后新增的输出。常用
迭代输出:Iterator接口
Iterator接口是一个提供便利各种集合类型的迭代器。有的集合类没有提供get()方法,就可以用迭代器来获取集合元素的信息。所有的Collection接口的子接口,子类都支持Iterator迭代器,可调用集合类中的iterator()方法即可获得该集合的迭代器
输出集合中的所有元素
Collection子类都提供iterator()为Iterator实例化,所以直接调用该方法就可以使用Iterator接口输出
import java.util.*;
public class Demo {
public static void main(String[] args) {
LinkedList <String> link = new LinkedList<String>();
link.add("A");
link.add("B");
link.add("C");
link.add("E");
link.add("D");
Iterator<String> iterator = link.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next()+" ");
}
}
}
A B C E D
删除集合中的部分元素
使用Iterator接口还可以删除集合中的元素,可以选择删除全部(removeAll方法)或者部分删除
import java.util.*;
public class Demo {
public static void main(String[] args) {
LinkedList <String> link = new LinkedList<String>();
link.add("A");
link.add("B");
link.add("C");
link.add("E");
link.add("D");
Iterator<String> iterator = link.iterator();
while(iterator.hasNext()){
String str = iterator.next(); //获取元素
if(str.equals("A")){
iterator.remove(); //删除元素
}else{
System.out.print(str+" ");
}
}
}
}
B C E D
Java新支持:foreach()
在JDK1.5以后,可以使用增强for循环foreach()循环
import java.util.*;
public class Demo {
public static void main(String[] args) {
LinkedList <String> link = new LinkedList<String>();
link.add("A");
link.add("B");
link.add("C");
link.add("E");
link.add("D");
for (String string : link) {
System.out.print(string+" ");
}
}
}
A B C E D
虽然使用foreach可以很便捷的输出集合中的所有程序,但是在程序设计中尽量使用Iterator输出集合的元素。因为那是标准的方式我们在下一节把Map抽出来单独研究