JavaSE之集合类的Collection集合接口

时间:2022-09-03 00:05:07

在java.util包下提供了两个核心接口用于处理集合类:Collection接口、Map接口。本文章主要介绍集合类中关于Collection接口的相关集合类以及集合的4种标准输出方式。

1. Collection接口

public interface Collection<E> extends Iterable<E> 

该接口定义了如下方法:

(1)向集合中添加数据:

    boolean add(E e);

(2)向集合中添加一组数据:

    boolean addAll(Collection<? extends E> c);

(3)清空集合数据:

    void clear();

(4)查找数据是否存在(需要使用equals()方法):

    boolean contains(Object o);

(5)删除数据(需要使用equals()方法):

    boolean remove(Object o);

(6)取得集合大小:

    int size();

(7)将集合变成对象数组返回:

    Object[] toArray();

(8)取得Iterable接口对象,用于集合输出:

    Iterator<E> iterator();

Collection接口下有两个子接口List接口、Set接口。下面一一介绍这两个接口。

2. Collection接口的子接口------List接口

List接口在java.util包下,而在java.awt包下有一个List类用于界面设计,和java.util包下的List接口没有任何关系。List接口中有两个比较重要的扩充方法:

(1)根据索引取得数据:

    E get(int index);

(2)根据索引修改数据:

    E set(int index, E element);

由于List本身也是接口,要想取得其实例化对象就必须有子类,List接口下有常用的三个子类:ArrayList、Vector、LinkedList类。下面一一介绍:

2.1 实现List接口的子类------ArrayList类

ArrayList类是针对List接口的数组实现的,故ArrayList类的底层是数组。下面观察利用ArrayList类对List接口进行的基本操作:

package www.bit.java.test;

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

public class Test3 {
	public static void main(String[] args) {
		List<String> list=new ArrayList<>();
		list.add("数据1");
		list.add("数据2");
		list.add("数据2");
		System.out.println(list);
	}
}

运行结果:

[数据1, 数据2, 数据2]

通过代码的运行结果可以看出,List接口允许保存重复数据。

再来观察get()方法和set()方法的调用:

package www.bit.java.test;

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

public class Test3 {
	public static void main(String[] args) {
		List<String> list=new ArrayList<>();
		list.add("数据1");
		list.add("数据2");
		list.add("数据2");
		//调用set()方法,修改指定索引的元素
		System.out.println(list.set(2,"数据3"));
		//调用get()方法,取得指定索引的元素
		for(int i=0;i<list.size();i++) {
			System.out.println(list.get(i));
		}	
	}
}

运行结果:

数据2
数据1
数据2
数据3

通过代码以及运行结果可以看出,set()方法返回的是修改指定索引前的元素值,还需要注意的是,get()方法和set()方法是List接口特有的方法,Collection接口没有定义这两个方法。

在开发中,集合里面一般保存的数据类型不是基本数据类型而是简单Java类,下面观察向集合中保存简单Java类对象的操作:

package www.bit.java.work;

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

class Person{//简单Java
	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;
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "Preson [name=" + name + ", age=" + age + "]";
	}
}
public class Test5 {
	public static void main(String[] args) {
		List<Person> list = new ArrayList<>();
		list.add(new Person("張三",20));
		list.add(new Person("李四",21));
		list.add(new Person("王麻子",31));
		System.out.println(list.contains(new Person("李四",21)));
		System.out.println(list.remove(new Person("王麻子",31)));
		System.out.println(list);
	}
}

运行结果如下:

false
false
[Preson [name=張三, age=20], Preson [name=李四, age=21], Preson [name=王麻子, age=31]]

通过代码以及运行结果可以看出,调用contiains()方法并没有找到对应的数据,调用remove()方法并没有删除对应的数据,这是因为什么?其实是因为new Person()是在堆上保存着其引用,每次调用new出来的对象都不一样,故其引用也不相同,所以会出现没有找到以及没删除指定数据的现象,要是删除指定的数据需要覆写equals()方法,因为集合操作简单Java类时,对于contains()和remove()方法需要equals()方法的支持。

修改以上代码,覆写equals()方法后再观察运行结果:

package www.bit.java.work;

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

