第十二章 Collection(1.2)

Collection

JDK中在java.util包下提供了一组类库,用以实现集合这一数据存储结构,作为容器,用以存储、处理大量对象。这些由JDK提供的集合主要围绕着Collection接口和Map接口实现,由此也分为两类,本节主要介绍由Collection接口实现的集合。
在这里插入图片描述

1.1、Collection接口相关继承关系

Collection接口下,主要由List接口Set接口Queue接口对其进行了继承。

  • List接口主要由ArrayList类Vector类LinkedList类等对其进行实现
  • Set接口主要由HashSet类TreeSet类等对其进行实现

它们都在java.util包下,它们之间简单的继承关系图可以概括如上。

解释:
1、java.util.Collection接口是一个泛型接口,主要规范的方法:

  • add(E e)
  • contains(Object o)
  • toArray()
  • remove(Object o)
  • size()
  • clear()

2、java.util.List接口是一个泛型接口,继承了java.util.Collection接口,规范了有序(这里的有序指元素具有下标,可以通过下标索引来访问元素)集合的行为,在java.util.Collection接口的基础上,又规范了get(int index)、set(int index, E element)、add(int index, E element)、remove(int index)、indexOf(Object o)、lastIndexOf(Object o)等方法。
3、java.util.Set接口是一个泛型接口,继承了java.util.Collection接口,规范了无序(这里的无序指元素不具有下标,无法通过下标索引来访问元素)集合的行为,该接口覆盖了java.util.Collection接口的部分方法。
4、java.util.ArrayList类、java.util.Vector类、java.util.LinkedList类均分别实现了java.util.List接口;
5、java.util.HashSet类、java.util.TreeSet类均分别实现了java.util.Set接口。
开发人员在学习、使用这些集合相关的类库时,需要熟悉这些类库中提供的常用API,进而掌握如何向集合中添加元素、如何从集合中获取某一个元素,如何获取集合中所有的元素,如何将一个元素从集合中移除,如何判断集合中是否包含了某个元素等常用操作。

1.2、List接口

实现了java.util.List接口的集合也被称为列表,它们和数组比较相似,使用这类集合存储的对象有序,可以重复。常见的实现类如java.util.ArrayList类、java.util.Vector类、java.util.LinkedList类等,它们的功能与用法几乎完全相同,只是内部实现不同。

java.util.List接口中的API如下:
参考API: JDK 1.8

序号返回值方法方法说明
01booleanadd(E e)将指定的元素追加到此列表的末尾(可选操作)
02voidadd(int index, E element)在列表中指定的位置上插入指定的元素(可选操作)
03booleanaddAll(Collection<? extends E> c)追加指定集合的所有元素到这个列表的末尾,按他们的指定集合的迭代器返回(可选操作)
04booleanaddAll(int index, Collection<? extends E> c)将指定的集合中的所有元素插入到指定位置的列表中(可选操作)
05voidclear()从这个列表中移除所有的元素(可选操作)。
06booleancontains(Object o)返回 true如果这个列表包含指定元素。
07booleancontainsAll(Collection<?> c)返回 true如果这个列表包含指定集合的所有元素。
08booleanequals(Object o)将指定的对象与此列表进行比较,以进行相等性。
09Eget(int index)返回此列表中指定位置的元素。
10inthashCode()返回此列表的哈希代码值。
11intindexOf(Object o)返回此列表中指定元素的第一个出现的索引,或-如果此列表不包含元素,或- 1。
12booleanisEmpty()返回 true如果此列表不包含元素
13Iterator<E>iterator()在这个列表中的元素上返回一个正确的顺序。
14intlastIndexOf(Object o)返回此列表中指定元素的最后一个发生的索引,或-如果此列表不包含元素,或- 1。
15ListIterator<E>listIterator()返回列表元素的列表迭代器(在适当的顺序)。
16ListIterator<E>listIterator(int index)在列表中的元素上返回列表迭代器(在适当的顺序),从列表中的指定位置开始
17Eremove(int index)移除此列表中指定位置的元素(可选操作)。
18booleanremove(Object o)从该列表中移除指定元素的第一个发生,如果它是存在的(可选操作)。
19booleanremoveAll(Collection<?> c)从这个列表中移除包含在指定集合中的所有元素(可选操作)。
20default voidreplaceAll(UnaryOperator<E> operator)用将运算符应用到该元素的结果替换此列表中的每个元素。
21booleanretainAll(Collection<?> c)仅保留包含在指定集合中的列表中的元素(可选操作)。
22Eset(int index, E element)用指定元素替换此列表中指定位置的元素(可选操作)。
23intsize()返回此列表中元素的数目。
24default voidsort(Comparator<? super E> c)分类列表使用提供的 Comparator比较元素。
25default Spliteratorspliterator()创建此列表中的元素的 Spliterator。
26List<E>subList(int fromIndex, int toIndex)返回一个视图之间的指定 fromIndex,包容,和 toIndex这份名单的部分,独家。
27Object[]toArray()返回一个数组,包含在这个列表中的所有元素在适当的顺序(从第一个到最后一个元素)。
28<T> T[]toArray(T[] a)返回一个数组,包含在这个列表中的所有元素在适当的顺序(从第一到最后一个元素);返回数组的运行时类型是指定的数组的运行时类型。

