欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

集合框架(List和Set)

程序员文章站 2022-03-03 08:51:11
...

一、概述

集合是一种可变数据项的容器,具有统一的父类接口Collection<E>Map并没有继承之),与其子集合的关系例如以下 图。集合的特点是长度可变,能够存储多种类型的对象(不加泛型时)。这也是与数组的两点最大的不同。

集合框架(List和Set)
java集合类关系图

Collection最为根接口,ListSetQueue接口均直接继承自它,Map接口尽管不是直接继承自Collection,可是接口中使用到了Collection,即Map的数据也是使用Collection存储的。
研究集合不可缺少的是先研究Collection接口的内容,先从接口名字定义開始说起public interface Collection<E> extends Iterable<E>,这是集合的根接口。<E>是JDK1.5開始的泛型,其功能是提高安全性,增强针对性,详细介绍后面再说。Iterable<E>接口是迭代器。以下有详细介绍。

Collection接口提供了一些集合的共性方法,如:

  • boolean add(E e)加入一条数据。

  • boolean addAll(Collection<?

    extends E> c)

    加入还有一个集合内的元素。
  • void clear()清除全部数据。

  • boolean contains(Object o)是否包括指定数据。

  • Iterator<E> iterator()返回迭代器。
  • boolean remove(Object o)删除指定数据。
  • <T>T[] toArray(T[] a)返回数据的数组形式等等。
  • boolean retainAll(Collection<?> c)取与集合c的交集。
  • boolean removeAll(Collection<?> c)取与集合c的差集。

二、迭代器

一种集合的取出方式,用于集合的数据的遍历等。

不同容器的数据结构不同,所以取出的动作实现不同,详细的取出动作定义在每一个容器的内部,这样取出方式就能够直接訪问集合内部元素。取出方式就被定义成了内部类。可是都有共性内容推断和取出,共性抽取便形成了Iterator接口(迭代器)。
迭代器的使用演示样例(暂时不考虑泛型):

ArrayList alist = new ArrayList();
alist.add("java01");
alist.add("java02");
alist.add("java03");
alist.add("java04");
for(Iterator it = alist.iterator(); it.hasNext(); ) {
    System.out.println(it.next());
}

三、List

List为Collection的常见子接口之中的一个,元素有序。能够反复,有索引。
List有些特有的方法,和下标相关的一些方法。如:
- 增:add(index, element)指定位置加入元素,addAll(index, Collection)指定位置加入一个集合的元素。


- 删:remove(index)删除指定为的元素。
- 改:set(index, element)改动指定位置的元素。
- 查:get(index)获取指定位置的元素。subList(from, to)获取[from, to)段的子ListlistIterator()获取List特有的迭代器,indexOf(E)获取对象的位置。

ListIterator

ListIteratorIterator的子接口,在迭代时不能够通过集合的操作对集合进行操作,会发生并发改动异常,可是Iterator的操作是有限的,假设想对元素进行加入和改动等,就须要用到其子接口ListIterator,该接口仅仅能通过ListlistIterator()方法获取,演示样例代码例如以下:

ArrayList alist = new ArrayList();
alist.add("java01");
alist.add("java02");
alist.add("java03");
alist.add("java04");
for(ListIterator it = alist.listIterator(); it.hasNext(); ) {
    System.out.println(it.next());
    it.add("java");
}
System.out.println(alist);

//输出结果是
java01
java02
java03
java04
[java01, java, java02, java, java03, java, java04, java]

通过上面能够看到,ListIterator在获取之后其遍历的内容就已经确定下来了。 ListIterator还有逆向遍历功能。

ArrayList alist = new ArrayList();
alist.add("java01");
alist.add("java02");
alist.add("java03");
alist.add("java04");
ListIterator it = alist.listIterator();
while(it.hasNext()) {
    System.out.println(it.next());
}
while(it.hasPrevious()) {
    System.out.println(it.previous());
}
// 输出的结果是
java01
java02
java03
java04
java04
java03
java02
java01

List中详细对象特点

List有三个详细的子类,各自是ArrayListLinkedListVector

