在实际开发中,数组的出现频率并不高,因为数组有一个很大的缺陷:数组长度固定。所以从JDK1.2开始,为了解决Java数组长度的问题,提供了动态的数组实现框架——Java集合类框架。
Java集合类框架实际上就是针对于数据结构的一种实现。
在Java的集合类库里面(java.util)包含了两个核心接口,Collection与Map。本次我们介绍的是Collection接口。
1. Collecion接口
首先我们看一下JDK给出的Collection接口定义:
public interface Collection<E> extends Iterable<E>
Collection接口是一个泛型接口,可以很好的避免向下转型时的ClassCastException异常。并且继承了Iterable接口,继承该接口的用途下面会讲到。
另外,Collection接口是保存单个数据的集合最大父接口。即Collection集合每次只能保存一个数据,这个数据可以是基本数据类型,自定义数据类型(自定义的类也可以),但每次只能保存一个,不能一次保存多个,但可以保存多次啊。而且,实现Collection接口的子类也遵循该性质。
Collection接口定义了如下常用方法:
// 返回集合长度
public int size();
// 集合是否为空,空返回true,反之false
public boolean isEmpty();
// 集合中是否包含指定元素
boolean contains(Object o);
// 返回一个Iterator接口对象,用于集合的输出,后面会讲
Iterator<E> iterator();
// 将集合编程对象数组返回
Object[] toArray();
// 将指定元素添加进集合中
boolean add(E e);
// 在集合中删除指定元素
boolean remove(Object o);
虽然Collection接口定义了如此众多的方法,但是在实际工程中我们并不会直接使用它,而是使用它的子接口List,Set。
Colletion接口继承关系如下:
Collection接口中定义的方法,子接口都有。
2. List接口
我们首先看看List接口的定义:
public interface List<E> extends Collection<E>
在实际开发中,List接口的使用占到了Collection集合系列的80%以上,在进行集合处理的时候,优先使用List接口。
因为在List接口中定义了两个很有用的扩充方法:
// 取得集合中指定下标的元素
E get(int index);
// 设置集合中指定下标元素
E set(int index, E element);
List接口有三个常用子类,分别为ArrayList,Vector,LinkedList。
继承关系如下:
接下来我们详细介绍一下这三个子类。
2.1 ArrayList类(重要)
ArrayList类可以添加重复的元素,也可以装入空(null)。
ArrayList是针对List接口的一个实现类,底层由数组实现,对应数据结构中的顺序表。因为它是Collection接口的实现类,所以自然是可以自动扩容,长度可变的。
JDK给出的ArrayList类定义以及常用方法:
// 类定义
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable // List接口的扩充get方法 public E get(int index) // List接口的扩充set方法 public E set(int index, E element)
关于ArrayList类中方法的具体实现大家可以参考一下这篇文章:
我们看下面的例子:
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
// 定义元素类型为String的ArrayList集合类
List<String> list = new ArrayList<>();
// 给集合类中添加元素,使用Collectio接口中定义的方法add
list.add("hello");
list.add("world");
list.add("hello");
list.add("java");
list.add(null);
// 这里为了简单起见,我们直接打印集合类,实际上,我们应该使用Iterator接口的对象进行集合类的输出
System.out.println(list);
}
}
运行结果:
这也证明了ArrayList可以添加重复元素和null。
我们再测试一下ArrayList的其他方法
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
// 定义元素类型为String的ArrayList集合类
List<String> list = new ArrayList<>();
// 给集合类中添加元素,使用Collectio接口中定义的方法add
list.add("hello");
list.add("world");
list.add("hello");
list.add("java");
list.add(null);
// size()
System.out.println("list size is : "+list.size());
// isEmpty()
System.out.println("list.isEmpty(): "+list.isEmpty());
// remove()
System.out.println("list.remove(): "+list.remove("world"));
System.out.println("after remove [world] :"+list);
// contains()
System.out.println("list.contains(): "+list.contains("world"));
// get()方法进行集合元素输出
System.out.println("print by list.get() :");
for(int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
运行结果:
另外注意一点的就是,我们声明ArrayList的时候的格式为:
List<T> list = new ArrayList<>();
最好不要采用:
Collection<T> list = new ArrayList<>();
虽然ArrayList是Collection接口的实现子类,但是List接口里对Collection接口进行了方法的扩充,而且这样的转换还要设计向下转型,所以最好使用List接口进行ArrayList对象的接收。
ArrayList集合类除了接收基本数据类型,也可以接收自定义数据类型,如下面的例子:
import java.util.ArrayList;
import java.util.List;
class Person {
public int age;
public String name;
public Person(String name, int age) {
super();
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "[name: "+this.name+", age: "+this.age+"]";
}
}
public class Test {
public static void main(String[] args) {
// 定义元素类型为String的ArrayList集合类
List<Person> list = new ArrayList<>();
// 给集合类中添加元素,使用Collectio接口中定义的方法add
list.add(new Person("xucc", 10));
list.add(new Person("lixx", 10));
list.add(new Person("zhss", 10));
// get()方法进行集合元素输出
System.out.println("print by list.get() :");
for(int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
运行结果:
最后要注意一点:ArrayList类的remove,contains都调用了对象的equals方法,如果没有覆写equals至想要的效果,会导致这两个方法无法判断出指定的对象,导致结果出现偏差。
2.2 Vector类(使用较少)
Vector类是JDK1.0提出的,而上面介绍了的ArrayList是JDK1.2提出的。它的定义如下:
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Vector类的的使用方法,底层实现形式都与ArrayList类一致,这里就不做赘述了。但是在细节上,两者还是有很大差距的:
- ArrayList是异步处理,性能更高。而Vector是同步处理,性能较低。
- ArrayList是非线程安全,Vector是线程安全(Vector线程安全的手段非常暴力,即所有的方法全加锁,为synchronized方法,虽然很安全,但这也导致了Vector效率极其低下)。
- 在输出形式上,ArrayLit支持Iterator,ListIterator,foreach三中输出方式。Vector拥有这三种之外,还多了一种,Enumeration(枚举输出)
在以后使用的时候优先考虑ArrayList,因为其性能更高,实际开发时很多时候也是每个线程拥有自己独立的集合资
源。如果需要考虑同步也可以使用concurrent包提供的工具将ArrayList变为线程安全的集合。
虽然日常中Vector的出场率不高,但是它却有一个很重要的子类——Stack类。
2.3 LinkedList类
LinkedList的底层实现为双链表,使用发发与ArrayList一模一样。
定义如下:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
ArrayList封装的是数组,LinkedList封装的是链表。
ArrayList的remove时间复杂度为O(n),效率较低,但是查找一个元素get时间复杂度为O(1)。
LinkedList的remove时间复杂度O(1),效率高,但是查找一个元素get时间复杂度为O(n)。