JDK 1.8比JDK 1.6多了三个方法。

sort(Comparator<? super E> c) 
spliterator()  
replaceAll(UnaryOperator<E> operator)

1.2.1、ArrayList类

Java.util.ArrayList类借助数组实现java.util.List接口,该类是一个泛型类,存储的对象有序且可重复

由于java.util.ArrayList类基于数组,在内存中分配的空间连续,因此访问元素速度相对较快,添加和删除元素速度相对较慢。

例1:
Student类的源码:

package com.bennett.test;

public class Student {
	private String name; // 姓名
	// 构造方法
	public Student(String name) {
		this.name = name;
	}
	// 学习的方法
    public void study(){
        System.out.println("student" + this.name + "在学习");
    }
}

Teacher类的源码:

package com.bennett.test;

public class Teacher {
	private String name; // 姓名
	// 构造方法
	public Teacher(String name) {
		this.name = name;
	}
	// 授课的方法
    public void teach(){
        System.out.println("teacher" + this.name + "在授课");
}

测试类Test类的源码:

package com.bennett.test;

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
      
  // 声明并实例化集合对象list
        List list = new ArrayList();
        // 向集合中添加不同类型的元素
        Teacher t = new Teacher("卢俊义");
        Student s = new Student("燕青");
        list.add("Hello");
        list.add(t);
        list.add(s);
        // 判断集合中是否包含"Hello"
        if (list.contains("Hello")) {
            System.out.println("集合中包含Hello");
        } else {
            System.out.println("集合中不包含Hello");
        }
        // 显示集合中所有的元素
        System.out.println("集合中有以下元素");
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i) instanceof java.lang.String) {
                System.out.println(list.get(i));
            } else if (list.get(i) instanceof Teacher) {
                Teacher tea = (Teacher) list.get(i);
                tea.teach();
            } else if (list.get(i) instanceof Student) {
                Student stu = (Student) list.get(i);
                stu.study();
            }
        }
        // 从集合中移除下标为0的元素
        list.remove(0);
        // 显示移除后集合中所有的元素
        System.out.println("移除后集合中还有" + list.size() + "个元素");
    }
}

执行输出结果:

集合中包含Hello
集合中有以下元素
Hello
teacher卢俊义在授课
student燕青在学习
移除后集合中还有2个元素

说明:

本例的测试类main方法中,声明了java.util.List类型的变量list,并引用了一个新实例化的java.util.ArrayList类对象,由于在此过程中没有指定类型参数所代表的具体类型,故类型参数所代表的具体类型就是java.lang.Object。随后,使用add(Object e)方法向集合list中添加了String类型的元素"Hello"、Teacher类型的元素t、Student类型的元素s。再之后,还使用了集合list调用了contains(Object o)、get(int index)、remove(int index)、size()等方法。
需要注意的是,由于没有指定类型参数具体代表的类型,get(int index)方法返回的集合元素的类型均为java.lang.Object,如果要赋值给其他类型的变量,需要强制类型转换。

例2:
Person类的源码:

package com.bennett.test;

public class Person {
	private String name; // 姓名
	// 构造方法
	public Person(String name) {  
		this.name = name;
	}
	// 工作的方法
    public void work() {
        System.out.println("person" + this.name + "在工作");
    }
}

测试类Test类的源码:

package com.bennett.test;

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        // 指定集合的类型参数为Person,该集合中只允许存储Person类型的对象
        List<Person> list = new ArrayList<Person>();
        // 实例化几个Person对象
        Person p1 = new Person("鲁智深");
        Person p2 = new Person("武松");
        Person p3 = new Person("林冲");
        // 向集合中添加元素
        list.add(p1); // add方法的参数是Person类型
        list.add(p2); // add方法的参数是Person类型
        list.add(p3); // add方法的参数是Person类型
        list.add(p3); // 将对象p3再添加一遍
        // 判断集合中是否包含p1
        if (list.contains(p1)) {
            System.out.println("集合中包含p1");
        } else {
            System.out.println("集合中不包含p1");
        }
        // 显示集合中所有的元素
        for (int i = 0; i < list.size(); i++) {
            list.get(i).work(); // get方法的返回值是Person类型
        }
        // 从集合中移除p1元素
        list.remove(p1);
        // 显示移除p1元素后集合中剩余的元素个数
        System.out.println("移除后集合中还有" + list.size() +"个元素");
    }  
}

执行输出结果:

集合中包含p1
person鲁智深在工作
person武松在工作
person林冲在工作
person林冲在工作
移除后集合中还有3个元素

说明:

本例在使用java.util.List接口声明变量时和实例化java.util.ArrayList类对象时,指定了类型参数为Person,因此,集合list中只能储存Person类型的对象,add(E e)方法的参数只能是Person类型,get(int index)方法的返回值类型也是Person类型,不需要强制类型转换。

1.2.2、Vector类

java.util.Vector类同java.util.ArrayList类非常相似,同样借助数组实现java.util.List接口,该类也是一个泛型类,存储的对象有序且可重复;与java.util.ArrayList类最核心的不同是,java.util.Vector类是线程同步Thread Synchronized)的,所以它也是线程安全的,而java.util.ArrayList类是线程异步Thread ASynchronized)的,在多线程的场景下不能保证线程安全。

例1:
Person类的源码同上例。
测试类Test类的源码:

package com.bennett.test;

import java.util.List;
import java.util.Vector;

public class Test {
    public static void main(String[] args) {
        // 声明集合变量list,并引用一个新实例化的Vector对象
        List<Person> list = new Vector<Person>();
        // 实例化几个Person对象
        Person p1 = new Person("鲁智深");
        Person p2 = new Person("武松");
        Person p3 = new Person("林冲");
        //向集合中添加元素
        list.add(p1); // add方法的参数是Person类型
        list.add(p2); // add方法的参数是Person类型
        list.add(p3); // add方法的参数是Person类型
        list.add(p3); // 将对象p3再添加一遍
        // 判断集合中是否包含p1
        if (list.contains(p1)) {
            System.out.println("集合中包含p1");
        } else {
            System.out.println("集合中不包含p1");
        }
        // 显示集合中所有的元素
        for (int i = 0; i < list.size(); i++) {
            list.get(i).work();
        }
        // 从集合中移除p1元素
        list.remove(p1);
        // 显示移除p1元素后集合中剩余的元素个数
        System.out.println("移除后集合中还有" + list.size() +"个元素");
    }
}

执行输出结果:

集合中包含p1
person鲁智深在工作
person武松在工作
person林冲在工作
person林冲在工作
移除后集合中还有3个元素

说明:

java.util.Vector类的使用与java.util.ArrayList类使用方法几乎完全相同,这里不再赘述。

1.2.3、LinkedList类

java.util.LinkedList类借助链表实现java.util.List接口,该类是一个泛型类,存储的对象有序且可重复。该类的使用方法和java.util.ArrayList类基本相同,而与java.util.ArrayList类不同的是,java.util.LinkedList类基于链表,在内存中分配的空间不必连续,因此添加和删除元素速度相对较快,而访问元素速度相对较慢。

例1:
Person类的源码同上例。
测试类Test类的源码:

package com.bennett.test;

import java.util.LinkedList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        // 声明集合变量list,并引用一个新实例化的LinkedList对象
        List<Person> list = new LinkedList<Person>();
        // 实例化几个Person对象
        Person p1 = new Person("鲁智深");
        Person p2 = new Person("武松");
        Person p3 = new Person("林冲");
        //向集合中添加元素
        list.add(p1); // add方法的参数是Person类型
        list.add(p2); // add方法的参数是Person类型
        list.add(p3); // add方法的参数是Person类型
        list.add(p3); // 将对象p3再添加一遍
        // 判断集合中是否包含p1
        if (list.contains(p1)) {
            System.out.println("集合中包含p1");
        } else {
            System.out.println("集合中不包含p1");
        }
        // 显示集合中所有的元素
        for (int i = 0; i < list.size(); i++) {
            list.get(i).work();
        }
        // 从集合中移除p1元素
        list.remove(p1);
        // 显示移除p1元素后集合中剩余的元素个数
        System.out.println("移除后集合中还有" + list.size() +"个元素");
    }
}