三者的特点与不同例如以下:

  • ArrayList:底层数据结构为数组结构,线程非同步。查询速度快,增删速度慢,效率比Vector高,最早出如今JDK1.2版本号。初始长度默觉得10。空间不足时50%延长。
  • LinkedList:底层数据结构为链表结构,线程非同步,查询速度慢,增删速度快,最早出如今JDK1.2版本号。

  • Vector:底层数据结构是数组结构,线程同步,与ArrayList有同样的功能。已被ArrayList取代,最早出如今JDK1.0版本号。

    默认长度为10,空间不足时100%延长。

Vector的特点

Vector的特有的一点就是,Vector的枚举(Vector的特有取出方式),和迭代器相似,名为EnumerationArrayList中没有此功能。可是由于方法名过长,且没有移除功能,已被Iterator取代,所以一般优先考虑使用Iterator。演示样例代码例如以下:

Vector v = new Vector();
v.add("java01");
v.add("java02");
v.add("java03");
Enumeration en = v.elements();
while(en.hasMoreElements()) {
    System.out.println(en.nextElement());
}
// 输出结果为
java01
java02
java03

LinkedList

LinkedList的特有方法:addFirst(E e)addLast(E e)加入到头部或者尾部、getFirst()getLast()获取头部元素或者尾部元素、removeFirst()removeLast()删除头部元素或者尾部元素,并返回删除的元素,没有会报NoSuchElementException,这些是LinkedList所特有的方法。JDK1.6版本号名称发生了变化,分别变成了offerFirst(E e)offerLast(E e)peekFirst()peekLast()pollFirst()pollLast()。没有会返回null
LinkedList练习,模拟队列进行加入和取出动作。演示样例代码例如以下:

import java.util.*;
class MyQueue {
    private LinkedList link;
    MyQueue() {
        link = new LinkedList();
    }

    public void myAdd(Object obj) {
        link.addFirst(obj);
    }

    public Object myGet() {
        return link.removeFirst();
    }

    public boolean isNull() {
        return link.isEmpty();
    }
}

class LinkedListTest {
    public static void main(String[] args) {
        MyQueue q = new MyQueue();
        q.myAdd("java01");
        q.myAdd("java02");
        q.myAdd("java03");
        q.myAdd("java04");

        while(!q.isNull()) {
            System.out.println(q.myGet());
        }
    }
}

ArrayList

ArrayList练习,去除ArrayList中的反复项。

代码示比例如以下:

import java.util.*;
class ArrayListTest {

    public static void main(String[] args) {
        ArrayList list = new ArrayList();

        list.add("java01");
        list.add("java02");
        list.add("java01");
        list.add("java02");
        list.add("java01");
        list.add("java03");

        // 去除反复前
        System.out.println(list);
        list = singleElement(list);
        // 去除反复后
        System.out.println(list);
    }

    public static ArrayList singleElement(ArrayList list) {
        //定义一个暂时容器。
        ArrayList newList = new ArrayList();
        // 获取迭代器
        Iterator it = list.iterator();
        while(it.hasNext()) {
            Object obj = it.next();
            if(!newList.contains(obj)) {
                newList.add(obj);
            }
        }
        return newList;
    }
}

ArrayList练习。自己定义类加入至ArrayList,须要覆盖自己定义类的equals方法。

import java.util.*;
class Person {
    private String name;
    private int age;
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 覆盖equals方法
    public boolean equals(Object obj) {

        if(!(obj instanceof Person))
            return false;

        Person p = (Person)obj;
        return this.name.equals(p.name) && this.age == p.age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String toString() {
        return "Name:" + this.name + " Age:" + this.age;
    }
}

class ArrayListTest {

    public static void main(String[] args) {
        ArrayList list= new ArrayList();
        list.add(new Person("lisi01",30));
        list.add(new Person("lisi02",32));
        list.add(new Person("lisi02",32));
        list.add(new Person("lisi04",35));
        list.add(new Person("lisi03",33));
        list.add(new Person("lisi04",35));

        System.out.println(list);
        list = singleElement(list);
        System.out.println(list);
    }

    public static ArrayList singleElement(ArrayList al) {
        // 定义一个暂时容器。
        ArrayList newList = new ArrayList();
        // 取出迭代器
        Iterator it = al.iterator();
        while(it.hasNext()) {
            Object obj = it.next();
            if(!newList.contains(obj))
                newList.add(obj);
        }
        return newList;
    }
}

四、Set

元素无序(存入与取出的顺序不一致),元素不可反复。

有两个主要子类:HashSetTreeSet

