Java基础知识:Collection接口

*本文是最近学习到的知识的记录以及分享,算不上原创。

*参考文献见文末。

这篇文章主要讲的是java的Collection接口派生的两个子接口List和Set。

目录

  Collection框架

  List接口

  Set接口

1.Collection框架

首先我们综合性地看一下java的Collection接口的框架,如下图:

  *图中绿色表示接口,白色表示类。

List接口和Set接口是Collection接口派生的主要的两个子接口。

2.List接口

List的主要特征

(1)有序(ordered):元素的存取是有序的,保证了取出的元素的顺序与输入的元素顺序保持一致。

例如:

 1 import java.util.ArrayList;
 2 import java.util.LinkedList;
 3 import java.util.Vector;
 4 
 5 public class CollectionDemo {
 6     public static void main(String[] args) {
 7         //ArrayList
 8         ArrayList<String> list=new ArrayList<String>();
 9         list.add("11");
10         list.add("22");
11         list.add("33");
12         for(int i=0;i<list.size();++i){
13             System.out.print(list.get(i)+"  "); //output: 11  22  33  
14         }
15         System.out.println("");
16         //LinkedList
17         LinkedList<String> list2=new LinkedList<String>();
18         list2.add("11");
19         list2.add("22");
20         list2.add("33");
21         for(int i=0;i<list2.size();++i){
22             System.out.print(list.get(i)+"  ");  //output: 11  22  33  
23         }
24         System.out.println("");
25         //Vector
26         Vector<String> list3=new Vector<String>();
27         list3.add("11");
28         list3.add("22");
29         list3.add("33");
30         for(int i=0;i<list3.size();++i){
31             System.out.print(list.get(i)+"  ");  //output: 11  22  33  
32         }
33         System.out.println("");
34     }
35 }
List元素存取有序

(2)索引(index):允许用户根据索引对元素进行精准定位并进行查询、插入、删除等操作。

*所以,对List的遍历,不仅可以通过Iterator,还可以通过索引(index)。

(3)允许重复:允许多个重复的元素存在。

List的主要方法

https://docs.oracle.com/javase/7/docs/api/java/util/List.html 

在提到List接口的各种实现类之前,首先我们回顾一下数据结构中数组和链表的各自的特色。

数组易于对元素的查询、遍历,但对元素的增删操作比较繁琐;链表能够方便地对元素进行增删,但不利于元素的查询、遍历。

2.1 ArrayList

http://www.cnblogs.com/skywang12345/p/3308556.html

ArrayList元素存储的数据结构是数组结构。ArrayList相当于动态数组,既保持了数组查询快速的优点,又不像数组那样对元素的增删慢,所以是最常用的集合。

ArrayList的Clone()

ArrayList的clone()属于浅拷贝

浅拷贝与深拷贝的问题,这与引用对象的存储方式有关系。浅拷贝简单地说,就是把复制一个指向该对象的箭头给你,深拷贝简单地说,就是复制一个对象给你。

当ArrayList中的元素为基本数据类型时,可以说不存在浅拷贝与深拷贝的问题。

例如:

import java.util.ArrayList;

public class ListDemo3 {
    public static void main(String[] args) {
        ArrayList<Integer> a1=new ArrayList<Integer>();
        a1.add(1);
        a1.add(2);
        a1.add(3);
        //基本数据类型: byte short int long float double boolean char
        ArrayList<Integer> a2=(ArrayList<Integer>) a1.clone();
        System.out.println(a1); //[1, 2, 3]
        System.out.println(a2); //[1, 2, 3]
        a1.set(0, 10);
        a2.remove(2);
        System.out.println(a1); //[10, 2, 3]
        System.out.println(a2); //[1, 2]
    }
}
View Code

当ArrayList中的元素为引用数据类型时,要意识到浅拷贝与深拷贝的问题。

例如:

import java.util.ArrayList;

public class ListDemo3 {
    public static void main(String[] args) {
        ArrayList<Student> b1=new ArrayList<Student>();
        Student s1=new Student(001,"zhangsan",22);
        Student s2=new Student(002,"lisi",21);
        Student s3=new Student(003,"wangwu",18);
        b1.add(s1);
        b1.add(s2);
        b1.add(s3);
        //基本数据类型: byte short int long float double boolean char
        //引用数据类型:接口interface, 类class, 数组
        ArrayList<Student> b2=(ArrayList<Student>) b1.clone();
        System.out.println(b1); 
        System.out.println(b2);
        /*
         * [Student [id=1, name=zhangsan, age=22], Student [id=2, name=lisi, age=21], Student [id=3, name=wangwu, age=18]]
         * [Student [id=1, name=zhangsan, age=22], Student [id=2, name=lisi, age=21], Student [id=3, name=wangwu, age=18]]
         */
        b2.remove(2);
        System.out.println(b1);
        System.out.println(b2);
        /*
         * [Student [id=1, name=zhangsan, age=22], Student [id=2, name=lisi, age=21], Student [id=3, name=wangwu, age=18]]
         * [Student [id=1, name=zhangsan, age=22], Student [id=2, name=lisi, age=21]]
         */
        b1.get(0).setName("wangmazi");
        System.out.println(b1);
        System.out.println(b2);
        /*
         * [Student [id=1, name=wangmazi, age=22], Student [id=2, name=lisi, age=21], Student [id=3, name=wangwu, age=18]]
         * [Student [id=1, name=wangmazi, age=22], Student [id=2, name=lisi, age=21]]
         */
    }
}