class Person{//简单Java
	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;
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "Preson [name=" + name + ", age=" + age + "]";
	}
	@Override
	public boolean equals(Object obj) {
		if(obj==this) {
			return true;
		}
		if(obj==null||obj.getClass()!=this.getClass()) {
			return false;
		}
		Person person=(Person)obj;
		return Objects.equals(this.name,person.getName())
				&&Objects.equals(this.age,person.getAge());
	}
}
public class Test5 {
	public static void main(String[] args) {
		List<Person> list = new ArrayList<>();
		list.add(new Person("張三",20));
		list.add(new Person("李四",21));
		list.add(new Person("王麻子",31));
		System.out.println(list.contains(new Person("李四",21)));
		System.out.println(list.remove(new Person("王麻子",31)));
		System.out.println(list);
	}
}

运行结果:

true
true
[Preson [name=張三, age=20], Preson [name=李四, age=21]]

此时,通过运行结果可以看出,覆写了equals()方法后能找到指定的数据以及能删除指定的数据。

2.2 实现List接口的子类------Vector类

Vector类是从JDK1.0时提出的,而ArrayList接口时从JDK1.2时提出的。Vector类的底层也是数组。使用Vector类实例化List接口的对象后,其他使用的方式与ArrayList一样,下面通过Vector类实例化List接口对象进行相关操作:

package www.bit.java.work;

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

public class Test5 {
	public static void main(String[] args) {
		List<String> list = new Vector<>();
		list.add("数据1");
		list.add("数据2");
		list.add("数据2");
		list.add("数据3");
		System.out.println(list);
	}
}

运行结果如下:

[数据1, 数据2, 数据2, 数据3]

那么,既然ArrayList类与Vector类在使用上基本一致,那么ArrayList类与Vector类的区别在哪儿呢?主要从四个方面介绍它们之间的区别:

(1)提出时间:ArrayList是从JDK1.2提出的;Vector是从JDK1.0提出的。

(2)处理形式:ArrayList是异步处理,性能高;Vector是同步处理,性能低。

(3)数据安全:ArrayList是线程不安全;Vector是线程安全。

(4)输出形式:ArrayList支持Iterator、ListIterator、foreach;Vector支持Iterator、ListIterator、foreach、Enumeration。

2.3 List接口的子类------LinkedList类

LinkedList类与ArrayList类、Vector类的区别是LinkedList类的底层是用链表实现的,而ArrayList类、Vector类的底层是用数组实现的。在使用方面与ArrayList类、Vector类没有任何区别。

下面通过LinkedList类实例化List接口对象,并进行相关操作:

package www.bit.java.work;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Vector;

public class Test5 {
	public static void main(String[] args) {
		List<String> list = new LinkedList<>();
		list.add("数据1");
		list.add("数据2");
		list.add("数据2");
		list.add("数据3");
		System.out.println(list);
	}
}

运行结果:

[数据1, 数据2, 数据2, 数据3]

3. Collection接口的子接口------Set接口

Collection接口的子接口List接口允许保存重复的数据,而Set接口不允许保存重复的数据。同List接口一样,既然是接口没法实例化其对象,就需要该接口的子类去实例化对象。实现Set接口的子类有:HashSet类、TreeSet类。下面一一介绍这两个子类:

3.1 实现Set接口的子类------HashSet类

HashSet类的底层是由链表+红黑树实现的,集合中的数据若为基本数据类型则是有序存储,其他数据类型是无序存储的并且HashSet允许数据为null。下面通过HashSet子类实例化Set接口对象并进行集合的相关操作:

package www.bit.java.work;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;

public class Test5 {
	public static void main(String[] args) {
		Set<String> set = new HashSet<>();
		set.add("C");
		set.add("A");
		set.add("D");
		set.add("B");
		set.add("A");
		set.add(null);
		System.out.println(set);
	}
}

运行结果:

[null, A, B, C, D]

3.2 实现Set接口的子类------TreeSet类

TreeSet类的底层是由红黑树实现的,集合中的数据是有序存储的但是TreeSet不允许数据为null。下面通过TreeSet子类实例化Set接口对象并进行集合的相关操作:

package www.bit.java.work;

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