  • HashSet:底层数据结构为哈希表。

  • TreeSet:底层数据结构为二叉树。

HashSet

依据hashCode()equals()方法进行存储。add时。假设是第一次加入返回true(加入成功),否则返回false(加入失败)。演示样例代码例如以下:

import java.util.*;
class HashSetDemo { 
    public static void main(String[] args) {
        HashSet hs = new HashSet();
        // 第一次加入java01返回true
        System.out.println(hs.add("java01"));
        // 第二次加入java01返回false
        System.out.println(hs.add("java01"));
        hs.add("java02");
        hs.add("java03");
        hs.add("java03");
        hs.add("java04");
        // 遍历集合
        Iterator it = hs.iterator();
        while(it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

HashSet存储自己定义对象,自己定义对象须要覆盖hashCodeequals方法。当hashCode值同样、equals返回真时,觉得是同一个对象。加入时。当hashCode值不同一时候,不会调用equals比較,当hashCode的值同样时,才会调用equals方法比較两个对象。

演示样例代码例如以下:

import java.util.*;
/**
 *姓名和年龄同样觉得是同一个人
 */
class Person {
    private String name;
    private int age;
    Person(String name,int age) {
        this.name = name;
        this.age = age;
    }

    public int hashCode() {
        System.out.println(this.name+"....hashCode");
        return name.hashCode() + age*37;
    }

    public boolean equals(Object obj) {
        if(!(obj instanceof Person))
            return false;
        Person p = (Person)obj;
        System.out.println(this.name+"...equals.."+p.name);
        return this.name.equals(p.name) && this.age == p.age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

class HashSetTest {
    public static void main(String[] args) {
        HashSet hs = new HashSet();
        hs.add(new Person("a1",11));
        hs.add(new Person("a2",12));
        hs.add(new Person("a3",13));
        // 加入一个同样的人
        hs.add(new Person("a2",12));
        hs.add(new Person("a4",14));

        // 遍历集合中的人
        Iterator it = hs.iterator();
        while(it.hasNext()) {
            Person p = (Person)it.next();
            System.out.println(p.getName()+"::"+p.getAge());
        }
    }
}
// 执行结果为
a1....hashCode
a2....hashCode
a3....hashCode
a2....hashCode
a2...equals..a2
a4....hashCode
a3::13
a1::11
a4::14
a2::12

从上面代码执行的结果能够看出,当加入new Person("a2",12)时。由于集合中已经有这个人了。所以hashCode的返回值已经存在。因此便会调用equals方法推断,终于结果还是此人已存在,所以最总集合中仅仅有4个人。
HashSet的推断和删除依据,能够看到判读元素是否存在和删除元素都是首先依据hashCode值,然后再通过equals推断是否同样。

import java.util.*;
/**
 *姓名和年龄同样觉得是同一个人
 */
class Person {
    private String name;
    private int age;
    Person(String name,int age) {
        this.name = name;
        this.age = age;
    }

    /**
     *统一返回1作为哈希值
     */
    public int hashCode() {
        System.out.println("hashCode");
        return 1;
    }

    /**
     * 姓名和年龄同样觉得是同一个人
     */
    public boolean equals(Object obj) {
        if(!(obj instanceof Person))
            return false;
        Person p = (Person)obj;
        System.out.println(this.name+"...equals.."+p.name);
        return this.name.equals(p.name) && this.age == p.age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

class HashSetTest {
    public static void main(String[] args) {
        HashSet hs = new HashSet();
        hs.add(new Person("a1",11));
        hs.add(new Person("a2",12));
        hs.add(new Person("a3",13));
        // 加入一个同样的人
        hs.add(new Person("a2",12));
        hs.add(new Person("a4",14));
        System.out.println("--------");

        // 推断是否存在元素
        hs.contains(new Person("a4", 14));
        System.out.println("--------");

        // 删除指定元素
        hs.remove(new Person("a1", 11));
    }
}
// 执行结果为
hashCode
hashCode
a2...equals..a1
hashCode
a3...equals..a1
a3...equals..a2
hashCode
a2...equals..a1
a2...equals..a2
hashCode
a4...equals..a1
a4...equals..a2
a4...equals..a3
--------
hashCode
a4...equals..a1
a4...equals..a2
a4...equals..a3
a4...equals..a4
--------
hashCode
a1...equals..a1

TreeSet

能够对集合中的元素进行排序。

底层数据结构为二叉树。中序遍历便是TreeSet的顺序。

演示样例代码例如以下:

import java.util.*;
class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet();
        ts.add("asdas");
        ts.add("abc");
        ts.add("beds");
        ts.add("abd");
        Iterator it = ts.iterator();
        while(it.hasNext()) {
            System.out.println(it.next());
        }
    }
}
// 执行结果为
abc
abd
asdas
beds

对于自己定义对象的排序,一般有两种做法,一是自己定义对象自己实现Comparable接口。二是集合提供比較器(Comparator接口)。


自己定义对象实现Comparable接口,演示样例代码例如以下:

import java.util.*;

class Student implements Comparable {
    private String name;
    private int age;

    Student(String name,int age) {
        this.name = name;
        this.age = age;
    }

    /**
     *比較结果又三种。负数表示小于,0表示等于,正数表示大于
     */
    public int compareTo(Object obj) {
        if(!(obj instanceof Student))
            throw new RuntimeException("不是学生对象");
        Student s = (Student)obj;
        System.out.println(this.name+"....compareto....."+s.name);
        if(this.age>s.age)
            return 1;
        if(this.age==s.age) {
            // String类已经实现了Comparable接口
            return this.name.compareTo(s.name);
        }
        return -1;
    }

    public String getName() {
        return name;

    }

    public int getAge() {
        return age;
    }
}

class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet();
        ts.add(new Student("lisi02",22));
        ts.add(new Student("lisi007",20));
        ts.add(new Student("lisi09",19));
        ts.add(new Student("lisi08",19));
        ts.add(new Student("lisi007",20));
        ts.add(new Student("lisi01",40));

        // 遍历集合
        Iterator it = ts.iterator();
        while(it.hasNext()) {
            Student stu = (Student)it.next();
            System.out.println(stu.getName()+"..."+stu.getAge());
        }
    }
}

集合提供比較器(Comparator接口)的比較方式。用于元素不具备比較性或者具备的比較性不是用户所须要的时。演示样例代码例如以下:

import java.util.*;
class Student implements Comparable {
    private String name;
    private int age;

    Student(String name,int age) {
        this.name = name;
        this.age = age;
    }

    /**
     *比較结果又三种,负数表示小于,0表示等于,正数表示大于
     */
    public int compareTo(Object obj) {
        if(!(obj instanceof Student))
            throw new RuntimeException("不是学生对象");
        Student s = (Student)obj;
        System.out.println(this.name+"....compareto....."+s.name);
        if(this.age>s.age)
            return 1;
        if(this.age==s.age) {
            return this.name.compareTo(s.name);
        }
        return -1;
    }

    public String getName() {
        return name;

    }

    public int getAge() {
        return age;
    }
}

class TreeSetDemo2 {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet(new MyCompare());

        ts.add(new Student("lisi02",22));
        ts.add(new Student("lisi02",21));
        ts.add(new Student("lisi007",20));
        ts.add(new Student("lisi09",19));
        ts.add(new Student("lisi06",18));
        ts.add(new Student("lisi06",18));
        ts.add(new Student("lisi007",29));
        ts.add(new Student("lisi007",20));
        ts.add(new Student("lisi01",40));

        Iterator it = ts.iterator();
        while(it.hasNext()) {
            Student stu = (Student)it.next();
            System.out.println(stu.getName()+"..."+stu.getAge());
        }
    }
}

class MyCompare implements Comparator {
    public int compare(Object o1,Object o2) {
        Student s1 = (Student)o1;
        Student s2 = (Student)o2;
        // 姓名比較优先
        int num = s1.getName().compareTo(s2.getName());
        if(num==0) {
            // 姓名同样时,年龄小的在前
            return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));
        }

        return num;
    }
}

五、泛型

泛型的出现攻克了类型不同的安全问题,JDK1.5版本号開始出现的。详细做法就是在集合定义时指明数据类型。如ArrayList<String> list= new ArrayList<String>(),这样一来假设向list中加入非String类型对象时。就会在编译时期提示ClassCastException异常。演示样例代码例如以下:

import java.util.*;
class GenericDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("abc01");
        list.add("abc0991");
        list.add("abc014");