第一步:创建ArrayList对象b1

 第二步:clone()

从下图中,可以看到左边的箭头和右边的箭头,箭头左边是地址,箭头右边是指向的对象。我们可以发现虽然b1和b2的地址不同,但指向相同的对象。如果仅仅改变左边的箭头,如改变b1的箭头则不会影响到b2,但如果改变了右边的箭头,如改变了s1的箭头,就会同时对b1, b2造成影响。

Arraylist的遍历

ArrayList有三种遍历方式。

例如:

import java.util.ArrayList;
import java.util.Iterator;

public class ListDemo2 {
    public static void main(String[] args) {
        ArrayList<String> list=new ArrayList<String>();
        list.add("11");
        list.add("22");
        list.add("33");
        //第一种遍历方式:Iterator
        Iterator<String> it=list.iterator();
        while(it.hasNext()){
            System.out.print(it.next()+" "); //11 22 33
        }
        System.out.println("");
        //第二种遍历方式
        for(int i=0;i<list.size();++i){
            System.out.print(list.get(i)+" "); //11 22 33
        }
        System.out.println("");
        //第三种遍历方式
        for(String s:list){
            System.out.print(s+" "); //11 22 33
        }
        System.out.println("");
    }
}

结果显示,这三种遍历方法中,第二种(使用索引index)的效率最高,第一种(使用Iterator)的效率最低。

ArrayList的toArray(T[] contents)

Arraylist提供了两个将ArrayList转换为数组的方法:

Object[] toArray()
<T> T[] toArray(T[] contents)

由于toArray()返回的类型是Object[],如果进行强制类型转换会造成java.lang.ClassCastException。因此调用toArray()容易出错,更建议使用toArray(T[] contents)。

例如:

import java.util.ArrayList;
import java.util.Arrays;

public class ListDemo2 {
    public static void main(String[] args) {
        ArrayList<String> list=new ArrayList<String>();
        list.add("11");
        list.add("22");
        list.add("33");
        //方法1
        String[] str=new String[list.size()];
        str=list.toArray(str);
        System.out.println(Arrays.toString(str)); //[11, 22, 33]
        //方法2
        String[] str2=(String[])list.toArray(new String[0]);
        System.out.println(Arrays.toString(str2)); //[11, 22, 33]
    }
}

2.2 LinkedList

LinkedList元素存储的数据结构是链表结构。LinkedList能够方便地对元素进行增删。

LinkedList提供了一些方法,来方便对首尾元素的操作。

LinkedList还可以作为堆栈、队列的结构使用,所以提供了一些和堆栈、队列相关的方法。

 例如:

 1 import java.util.LinkedList;
 2 public class ListDemo1 {
 3     public static void main(String[] args) {
 4         LinkedList<String> list=new LinkedList<String>();
 5         list.add("11");
 6         list.add("22");
 7         list.add("33");
 8         while(!list.isEmpty()){
 9             System.out.print(list.pop()+" "); //output: 11 22 33
10         }
11         System.out.println("");
12     }
13 }
LinkedList的pop()

*注意元素存取的顺序,保持着先进先出的顺序。

2.3 Vector

 Vector元素存储的数据结构是数组结构。Vector与ArrayList类似,Vector提供的Enumeration与ArrayList提供的Iterator类似,二者在功能上可以说的上是重复的。

例如:

 1 import java.util.ArrayList;
 2 import java.util.Enumeration;
 3 import java.util.Iterator;
 4 import java.util.Vector;
 5 
 6 public class ListDemo1 {
 7     public static void main(String[] args) {
 8         //ArrayList和Iterator
 9         ArrayList<String> list=new ArrayList<String>();
10         list.add("11");
11         list.add("22");
12         list.add("33");
13         Iterator<String> it=list.iterator();
14         while(it.hasNext()){
15             System.out.print(it.next()+" ");
16         }
17         System.out.println("");
18         //Vector和Enumeration
19         Vector<String> list2=new Vector<String>();
20         list2.add("11");
21         list2.add("22");
22         list2.add("33");
23         Enumeration<String> en=list2.elements();
24         while(en.hasMoreElements()){
25             System.out.print(en.nextElement()+" ");
26         }
27         System.out.println("");
28     }
29 }
ArrayList和Iterator vs.Vector和Enumration

3.Set接口

Set的主要特征

(1)不允许重复:元素不允许重复。Set在存储元素时会通过hashCode()和equals()来保证元素的唯一性。

Set如何保证元素的唯一性

Set在存储元素时,通过hashCode()和equals()来保证元素的唯一性。

事实上,当存储一个新的元素时,仅仅通过equals()来逐一判断新元素是否与集合中已有的元素是否重合,这种方法也是可行的,那为什么还需要hashCode()呢。因为当Set中元素数量很多时,通过equals()逐一判断并不是一个高效率的方法,所以同时通过hashCode()和equals()进行判断可以提高判断的效率。

