Collection接口
1.首先,我们要对Collection接口有以下几点认知:
在Java类库中,集合类的基本接口就是Collection接口,它包含了集合的基本操作和属性,是高度抽象出来的。 同时,Collection也是单个集合保存的最大父接口。另外,Collection中有一个iterator()函数,它的作用是返回一个Iterator接口。通常,我们通过Iterator迭代器来遍历集合。
2.定义:
public interface Collection extends Iterable
3.Collecntion接口的基本方法:
其中使用最频繁的是add()、iterator()方法
Iterator iterator():用于访问集合中每个元素的迭代器
4.Collection接口的定义:
其子接口均有add()、iterator()方法
5.List接口
(1)定义:List接口是我们进行集合处理时优先考虑的。List是一个有序的队列,每一个元素都有它的索引。第一个元素的索引值是0。
(2) 特点:
a.List接口继承于Collection接口,它可以定义一个允许重复的有序集合。因为List中的元素是有序的,所以我们可以通过使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
b.List接口为Collection直接接口。List所代表的是有序的Collection,即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
c.在List接口中,通过ListIterator()返回一个ListIterator对象
d.实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。
(2)与Collection接口的不同点:a.扩充了两个新的重要方法
public E get(int index); -根据索引取得保存的数据
public E set(int index,E element);–修改数据
b.
(3)类图关系:
(4)ArrayList
a.它是一个针对于List接口的数组实现,是一个动态数组,也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括null。每一个ArrayList都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。
b.
优点:对于get()和set()方法的调用花费为常数时间。ArrayList擅长于随机访问。
缺点:新项的插入和现有项的删除代价昂贵。且ArrayList是非同步的
(5)LinkedList
a.LinkedList是一个双向链表, 它除了有ArrayList的基本操作方法外还额外提供了get(),remove(),insert()方法在LinkedList的首部或尾部。
b.优点:新项插入与删除的开销均很小。
缺点:不易被索引。
(6)对比ArrayList和LinkedList:
在末端添加一些项构造一个List
public static void makelist(List<Integer> lst,int N){
lst.clear();
for(int i=0;i<N;i++){
lst.add(i);
}
}
不管是ArrayList还是LinkedList作为参数被传递,此方法的运行时间均为o(N)。
在表的前端添加一些项构造一个List
public static void makelist(List<Integer> lst,int N)
{
lst.clear();
for(int i=0;i<N;i++){
lst.add(0,i);
}
}
对于LinkedList,它的运行时间为o(N) ;对于ArrayList,运行时间为o(N^2)。
计算List中的所有数的和:
public static int sum(List<Integer> lst,int N)
{
int total=0;
for(int i=0;i<N;i++){
total+=lst.get(i);
}
return total;
}
对于ArrayList来说,运行时间为o(N);对于LinkedList来说,运行时间为o(N^2)。
(7)Vector
与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。
(8)Stack
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
(9)总结对比:
6.Set接口
(1)a.Set是一种不包括重复元素的Collection。它维持它自己的内部排序,所以随机访问没有任何意义。与List一样,它同样允许null的存在但是仅有一个。由于Set接口的特殊性,所有传入Set集合中的元素都必须不同,同时要注意任何可变对象,如果在对集合中元素进行操作时,导致e1.equals(e2)==true,则必定会产生某些问题。Set接口有三个具体实现类,分别是散列集HashSet、链式散列集LinkedHashSet和树形集TreeSet。
b.Set是一种不包含重复的元素的Collection,无序,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。需要注意的是:虽然Set中元素没有顺序,但是元素在set中的位置是由该元素的HashCode决定的,其具体位置其实是固定的。
c.Set接口并没有对Collection接口进行扩充,即没有get()和set()方法。
(2)类图:
(3)HashSet(散列集)
a. HashSet 是一个没有重复元素的集合。它是由HashMap实现的,不保证元素的顺序(这里所说的没有顺序是指:元素插入的顺序与输出的顺序不一致),而且HashSet允许使用null 元素。
b.HashSet是非同步的,如果多个线程同时访问一个哈希set,而其中至少一个线程修改了该set,那么它必须保持外部同步。 HashSet按Hash算法来存储集合的元素,因此具有很好的存取和查找性能。
c.HashSet的实现方式大致如下,通过一个HashMap存储元素,元素是存放在HashMap的Key中,而Value统一使用一个Object对象。
HashSet使用和理解中容易出现的误区:
a.HashSet中存放null值:
HashSet中是允许存入null值的,但是在HashSet中仅仅能够存入一个null值。
b.HashSet中存储元素的位置是固定的
*
HashSet中存储的元素的是无序的,这个没什么好说的,但是由于HashSet底层是基于Hash算法实现的,使用了hashcode,所以HashSet中相应的元素的位置是固定的。
*
c.必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题
HashSet的具体使用:
class Person implements Comparable<Person>{
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) &&
Objects.equals(age, person.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Person o) {
if(this.age>o.age){
return 1;
}
else if(this.age<o.age){
return -1;
}else{
return this.name.compareTo(o.name);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
public class TEST{
public static void main(String[] args) {
Set<Person> set=new HashSet<>();
set.add(new Person("张三",20));
set.add(new Person("张三",20));
set.add(new Person("李四",26));
set.add(new Person("王五",28));
System.out.println(set);
}
}
(4)TreeSet
a.TreeSet是一个有序集合,其底层是基于TreeMap实现的,非线程安全。
b.TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。当我们构造TreeSet时,若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。
c.注意:TreeSet集合不是通过hashcode和equals函数来比较元素的.它是通过compare或者comparaeTo函数来判断元素是否相等.compare函数通过判断两个对象的id,相同的id判断为重复元素,不会被加入到集合中。
具体使用:
class Person{
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//升序排列
class AscAgeComparator implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
}
//降序排列
class DesAgeCompartor implements Comparator<Person>{
@Override
public int compare(Person o1, Person o2) {
return o2.getAge()-o1.getAge();
}
}
public class TEST4{
public static void main(String[] args) {
Set<Person> set1=new TreeSet<>(new AscAgeComparator());
set1.add(new Person("张三",22));
set1.add(new Person("李四",24));
System.out.println(set1);
Set<Person> set2=new TreeSet<>(new DesAgeCompartor());
set2.add(new Person("张三",22));
set2.add(new Person("李四",24));
System.out.println(set2);
}
}
二、迭代输出:Iterator
1.Iterator接口有三个抽象方法:
(1)public boolean hasNext(); 判断是否有下一个元素;
(2)public E next(); 取得当前元素;
(3)public default void remove(); 删除元素
(1)注意:
a.Iterator只能单向移动。
b.Iterator.remove()是唯一安全的方式来在迭代过程中修改集合;如果在迭代过程中以任何其它的方式修改了基本集合将会产生未知的行为。而且每调用一次next()方法,remove()方法只能被调用一次,如果违反这个规则将抛出一个异常。
2.标准的Iterator使用
public class test{
public static void main(String[] args) {
List<String> lst=new ArrayList<>();
lst.add("Hello");
lst.add("Hello");
lst.add("World");
Iterator<String> Itr=lst.iterator();//实例化Iterator对象
while(Itr.hasNext()){
String str=Itr.next();
System.out.println(str);
}
}
}
3.删除元素
public class test {
public static void main(String[] args) {
List<String> list = new ArrayList<>() ;
list.add("Hello") ;
list.add("Hello") ;
list.add("B") ;
list.add("Bit") ;
list.add("Bit") ;
Iterator<String> iterator = list.iterator() ; // 实例化Iterator对象
while (iterator.hasNext()) {
String str = iterator.next() ;
if (str.equals("B")) {
// 使用集合提供的remove()方法,则会产生ConcurrentModificationException
//list.remove("B") ;
// 使用Iterator的remove方法则不会产生异常
iterator.remove();
continue;
}
System.out.println(str) ;
}
}
}
4.双向迭代接口:ListIterator
(1)ListIterator是一个功能更加强大的迭代器, 它继承于Iterator接口,只能用于各种List类型的访问。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator, 还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator.
(2)功能 :
a.双向移动(向前/向后遍历).
b.产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引.
c.可以使用set()方法替换它访问过的最后一个元素.
d.可以使用add()方法在next()方法返回的元素之前或previous()方法返回的元素之后插入一个元素.
(3)具体使用:
public class test{
public static void main(String[] args) {
List<String> lista=new LinkedList<>();
lista.add("Carl");
lista.add("James");
lista.add("Mary");
List<String> listb=new LinkedList<>();
listb.add("Bob");
listb.add("Doug");
listb.add("Linda");
listb.add("Kris");
ListIterator<String> aIter=lista.listIterator();
Iterator<String> bIter=listb.iterator();
//合并两个链表
while(bIter.hasNext()){
if(aIter.hasNext()){
aIter.next();
}
aIter.add(bIter.next());
}
System.out.println(lista);
//remove every second word from b 在链表b中每隔一个元素删除一个
bIter=listb.iterator();
while(bIter.hasNext()){
bIter.next();//skip one element
if(bIter.hasNext()){
bIter.next();
bIter.remove();
}
}
System.out.println(listb);
//remove all words in b from a
lista.removeAll(listb);
System.out.println(lista);
}
}