        // Iterator也有相应的泛型
        Iterator<String> it = al.iterator();
        while(it.hasNext()) {
            System.out.println(s+":"+s.length());
        }
    }
}

自己定义类中使用泛型的概念。试用于用数据类型不确定时,演示样例代码例如以下:

class Worker {

}
class Student {

}

/**
 *泛型类,使用与引用数据类型不确定时。
 */
class Utils<Q> {
    private Q q;
    public void setObject(Q q) {
        this.q = q;
    }

    public Q getObject() {
        return q;
    }
}
class  GenericDemo3 {
    public static void main(String[] args) {
        Utils<Worker> u = new Utils<Worker>();
        u.setObject(new Worker());
        Worker w = u.getObject();;
    }
}

泛型定义在方法上,演示样例代码例如以下:

class Demo {
    public <T> void show(T t) {
        System.out.println("show:"+t);
    }

    public <Q> void print(Q q) {
        System.out.println("print:"+q);
    }

    /**
     * 静态方法不能够訪问类上的泛型。可使用静态泛型方法。注意:泛型标识<E>需放在返回值前。

*/

public static <E> void printf(E e) { System.out.println("printf:"+e); } } class GenericDemo4 { public static void main(String[] args) { Demo demo = new Demo(); demo.show("Hi"); demo.print("Hello"); Demo.printf(5); } } //执行结果为 show:Hi print:Hello show:5

