6.2 List和Set接口
1. List接口
List接口继承自Collection接口,List接口实例中允许存储重复的元素,所有的元素以线性方式进行存储。在程序中可以通过索引访问List接口实例中存储的元素。另外,List接口实例中存储的元素是有序的,即元素的存入顺序和取出顺序一致。
1.1. List接口的常用方法
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引操作集合的特有方法。List接口的常用方法如下表所示。
方法声明 | 功能描述 |
void add(int index,Object element) | 将元素element插入List的index索引处 |
boolean addAll(int index,Collection c) | 将集合c所包含的所有元素插入到List集合的index索引处 |
Object get(int index) | 返回集合index索引处的元素 |
Object remove(int index) | 删除index索引处的元素 |
Object set(int index, Object element) | 将index索引处元素替换成element对象,并将替换后的元素返回 |
int indexOf(Object o) | 返回对象o在List中第一次出现的位置索引 |
int lastIndexOf(Object o) | 返回对象o在List中最后一次出现的位置索引 |
List subList(int fromIndex, int toIndex) | 返回从索引fromIndex(包括)到 toIndex(不包括)处所有元素集合组成的子集合 |
1.2. ArrayList
ArrayList是List接口的一个实现类,它是程序中最常见的一种集合。ArrayList集合内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组。
public class TestDemo {
@Test
public void test(){
ArrayList list = new ArrayList(); // 创建ArrayList集合
list.add("张三"); // 向集合中添加元素
list.add("李四");
list.add("王五");
list.add("赵六");
System.out.println("集合的长度:" + list.size()); //获取集合中元素的个数
System.out.println("第2个元素是:" + list.get(1)); //取出并打印指定位置的元素
list.remove(3); //删除索引为3的元素
System.out.println("删除索引为3的元素:"+list);
list.set(1,"李四2"); //替换索引为1的元素为李四2
System.out.println("替换索引为1的元素为李四2:"+list);
}
}
1.3. ArrayList练习
ArrayList存储多个学生信息(学生信息包含name和age属性),然后for循环输出学生信息。
-
Student类
public class Student {
private String name;
private int age;
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
-
测试类
public class TestDemo {
@Test
public void test(){
Student stu1 = new Student();
stu1.setName("张三");
stu1.setAge(20);
Student stu2 = new Student();
stu2.setName("李四");
stu2.setAge(22);
ArrayList list = new ArrayList();
list.add(stu1);
list.add(stu2);
for(int i=0;i<list.size();i++){
Student stu = (Student) list.get(i);
System.out.println(stu);
}
}
}
1.4. LinkedList
由于ArrayList集合的底层是使用一个数组来存储元素,在增加或删除指定位置的元素时,会创建新的数组,效率比较低,因此Arraylist集合不适合做大量的增删操作,而适合元素的查找。为了克服ArrayList集合在查询元素时速度很快,但在增删元素时效率较低的局限性,可以使用List接口的另一个实现类LinkedList。
LinkedList集合内部维护了一个双向循环链表,链表中的每一个元素都使用引用的方式记录它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来。当插入一个新元素时,只需要修改元素之间的引用关系即可,删除一个节点也是如此。正因为这样的存储结构,所以LinkedList集合增删效率非常高。
(1)LinkedList集合添加、删除元素过程如下图所示。
(2)LinkedList案例
public class TestDemo {
@Test
public void test(){
LinkedList link = new LinkedList(); // 创建LinkedList集合
link.add("张三");
link.add("李四");
link.add("王五");
link.add("赵六");
System.out.println(link.toString()); // 获取并打印该集合中的元素
link.add(3, "Student"); // 向link集合中索引3处插入元素Student
link.addFirst("First"); // 向link集合第一个位置插入元素First
System.out.println(link);
System.out.println(link.getFirst()); // 取出link集合中第一个元素
link.remove(3); // 移除link集合中指定索引位置为3的元素
link.removeFirst(); // 移除link集合中第一个元素
System.out.println(link);
}
}
1.5. LinkedList练习
LinkedList存储多个学生信息(学生信息包含name和age属性),然后for循环输出学生信息。
-
Student类
public class Student {
private String name;
private int age;
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
-
测试类
public class TestDemo {
@Test
public void test(){
Student stu1 = new Student();
stu1.setName("张三");
stu1.setAge(20);
Student stu2 = new Student();
stu2.setName("李四");
stu2.setAge(22);
LinkedList list = new LinkedList();
list.add(stu1);
list.add(stu2);
for(int i=0;i<list.size();i++){
Student stu = (Student) list.get(i);
System.out.println(stu);
}
}
}
2. 集合遍历
Iterator接口是Java集合框架中的一员,但它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(遍历)Collection中的元素,通常情况下Iterator对象也被称为迭代器。
2.1. 迭代器迭代元素过程
迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。迭代器迭代元素过程如下图所示。
注意:通过迭代器获取ArrayList集合中的元素时,这些元素的类型都是Object类型,如果想获取到特定类型的元素,则需要进行对数据类型强制转换。
2.2. 迭代器案例
public class TestDemo {
@Test
public void test(){
ArrayList list = new ArrayList(); // 创建ArrayList集合
list.add("张三"); // 向该集合中添加字符串
list.add("李四");
list.add("王五");
list.add("赵六");
Iterator it = list.iterator(); // 获取Iterator对象
while (it.hasNext()) { // 判断ArrayList集合中是否存在下一个元素
Object obj = it.next(); // 取出ArrayList集合中的元素
System.out.println(obj);
}
}
}
2.3. 迭代器练习
ArrayList存储多个学生信息,然后使用Iterator遍历输出学生信息。
-
Student类
public class Student {
private String name;
private int age;
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
-
测试类
public class TestDemo {
@Test
public void test(){
Student stu1 = new Student();
stu1.setName("张三");
stu1.setAge(20);
Student stu2 = new Student();
stu2.setName("李四");
stu2.setAge(22);
ArrayList list = new ArrayList();
list.add(stu1);
list.add(stu2);
//遍历
Iterator it = list.iterator();
while (it.hasNext()){
Student stu = (Student) it.next();
System.out.println(stu);
}
}
}
3. Set接口
3.1. Set接口概述
Set接口也继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充。与List接口不同的是,Set接口中元素是无序的,并且都会以某种规则保证存入的元素不出现重复。
Set接口常见的实现类有3个,分别是HashSet、LinkedHashSet、TreeSet。其中,HashSet根据对象的哈希值来确定元素在集合中的存储位置,具有良好的存取和查找性能;LinkedHashSet是链表和哈希表组合的一个数据存储结构;TreeSet则是以二叉树的方式存储元素,它可以对集合中的元素进行排序。
3.2. HashSet
HashSet是Set接口的一个实现类,它所存储的元素是不可重复的。当向HashSet集合中添加一个元素时,首先会调用该元素的hashCode()方法来确定元素的存储位置,然后再调用元素对象的equals()方法来确保该位置没有重复元素。Set集合与List集合存取元素的方式都一样,但是Set集合中的元素是无序的。
(1)HashSet案例
public class TestDemo {
@Test
public void test(){
HashSet hset = new HashSet(); // 创建HashSet集合
hset.add("张三"); // 向该Set集合中添加字符串
hset.add("李四");
hset.add("王五");
hset.add("李四"); // 向该Set集合中添加重复元素
Iterator it = hset.iterator(); // 获取Iterator对象
while (it.hasNext()) { // 通过while循环,判断集合中是否有元素
Object obj = it.next(); // 如果有元素,就调用迭代器的next()方法获取元素
System.out.println(obj);
}
}
}
(2)HashSet存储元素的流程
当调用HashSet集合的add()方法存入元素时,首先调用HashSet集合的hashCode()方法获得元素对象的哈希值,然后根据对象的哈希值计算出一个存储位置。如果该位置上没有元素,则直接将元素存入。如果该位置上有元素存在,则会调用equals()方法让当前存入的元素和该位置上的元素进行比较,如果返回的结果为false,就将该元素存入集合,返回的结果为true,则说明有重复元素,就将需要存入的重复元素舍弃。
(3)HashSet存储自定义对象
HashSet存储多个学生信息,然后使用Iterator遍历输出学生信息。
注意:HashSet中如果去掉地址不同但是内容相同的Student的重复元素,需要定义在Student类时重写hashCode()和equals()方法。
-
Student类
public class Student {
private String name;
private int age;
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
-
测试类
public class TestDemo {
@Test
public void test(){
Student stu1 = new Student();
stu1.setName("张三");
stu1.setAge(20);
Student stu2 = new Student();
stu2.setName("李四");
stu2.setAge(22);
Student stu3 = new Student();
stu3.setName("李四");
stu3.setAge(22);
HashSet hset = new HashSet(); // 创建HashSet集合
hset.add(stu1);
hset.add(stu2);
hset.add(stu3);
Iterator it = hset.iterator(); // 获取Iterator对象
while (it.hasNext()) {
Object obj = it.next();
System.out.println(obj);
}
}
}