首先,我们回忆一下什么是hashcode。

https://www.cnblogs.com/dolphin0520/p/3681042.html

hashCode()是Object的类,每个对象都具有hashCode值。不同的对象可能会有相同的hashCode值,但hashCode值不相同的两个对象肯定不同。

我们可以用映射的概念来理解对象与hashCode之间的关系,对象(value)与hashCode(key)构成了多对一的映射。

当每次存储新的元素时,首先通过hashCode()获得新元素的hashCode,判断是否与已有元素的hashCode相同。如果没有,将新元素加入到集合中。如果有,再通过equals()判断元素是否相同,如果相同,则不添加该元素,如果不同,则把该元素加到集合中。

3.1 HashSet

HashSet元素存储的结构是哈希表

hashSet除了不允许重复元素外,还不能保证元素存取的顺序。

例如:

 1 import java.util.HashSet;
 2 import java.util.Iterator;
 3 
 4 public class setDemo1 {
 5     public static void main(String[] args) {
 6         HashSet<String> hash=new HashSet<String>();
 7         hash.add("11");
 8         hash.add("22");
 9         hash.add("33");
10         Iterator<String> it=hash.iterator();
11         while(it.hasNext()){
12             System.out.print(it.next()+" ");
13         }
14         System.out.println(""); //output: 22 33 11
15     }    
16 }
不能保证存取顺序

hashCode()和equals()的重写

就像我们刚才提到的,hashSet是通过hashCode()和equals()来保证元素的唯一性。JaveAPI中的每个类(如String, Integer类)都能获得hashCode和equals的比较方法,所以这类元素可以直接用hashSet存储,但是用户自定义的类,也需要先重写hashCode()和equals(),之后才能用hashSet存储该类元素。

*Object的equals()与==功能相同,判断的是地址是否相同。所以想要判断内容是否相同,必须要重写equals(),比如String类。

例如:

//用户自定义类
public class Student {
    private int id;
    private String name;
    private int age;
    //构造方法
    public Student() {
        // TODO Auto-generated constructor stub
    }
    public Student(int id,String name,int age){
        this.id=id;
        this.name=name;
        this.age=age;
    }
    //getter and setter
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    //overwrite hashCode() and equals()
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + id;
        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;
        Student other = (Student) obj;
        if (age != other.age)
            return false;
        if (id != other.id)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    //overwrite toString()
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
}

//hashSet分别存储JavaAPI类和用户自定义类
import java.util.HashSet;
import java.util.Iterator;

public class setDemo1 {
    public static void main(String[] args) {
        //用HashSet存储JavaAPI类,如String、Integer等
        HashSet<String> hash=new HashSet<String>();
        hash.add("11");
        hash.add("22");
        hash.add("33");
        hash.add("22");
        Iterator<String> it=hash.iterator();
        while(it.hasNext()){
            System.out.print(it.next()+" ");
        }
        /*
         * 不允许重复元素
         * 不保证存取顺序
         */
        System.out.println(""); //output: 22 33 11
        //用HashSet存储用户自定义类
        //首先在用户自定义类中需要重写hashCode()和equals()
        HashSet<Student> hash2=new HashSet<Student>();
        Student s1=new Student(001,"zhangsan",22);
        Student s2=new Student(002,"lisi",21);
        Student s3=new Student(003,"wangwu",18);
        hash2.add(s1);
        hash2.add(s2);
        hash2.add(s3);
        hash2.add(s1);
        Iterator<Student> it2=hash2.iterator();
        while(it2.hasNext()){
            System.out.println(it2.next()+" ");
        }
        /*
         * output:
         *     Student [id=3, name=wangwu, age=18] 
         *  Student [id=1, name=zhangsan, age=22] 
         *  Student [id=2, name=lisi, age=21] 
         */
    }    
}
View Code

3.2 LinkedHashSet

LinkedHashSet的元素的存储结构是链表和哈希表

LinkedHashSet保证了元素存取的顺序。

*LinkedHashSet遵循先进先出的顺序

例如:

 1 import java.util.Iterator;
 2 import java.util.LinkedHashSet;
 3 
 4 public class setDemo2 {
 5     public static void main(String[] args) {
 6         LinkedHashSet<String> lset=new LinkedHashSet<String>();
 7         lset.add("11");
 8         lset.add("22");
 9         lset.add("33");
10         Iterator<String> it=lset.iterator();
11         while(it.hasNext()){
12             System.out.print(it.next()+" ");
13         }
14         System.out.println(""); //output: 11 22 33
15     }
16 }
View Code

3.3 TreeSet

TreeSet可以保证元素存取的顺序。

 参考文献

https://docs.oracle.com/javase/7/docs/api/java/util/List.html

https://www.cnblogs.com/dolphin0520/p/3681042.html

https://www.cnblogs.com/jmsjh/p/7740123.html

posted @ 2018-10-26 15:40 沙田柚 阅读(...) 评论(...) 编辑 收藏