泛型限定

一般有三种情况。

  • <?

    >

    :?为通配符。
  • <? extends E>:E和E的子类。称为上限。

  • <? super E>:E和E的父类。称为下限。

如对全部类型的ArrayList进行遍历输出,演示样例代码例如以下:

public static void print(ArrayList<?> list) {
    Iterator<?> it = list.iterator();
    while(it.hasNext()) {
        System.out.println(it.next().toString());
    }
}

Person或者Person的子类Student进行遍历并输出,演示样例代码例如以下:

public static void print(ArrayList<? extends Person> list) {
    Iterator<? extends Person> it = list.iterator();
    while(it.hasNext()) {
        System.out.println(it.next().getName());
    }
}

对于<?

super E>并不经常使用。举一个TreeSet里的样例,TreeSet(Comparator<?

super E> comparator),使用父类的比較器进行子类的比較。演示样例代码例如以下:

import java.util.*;
class Person {
    private String name;
    Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public String toString() {
        return "Person :"+name;
    }
}

class Student extends Person {
    Student(String name) {
        super(name);
    }
}

class Worker extends Person {
    Worker(String name) {
        super(name);
    }
}

/**
 * 定义使用与Person的比較器
 */
class Comp implements Comparator<Person> {
    public int compare(Person p1,Person p2) {
        // 倒序
        return p2.getName().compareTo(p1.getName());
    }
}

class GenericDemo {
    public static void main(String[] args) {

        // 使用使用父类的比較器进行比較
        TreeSet<Student> ts = new TreeSet<Student>(new Comp());
        ts.add(new Student("abc03"));
        ts.add(new Student("abc02"));
        ts.add(new Student("abc06"));
        ts.add(new Student("abc01"));
        Iterator<Student> it = ts.iterator();
        while(it.hasNext()) {
            System.out.println(it.next().getName());
        }

        // 使用使用父类的比較器进行比較
        TreeSet<Worker> ts1 = new TreeSet<Worker>(new Comp());
        ts1.add(new Worker("wabc--03"));
        ts1.add(new Worker("wabc--02"));
        ts1.add(new Worker("wabc--06"));
        ts1.add(new Worker("wabc--01"));
        Iterator<Worker> it1 = ts1.iterator();
        while(it1.hasNext()) {
            System.out.println(it1.next().getName());
        }
    }
}
// 执行结果为
abc06
abc03
abc02
abc01
wabc--06
wabc--03
wabc--02
wabc--01