Java|Java集合类讲解篇
一、引入
了解编程的同学们都应该知道一个必不可少的概念——数组,数组的长度固定,可以用来存放基本类型数据,也可以存放引用类型的数组,且在同一个数组中只能存放相同类型的数据。不得不说,数组的使用,极大提高我们日常的编码效率。
我们在创建Java数组时,必须指出数组的长度,并且数组一旦被创建,它的长度也就不能再变化,但是在很多应用场景中,常常出现一组数据的数目不固定的情况,如果此时我们再用数组来存储数据,会给我们的编码带来很多麻烦,甚至造成内存的浪费。因此,为了使程序能方便地存储和操纵数目不固定的一组数据,JDK类库提供了位于java.util包中的Java集合类。
与Java数组不同,Java集合不能存放基本类型数据,只能存放对象的引用,对于Java集合类,其主要包括下列四种接口:Set,List,Queue,Map,详细讲解下文将逐一阐述。
二、四种Java集合类的理解及用法
1、概述
在Java API中,Collection接口表示集合,细分到Java集合的四种子接口类型,其中Set,List,Queue都是Collection接口的子接口,而Map接口没有继承Collection接口,其继承独立接口,下图显示了Java的主要集合类的类框图。
同时,在接口Collection中声明了适用于Java集合(Set,List和Queue)的通用方法,参见下表,其中iterator()和toArray()方法都可以用来获得集合中的所有元素,前者返回Iterator对象,后者返回一个包含集合中所有元素的数组,针对Iterator接口,具体内容及方法自行了解。
方法 | 描述 |
---|---|
boolean add(Object o) | 向对象中加入一个对象的引用 |
void clear() | 删除集合中的所有对象,即不再持有这些对象的引用 |
boolean contains(Object o) | 判断在集合中是否持有特定对象的引用 |
boolean isEmpty() | 判断集合是否为空 |
Iterator iterator() | 返回一个Iterator对象,可用它来遍历集合中的元素 |
boolean remove(Object o) | 从集合中删除一个对象的引用 |
int size() | 返回集合中元素的数目 |
Object[] toArray() | 返回一个数组,该数组包含几何中的所有元素 |
2、Set(集)
针对Set类型的集合,简单来说,其与数学中的集合最接近,不允许包含重复的元素,且集合中的对象不按特定方式排序。Set接口主要有三个实现类:HashSet,TreeSet,LinkedHashSet(也有部分教材中说是两类,因为实质来说,LinkedHashSet是HashSet的子类)。
具体来说,HashSet类按照哈希算法来存取集合中的对象,存取速度快;而LinkedHashSet,其不仅实现了哈希算法,并且实现链表数据结构,了解链表的读者都知道,链表的一大好处就是增删较顺序表更加方便;对于TreeSet,其实现了SortedSet接口,具有排序功能,接下来将对部分实现类进行讲解。
2.1 一般用法
Set集合中存放对象的引用(对于基本类型数据的存储,通过自动装箱和拆箱的功能实现),并且Set集合没有重复对象,具体实例参考下列代码,由于s1,s2变量引用同一个字符串变量“hello”,因此最后输出结果为2。
Set<String> set=new HashSet<String>();
String s1=new String("hello");
String s2=s1; //s1,s2为同一个字符串
String s3=new String("Java");
set.add(s1);
set.add(s2);//该操作不会向集合中加入元素
set.add(s3);
System.out.println(set.size());//最终输出结果为2
2.2 HashSet类
HashSet类按照哈希算法来存取集合中的对象,极大提升存取和查找性能。当HashSet内添加对象时,HashSet会调用对象的hashCode()方法获取哈希码,根据哈希码来计算对象在集合中的存放位置,在Object类中自动定义了**hashCode()和equals()**方法,当两对象equals()方法返回true,即两对象哈希码(hashCode()返回值)相等,当然这两种方法可以在自定义类中进行重写,并且一般情况下我们需要对两方法同时进行重写覆盖,否则会导致HashSet无法正常工作的异常发生,具体对比代码示例参考2.2.1及2.2.2代码。
2.2.1 示例代码1
class Customer{
private int age;
private String name;
public Customer(int age,String name){
this.age=age;
this.name=name;
}
}
public class HashSetinstantiation {
public static void main(String args[]){
Set<Customer> set=new HashSet<Customer>();
Customer s1=new Customer(15,"lilei");
Customer s2=new Customer(15,"lilei");
set.add(s1);
set.add(s2);
System.out.println(set.size());//最终输出结果为2
}
}
在此示例中,代码中未对hashCode()和equals()方法进行重写,虽然s1,s2两对象内部内容一致,但是由于二者属于不同对象,因此返回hashcode码不同,最终以两个不同对象的形式加入到set集合中。
2.2.2 示例代码2
class Customer{
private int age;
private String name;
public Customer(int age,String name){
this.age=age;
this.name=name;
}
//重写equals()方法
@Override
public boolean equals(Object obj) {
if(this==obj) return true;
if(!(obj instanceof Customer)) return false;
final Customer o=(Customer) obj;
if(this.age==o.age&&this.name==o.name)
return true;
else
return false;
}
//重写hashCode()方法
@Override
public int hashCode() {
int result;
result=(name==null?0:name.hashCode());
result=29*result+age;
return result;
}
}
public class HashSetinstantiation {
public static void main(String args[]){
Set<Customer> set=new HashSet<Customer>();
Customer s1=new Customer(15,"lilei");
Customer s2=new Customer(15,"lilei");
set.add(s1);
set.add(s2);
System.out.println(set.size());//最终输出结果为1
}
}
在此示例中,我们重写了hashCode()和equals()方法,其中规定只要二者的age和name相同,其equals()返回true,并且hashcode()返回码相同,因此最终两对象对于se集合来说是一个相同元素,最终只能加入一个到集合中。
2.3 TreeSet类
TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序,下列代码创建了一个TreeSet对象,然后计入了3个Integer对象,最终输出Set集合内容。
Set<Integer> set=new TreeSet<Integer>();
set.add(8);//自动装箱,把int转换成相应的Integet对象,再加入到Set中
set.add(7);
set.add(9);
for(int i:set)//自动拆箱,把集合中的Integer对象转换为int基本类型的数据
System.out.println(i+" ");//输出结果为7,8,9
我们将对象加入到TreeSet集合内后,TreeSet支持两种排序方式:自然排序和客户化排序,默认情况下使用自然排序方法。
2.3.1 自然排序
JDK类库中有一些如Integer、Double以及String等类实现了实现了Comparable接口(其中包含一个compareTo(Object o)方法,返回整数类型)。对于x.compareTo(y),若x=y,返回0;若x>y,返回值大于0;若x<y,返回值小于0,具体相关类的排序方式参考下表。
类 | 排序 |
---|---|
BigInteger,Byte,Double,Float,Integer,Long,Short | 数字大小排序 |
Charater | 字符Unicode值的数字大小排序 |
String | 字符串中字符的Unicode值数字大小排序 |
对于一些没有实现Comparable接口的类,需要我们自行实现Comparable接口,相应的在类中实现compareTo()方法,详细参考下述代码。
public class Customer implements Comparable{
private int age;
private String name;
...
@Override
public int compareTo(Object o) {
Customer obj=(Customer)o;
//自行界定排序方法,参考不同情况返回不同不同值
}
}
2.3.2 客户化排序
除了自然排序外,TreeSet还支持客户化排序。java.util.Comparator接口提供了排序方式,Comparator接口有一个compare(T x,T y)的方法,用于比较两个对象的大小。、若x=y,返回0;若x>y,返回值大于0;若x<y,返回值小于0。具体代码参考如下,先创建一个实现Comparator接口的CustomerComparator类,然后在TreeSet中进行调用。
public class CustomerComparator implements Comparator<Customer>{
@Override
public int compare(Customer o1, Customer o2) {
//自行界定排序方法,参考不同情况返回不同不同值
}
public static void main(String args[]){
Set<Customer> set=new TreeSet<Customer>(new CustomerComparator());
...
}
}
3、List(列表)
3.1 总述
List的主要特征是其元素以线性方式存储,集合中允许存放重复对象。List主要的实现类包括:ArrayList以及LinkedList。
3.2 ArrayList及LinkedList讲解
-
ArrayList:ArrayList代表长度可变的数组。ArrayList实现了RandomAccess接口,其仅仅是个标识类型的接口,不包含任何方法,使得ArrayList对元素进行快速的随机访问,但是向ArrayList中插入或删除元素的速度较慢,代码示例如下。
ArrayList<String> cityList = new ArrayList<String>(); cityList.add("London"); cityList.add("Denver"); cityList.add("Paris"); cityList.add("Miami"); cityList.add("Seoul"); cityList.add("Tokyo"); //返回List的size() System.out.println( cityList.size()); // List中是否包含"Miami",返回true System.out.println(cityList.contains("Miami")); // 返回"Denver"的索引号,返回索引1 System.out.println(cityList.indexOf("Denver")); // List是否为空,返回false System.out.println(cityList.isEmpty()); //返回List中索引为0的城市名 System.out.println(cityList.get(0)); //在索引2处添加"Xian"的元素 cityList.add(2, "Xian"); //删除名为"Miami"的元素 cityList.remove("Miami"); //删除索引为1的元素 cityList.remove(1); //输出[London, Xian, Paris, Seoul, Tokyo] System.out.println(cityList.toString()); //输出Tokyo Seoul Paris Xian London for (int i = cityList.size() - 1; i >= 0; i--) System.out.print(cityList.get(i) + " ");
-
LinkedList:在实现中采用链表数据结构。了解过链表的读者都清楚,链表对顺序访问进行了优化,使得插入和删除元素的速度较快,随机访问则相对较慢。随机访问是指检索位于特定索引位置的元素。LinkedLIst单独具有addList()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()方法这些方法可以使得LinkedList可以作为堆栈以及队列使用,代码示例如下。
LinkedList<String> linkedList=new LinkedList<String>(); linkedList.add("1"); linkedList.add("2"); linkedList.add("3"); linkedList.add("4"); linkedList.add("5"); linkedList.add("6"); //输出linkedlist System.out.println(linkedList); //输出索引为3的元素 System.out.println(linkedList.get(3)); //输出第一个元素 System.out.println(linkedList.getFirst()); //输出最后一个元素 System.out.println(linkedList.getLast());
4、Queue(队列)
4.1 一般用法
有队列数据结构基础的读者应该都了解,队列的基本特点是队尾添加元素,队头删除元素,先进先出,并且允许又重复的元素出现,下列将罗列一些Queue的基础方法。
-
加入元素的方法:
boolean add(E element) boolean offer(E element)
对于大多数队列的容量不受限制,但对于某些限制容量的队列实现类,容量满时,用add()方法添加元素会抛出IllegalStateException异常。
-
删除元素的方法:
E remove() E poll()
两个方法均会删除队列头部元素。但是这两个方法区别在于,如果队列为空,前者抛出异常,后者返回null。
-
获取元素的方法:
E element() E peek()
两个方法均会返回队列头部元素。但是这两个方法区别在于,如果队列为空,前者抛出异常,后者返回null。
4.2 Deque(双向队列)
queue接口是单向接口,它有一个子接口Deque,表示双向队列,其特点在于在队列的头部和末尾都可以添加或删除元素,下列将罗列一些Deque的基础方法。
-
加入元素的方法(队头和末尾双向操作):
方法 队列已满 void addFirst(E element) 抛出异常 void addLast(E element) 抛出异常 boolean offerFirst(E element) Null boolean offerLast(E element) Null -
删除元素的方法(队头和末尾双向操作):
方法 队列已空 E removeFirst() 抛出异常 E removeLast() 抛出异常 E pollFirst() Null E pollLast() Null -
获取元素的方法:
方法 队列已空 E getFirst() 抛出异常 E getLast() 抛出异常 E peekFirst() Null E peekLast() Nul
4.3 PriorityQueue(优先级队列)
PriorityQueue会按照排序的方式对队列中的元素排序和检索。因此加入到PriorityQueue中的对象必须实现Comparable接口,提供对元素排序时两个元素之间的比较原则。
Queue<Integer> queue=new PriorityQueue<Integer>();
queue.add(67);
queue.add(12);
queue.add(33);
for(Integer e:queue)
System.out.println(e+" ");//12 67 33
While(!queue.isEmpty())
System.out.println(queue.remove()+" ");//12 33 67
不难发现,我们在遍历优先级队列时,PriorityQueue的入列操作并没有对加入的元素进行优先级排序,仅仅保证了第一个元素是最小元素即可,这就不难解释,在通过remove()方法删除元素时,该方法会依次删除当前队列中的最小元素,因此最后会以12,33,67的自然排序法进行输出。
##5、Map(映射)
5.1 一般用法
Map(映射)是一种把键对象和值对象进行映射的集合,它的每一个元素都包含一对键对象和值对象,而值对象仍然可以是Map类型,因此形成多级映射。向Map集合中加入元素时,必须提供一对键对象和值对象,,从Map集合中检索元素时,只要给出键对象,就可以返回相应的值对象,示例代码参考如下。
Map<String,String> map=new HashMap<String,String>();
map.put("1","Monday");
map.put("2","Tuesday");
String day=map.get("1");//day=Monday
5.2 HashMap及TreeMap讲解
-
HashMap:HashMap按照哈希算法来存取键对象,有很好的存取性能,为了保证HashMap能正常工作,同理HashSet,需要通过equals()以及hashCode()方法来进行判定键对象是否重复,是否进行对象覆盖示例代码如下:
HashMap<String, Object> map = new HashMap<String, Object>(); // 添加元素 map.put("Join", new Integer(96)); map.put("Tom", new Integer(97)); map.put("Jane", new Integer(98)); map.put("Hall", new Integer(99)); // 得到映射的集合,Map的entrySet()方法返回一个Set集合,每个Entry存储Map的一对键与值 Set<Entry<String, Object>> set = map.entrySet(); // 运用泛型得到Iterator对象(隐藏底层集合的数据结构),通过hasNext()方法来进行遍历 Iterator<Entry<String, Object>> i = set.iterator(); // 遍历 while (i.hasNext()) { Entry<String, Object> me = i.next(); System.out.println(me.getKey() + ": " + me.getValue()); }
-
TreeMap:实现了SortedMap接口,能对键对象进行排序,当然,正如我们之前提到的,和TreeSet一样,其支持自然排序和客户化排序两种方式,示例代码如下:
Map<String,String> map=new TreeMap<String,String>(); map.put("1","Monday"); map.put("2","Tuesday"); map.put("4","Thursday"); map.put("3","Wednesday"); Set<String> keys=map.keySet(); for(String key:keys){ String value=map.get(key); System.out.println(kay+" "+value);//1 Monday 2 Tuesday 3 Wednesday 4 Thursday }
三 总结
Java集合给我们的日常编程带来了较数组而言更广泛的应用空间以及性能提升,希望大家看完此文后对Java集合类有一个较为深刻的认识,由于小编水平有限,讲不到的地方,还请读者们自行补充,若文中理论或者代码有错误,希望大家及时联系小编进行修正,谢谢大家!