public class Test5 {
	public static void main(String[] args) {
		Set<String> set = new TreeSet<>();
		set.add("C");
		set.add("A");
		set.add("D");
		set.add("B");
		set.add("A");
		System.out.println(set);
	}
}

运行结果如下:

[A, B, C, D]

3.3 关于TreeSet有序存储的分析

既然TreeSet子类可以对结合进行有序存储,那么我们可以利用其特性实现数据的排序处理操作。此时,要想实现集合的有序存储,实际上是对数据对象的排序,那么就要求对象所在类一定要实现Comparable接口并且覆写compareTo()方法。在TreeSet类的操作中,我们存储的对象为String类对象从而在运行结果中可以看出集合是有序存储,实际上是因为String类实现了Comparable接口并且覆写了compareTo()方法。

下面存储简单Java类对象并在其所在类中实现Comparable接口以及覆写compareTo()方法,从而对该集合进行有序存储。

package www.bit.java.work;

import java.util.Set;
import java.util.TreeSet;
class Person implements Comparable<Person>{
	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;
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int compareTo(Person person) {
		if(this.age>person.age) {
			return 1;
		}else if(this.age<person.age) {
			return -1;
		}else {
			return this.name.compareTo(person.name);
		}
	}
}
public class Test5 {
	public static void main(String[] args) {
		Set<Person> set=new TreeSet<>();
		set.add(new Person("张三",11));
		set.add(new Person("张三",10));
		set.add(new Person("李四",11));
		set.add(new Person("张三",12));
		System.out.println(set);
	}
}

运行结果如下:

[Person [name=张三, age=19], Person [name=李四, age=20], Person [name=王麻子, age=20], Person [name=张三, age=21]

在实际的使用中,由于使用TreeSet类过于麻烦需要实现Comparable并覆写compareTo()方法,所以一般使用的是HashSet类。

3.4 重复元素的判断

由于Set接口不允许出现重复元素,那么Set接口是怎么判断重复元素的呢?在使用TreeSet子类进行数据的保存时,重复元素的判断是通过实现Comaprable接口中的compareTo()方法完成的。但是由于HashSet子类没有实现Comparable接口,所以它判断重复元素的方式依靠的是Object类中的两个方法:hashCode()方法和equals()方法。

在Java中进行对象的比较操作:通过一个对象的唯一编码找到该对象的信息,当编码匹配时再调用equals()方法进行内容的比较

(1)hash码比较:

    public native int hashCode();

(2)对象比较:

    public boolean equals(Object obj)

所以Set接口中的HashSet实现判断数据重复的方式是覆写Object类的hashCode()方法以及equals()方法,下面进行相关操作:

package www.bit.java.work;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
class Person{
	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;
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int hashCode() {
		return Objects.hash(name,age);
	}
	@Override
	public boolean equals(Object obj) {
		if(obj==this) {
			return true;
		}
		if(obj==null||obj.getClass()!=this.getClass()) {
			return false;
		}
		Person person=(Person)obj;
		return Objects.equals(this.name,person.name)
				&&Objects.equals(this.age,person.age);
	}
}
public class Test5 {
	public static void main(String[] args) {
		Set<Person> set=new HashSet<>();
		set.add(new Person("张三",11));
		set.add(new Person("张三",11));
		set.add(new Person("李四",11));
		set.add(new Person("张三",12));
		System.out.println(set);
	}
}

运行结果:

[Person [name=李四, age=11], Person [name=张三, age=12], Person [name=张三, age=11]]

4. 集合标准输出

在之前的所有操作中,我们均利用toString()方法或者利用List接口中的get()方法对集合中的元素进行输出,但这些都不是集合的标准输出,集合的标准输出共有四种方式:Iterator、ListIterator、Enumration、foreach。

4.1 迭代输出:Iterator

在JDK1.5之前,Collection接口中就定义了iterator()方法用于取得Iterator接口的实例化对象,而在JDK1.5以后,将iterator()方法提升为Iterator接口中的方法。对于使用Iterator实现集合标准输出的三个重要方法:

(1)iterator()方法:用于取得集合对应的Iterator对象

(2)hasNext()方法:判断是否有下一个元素

(3)next()方法:取得当前元素

下面通过调用以上三个方法实现标准输出:

package www.bit.java.work;

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

public class Test5 {
	public static void main(String[] args) {
		List<String> list=new ArrayList<>();
		list.add("ABC");
		list.add("def");
		list.add("hello");
		list.add("HI");
		//1.实例化Iterator对象
		Iterator<String> iterator=list.iterator();
		//2.判断是否有下一个元素
		while(iterator.hasNext()) {
			//3.取得当前元素
			String string=iterator.next();
			System.out.println(string);
		}
	}
}

运行结果:

ABC
def
hello
HI

4.2 双向迭代输出:ListIterator

Iterator标准输出有一个特点是只能从前向后进行内容的迭代输出,如果想要进行双向迭代输出(即可以从前向后迭代输出也可以从后向前迭代输出),需要使用Iterator接口的子接口ListIterator接口。这里需要注意的是:只有List接口才可以使用ListIterator接口进行集合的标准输出,而Set接口无法使用ListIterator接口进行集合的标准输出。

先来介绍ListIterator接口:

public interface ListIterator<E> extends Iterator<E>

Iterator接口对象是由Collection接口支持的,ListIterator接口对象是由List接口支持的。List接口提供以下方法实例化ListIterator对象:

    ListIterator<E> listIterator();

在使用ListIterator接口对集合进行标准输出的方法主要有以下几个:

(1)判断是否有下一个元素:hasNext()方法

(2)取得下一个元素:next()方法

(3)判断是否有前一个元素:hasPrevious()方法

(4)取得前一个元素:previous()方法

下面调用以上方法实现集合的双向标准输出:

package www.bit.java.work;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class Test5 {
	public static void main(String[] args) {
		List<String> list=new ArrayList<>();
		list.add("ABC");
		list.add("def");
		list.add("hello");
		list.add("HI");
		//1.实例化ListIterator对象
		ListIterator<String> iterator=list.listIterator();
		System.out.println("从前向后输出:");
		//2.判断是否有下一个元素
		while(iterator.hasNext()) {
			//3.取得当前元素
			String string=iterator.next();
			System.out.print(string+" ");
		}
		System.out.println("\n从后向前输出:");
		//2.判断是否有下一个元素
		while(iterator.hasPrevious()) {
			//3.取得当前元素
			String string=iterator.previous();
			System.out.print(string+" ");
		}
	}
}

运行结果:

从前向后输出:
ABC def hello HI 
从后向前输出:
HI hello def ABC 
4.3 枚举输出:Enumeration

在JDK1.0时引入了Enumeration输出接口,在JDK1.5时对Enumeration接口进行的一定的修改。先来观察Enumeration接口的定义:

public interface Enumeration<E> 

在使用Enumeration接口实现集合的标准输出时,用到Enumeration接口的两个方法:

(1)判断是否有下一个元素:hasMoreElements()方法

(2)取得元素:nextElement()方法

但是要想取得Enumeration接口的对象,不再是依靠Collection、List、Set等接口,而是依靠Vector子类,因为早期设计Enumeration接口时就是为Vector子类服务的。在Vector子类中提供了一个取得Enumeration接口对象的方法:

    public Enumeration<E> elements()

下面通过调用以上方法实现集合的标准枚举输出:

package www.bit.java.work;

import java.util.Enumeration;
import java.util.Vector;

public class Test5 {
	public static void main(String[] args) {
		Vector<String> vector=new Vector<>();
		vector.add("abc");
		vector.add("hello");
		vector.add("hi");
		//1.实例化Enumeration接口对象
		Enumeration<String> enumeration=vector.elements();
		//2.判断是否有下一个元素
		while(enumeration.hasMoreElements()) {
			String string=enumeration.nextElement();
			System.out.println(string);
		}
	}
}

运行结果:

abc
hello
hi

4.4 foreach输出

从JDK1.5开始foreach支持输出数组,实际上也可以利用它输出集合,调用如下:

package www.bit.java.work;

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

public class Test5 {
	public static void main(String[] args) {
		List<String> list=new ArrayList<>();
		list.add("hello");
		list.add("world");
		list.add("hello");
		list.add("world");
		for(String string : list) {
			System.out.println(string);
		}
	}
}

运行结果:

hello
world
hello
world

关于集合的4种输出方式:推荐使用Iterator。