执行输出结果:

集合中包含p1

person鲁智深在工作
person武松在工作
person林冲在工作
person林冲在工作
移除后集合中还有3个元素

说明:

java.util.LinkedList类的是使用和java.util.Vector类、java.util.ArrayList类几乎完全相同,这里不再赘述。

1.3、Set接口

实现了java.util.Set接口的集合,存储的对象无序且不可重复。常见的实现类如java.util.HashSet类、java.util.TreeSet类等,它们的功能与用法几乎完全相同,只是内部实现不同。java.util.Set接口中的一些常用API如下:

方法 返回值 方法说明
add(E e) boolean 如果指定的元素不存在,则添加该元素
clear() void 从此集合中删除所有元素
contains(Object o) boolean 如果此集合包含指定的元素,则返回true,否则返回false
isEmpty() boolean 如果此集合不包含元素,则返回true,否则返回false
remove(Object o) boolean 如果存在,则从该集合中删除指定的元素
size() int 返回此集合中的元素数
toArray() Object[] 返回一个包含此集合中所有元素的数组

1.3.1、HashSet类

java.util.HashSet类借助散列算法实现java.util.Set接口,该类是一个泛型类。该类的对象可以储存无序、唯一的对象,由于储存的元素是无序的,故无法基于下标进行操作。

下面是一个示例:
Person类的源码同上例。
测试类Test类的源码:

package com.bennett.test;

import java.util.HashSet;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        // 声明并实例化Set集合,指定类型参数为Person
        Set<Person> set = new HashSet<Person>();
        // 实例化几个Person对象
        Person p1 = new Person("鲁智深");
        Person p2 = new Person("武松");
        Person p3 = new Person("林冲");
        // 向集合中添加元素
        set.add(p1); // add方法的参数是Person类型
        set.add(p2); // add方法的参数是Person类型
        set.add(p3); // add方法的参数是Person类型
        set.add(p3); // 将对象p3再添加一遍
        // 显示集合中的元素个数
        System.out.println("集合中有"+ set.size() +"个元素");
        // 显示集合中所有的元素
        for (Person person : set) {
            person.work();
        }
        // 从集合中删除p1元素
        set.remove(p1);
        // 显示移除p1元素后集合中剩余的元素个数
        System.out.println("移除后集合中还有" + set.size() +"个元素");
    }
}

执行输出结果:

集合中有3个元素
person林冲在工作
person武松在工作
person鲁智深在工作
移除后集合中还有2个元素

说明:

本例使用java.util.Set接口声明变量set,实例化java.util.HashSet类对象并赋值给变量set。由于set集合中的元素不可重复,故本例中两次执行语句set.add(p3),集合中只会保存一个对象p3的引用。又由于set集合是无序的,故无法使用普通的for循环通过下标遍历集合中的元素,不过set集合仍然可以使用For-Each循环(加强型循环)遍历。
下面是另一个示例:
Person类的源码同上例。
测试类Test类的源码:

package com.codeke.java.test;

import java.util.HashSet;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        // 声明并实例化Set集合,指定类型参数为Integer
        Set<Integer> integerSet = new HashSet<Integer>();
        // 实例化两个Integer对象
        Integer num1 = new Integer(108);
        Integer num2 = new Integer(108);
        // 向集合中添加元素
        integerSet.add(num1);
        integerSet.add(num2);
        // 打印集合长度
        System.out.println("integerSet的长度是 = " + integerSet.size());

        // 声明并实例化Set集合,指定类型参数为Person
        Set<Person> personSet = new HashSet<Person>();
        // 实例化两个Person对象
        Person p1 = new Person("吴用");
        Person p2 = new Person("吴用");
        // 向集合中添加元素
        personSet.add(p1);
        personSet.add(p2);
        // 打印集合长度
        System.out.println("personSet的长度是 = " + personSet.size());
    } 
}

执行输出结果:

integerSet的长度是 = 1
personSet的长度是 = 2

说明:

前文提到,java.util.HashSet类实例化的集合中元素是不可重复的,观察本例中的代码,java.lang.Integer类的对象num1、num2在内存中的地址不同,但所包装的字面值相同,在将对象num1、num2添加到java.util.HashSet类实例化的集合integerSet中后,集合integerSet的长度为1,显然该集合认为对象num1、num2重复了;之后,两个Person类的对象p1和p2,它们在内存中的地址也不同,但属性字面值相同,在将对象p1和p2添加到java.util.HashSet类实例化的集合personSet中后,集合personSet的长度为2,显然该集合认为对象p1、p2没有重复。
本例的代码便提出了这样一个问题,由java.util.HashSet类实例化的集合中元素是否重复是如何界定的?
实际上,由java.util.HashSet类实例化的集合通过元素的hashCode()方法和equals(Object obj)方法来判断元素是否重复,其逻辑大致为,当向集合中添加一个对象时,先调用该对象的hashCode()方法,获得该对象的哈希值,通过哈希值分析元素在集合中的存储位置,得出的存储位置没有被其他元素占据时,存储该对象,而当得出存储位置上已经存储了另一元素时,使用该对象的equals(Object obj)方法与另一元素比较,equals(Object obj)方法返回false则继续存储,equals(Object obj)方法返回true则认为对象重复,不再存储。本例中,集合integerSet集合认为对象num1、num2重复,是因为java.lang.Integer类中重写了hashCode()方法和equals(Object obj)方法,保证了在java.lang.Integer类的对象封装的整数字面值相同时,hashCode()方法返回值相同且equals(Object obj)方法比较后返回的结果为true。
前面的章节提到,在实际开发中,有时需要当两个对象的属性值完全对应相同时即认为两个对象相同,此时,equals(Object obj)方法需要被重写;而在这种情况下,如果对象还需要存储到本质是哈希表的数据结构中(Java中常用的有java.util.HashSet、java.util.HashMap、java.util.HashTable等),并要确保属性值完全对应相同的对象在该数据结构中唯一,仅仅重写equals(Object obj)方法是不够的,还需要重写hashCode()方法,确保属性值完全对应相同的对象调用hashCode()方法时返回的哈希值完全相同。下面重写Person类中的hashCode()方法和equals(Object obj)方法,Person类修改后的源码如下:

package com.codeke.java.test;

public class Person {
    private String name; // 姓名
    // 构造方法
    public Person(String name) {
        this.name = name;
    }
    // 工作的方法
    public void work() {
        System.out.println("person" + this.name + "在工作");
    }
    // 重写后的equals(Object o)方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person person = (Person) o;
        return name.equals(person.name);
    }
    // 重写后的hashCode()方法
    @Override
    public int hashCode() {
        return name.hashCode();
    }
}

再次执行测试类Test类中的main方法,执行输出结果如下:

integerSet的长度是 = 1
personSet的长度是 = 1

可以看到,在将对象p1和p2添加到java.util.HashSet类实例化的集合personSet中后,集合personSet的长度为1,显然此时该集合认为对象p1、p2重复了。

1.3.2、TreeSet类

java.util.TreeSet类借助二叉树(红黑树)实现java.util.Set接口,该类也是一个泛型类。该类的使用方法与java.util.HashSet类几乎完全相同,不同的是,java.util.TreeSet集合在保存元素时会对元素进行比较并按照自然的方式对元素进行排序(注意这里的排序和前文中“有序”二字的区别,有序指的是元素按照进入集合的顺序具有下标,而这里的排序值的是按照元素本身的值比较之后进行排序,和元素的下标没有关系),java.util.TreeSet集合中的元素仍然无法通过下标访问。

下面是一个示例:

package com.codeke.java.test;

import java.util.HashSet;
import java.util.TreeSet;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        // 声明变量hashSet,引用一个HashSet实例
        Set<Integer> hashSet = new HashSet<Integer>(5);
        hashSet.add(7);
        hashSet.add(25);
        hashSet.add(199);
        hashSet.add(78);
        hashSet.add(957);
        hashSet.add(3);
        // 遍历hashSet集合中的元素
        for (Integer num : hashSet) {
            System.out.print(num + " ");
        }
        System.out.println();
        // 声明变量treeSet,引用一个TreeSet实例
        Set<Integer> treeSet = new TreeSet<Integer>();
        treeSet.add(7);
        treeSet.add(25);
        treeSet.add(199);
        treeSet.add(78);
        treeSet.add(957);
        treeSet.add(3);
        // 遍历treeSet集合中的元素
        for (Integer num : treeSet) {
            System.out.print(num + " ");
        }
    }
}

执行输出结果:

25 3 957 78 7 199 
3 7 25 78 199 957

说明:

本例比较了java.util.TreeSet类的实例和java.util.HashSet类的实例在存储完全相同的若干个java.lang.Integer实例时会有什么不同,观察输出结果可以看到,集合treeSet中存储的java.lang.Integer对象已经按照其所包装的整型数值的大小进行了排序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值