Minecraft(我的世界)中文论坛
标题: 【万年坑】【本章完结】Java高手训练营第十章:集合类
作者: ufof 时间: 2015-11-14 12:34
标题: 【万年坑】【本章完结】Java高手训练营第十章:集合类
本帖最后由 ufof 于 2016-1-24 23:17 编辑
10.1 集合类体系
提前说一下,从这一章开始,建议大家使用一些高级的IDE了。本章以及以后使用Eclipse IDE示范。
10.1.1 集合类概述
如果要储存多个对象,人们很容易想到数组。不过我们在数组章中已经提过了,数组的长度是不可变的。所以说如果不光要储存多个对象,还要对其这些对象进行编辑,例如增删改查等,就稍微有些麻烦。
集合类是Java中十分实用的一些工具类。这些类可以用来储存对象,而且长度是可变的,增删改查这些操作十分容易。在开发当中,这些类也是十分常用的。这一章开始,我们就来学习集合类。
10.1.2 集合类体系
集合类中的一大部分是接口。不过真正使用的肯定是这些接口的实现类。
Collection接口是所有集合接口的父接口,Collection接口有两个子接口:List和Set。
集合类中还有一个接口,Map,但是这个接口没有继承Collection。因为Map中的方法稍微有一些特殊。
那么,List、Set、Map之间有什么区别呢?
- List:对象是有序的,可以重复
- Set:对象是无序的,不可以重复
- Map:对象都是成对出现的,每一个键对应一个值

这三个接口也都有自己的实现类,不同的实现类都有自己的特点。关于实现类,我们会在后面的章节中详细的讲。
那么,简单的来说,这个体系是这样的:

好的,这个体系我们大概已经了解了。下一节我们来学习Collection接口。
本章完结
- 集合类是Java的工具类,用于存储对象,长度可变
- Collection接口有List和Set子接口
- Map接口属于集合类,但是不继承Collection接口
- List、Set、Map有各自的特点,他们的实现类也有不同的特点
10.2 Collection接口
10.2.1 Collection接口概述
在上一节中提到了,Collection是List和Set接口的父接口,所以说Collection接口中的方法是通用的。为了更加方便的了解集合类体系的方法,我们就从这个父接口中入手。
注意:大家在查阅相关集合类的API文档时,可能会见到类似<E>、<T>、<K,V>等。这些是泛型,我们还没有讲到,请读者暂时无视。
10.2.2 Collection接口方法概述
Collection接口有下列常用方法(查API大法好):
- boolean add(Object obj); //在集合中加入一个obj元素,若加入成功,返回true
- boolean addAll(Collection c); //在集合中加入c中的所有元素,若加入成功,返回true
- void clear(); //清除当前集合中的所有元素
- boolean contains(Object o); //如果当前集合中包含o,返回true
- boolean containsAll(Collection c) //如果当前集合中包含c中的所有元素,返回true
- boolean isEmpty(); //如果当前集合中没有元素,返回true
- Iterator iterator(); //返回本集合对应的迭代器,详见下一节
- boolean remove(Object o); //如果集合中含有与o相同的对象,删除,并返回true
- boolean removeAll(Collection c) //如果集合中含有c中的所有对象,删除,并返回true
- int size(); //返回集合的长度,也就是包含的对象数量
- Object[] toArray(); //将集合中的元素化为数组,并返回
由于Collection是接口,不能实例化,演示使用Collection接口子接口List的实现类ArrayList演示。
- import java.util.*;
- public class CollectionDemo {
- public static void main(String[] args) {
- //通过接口多态,让Collection接口实例指向ArrayList实现类
- Collection collection = new ArrayList();
-
- collection.add("abc"); //将String类引用"abc"加入collection
- collection.add(4); //将Integer类4加入collection
-
- Collection collection2 = new ArrayList();
- collection2.addAll(collection); //将collection中的所有元素加入collection2
- collection.clear(); //将collection集合的所有元素清除
-
- //打印collection的isEmpty()方法的返回值
- System.out.println("collection实例是否为空?"+collection.isEmpty());
-
-
- //打印collection2的size()方法的返回值
- System.out.println("collection2实例的长度是"+collection2.size());
-
- collection2.remove(4); //将4从collection2中移除
- collection2.add(true); //将Boolean类true加入collection2中
-
- Object[] arr = collection2.toArray(); //将collection2转为Object[]类型
- for(int x = 0;x<arr.length;x++){ //遍历这个素组
- System.out.println("collection2的第"+(x+1)+"元素是:"+arr[x]);
- }
- }
- }
复制代码 结果:

注:如果使用命令行编译这个程序,在编译时会出现警告,在Eclipse当中这段程序大部分会被打上黄色下划线,是因为我们没有添加泛型。不过读者不需要理会,运行不会有问题。
在这个程序当中,我对collection和collection2这两个实例使用了Collection接口的方法来进行增删改查等操作。这些方法也是List和Set的基本方法。
那么,如何对Collection接口中的元素进行遍历呢?Collection接口为我们提供Iterator接口,也就是迭代器来获取元素。我们在下一节中会讲解。
本章小结
- Collection接口是List和Set的父接口,所以说其中的方法是通用的
- Collection中的方法基本都是增删改查,例如add()、remove()、contains()等
10.3 Iterator接口
10.3.1 Iterator接口概述
Iterator接口称作迭代器。每一个Collection都会有一个与之对应的Iterator。迭代器用于按顺序获取一个Collection中的元素,十分常用。
不过,Iterator是接口,不可能直接new出来。Collection接口中的iterator()方法会返回与之对应的迭代器。
10.3.2 Iterator中的方法
获取iterator()的方法:
- Iterator iterator(); //返回该Collection对应的迭代器
Iterator中有两个常用方法:
- boolean hasNext(); //若还有元素可以取出,返回true
- Object next(); //取出下一个元素
使用这两个方法,可以方便的将Collection中的元素取出。我们可以建立一个while循环,以hasNext()为循环条件,打印next()。
- import java.util.*;
- public class IteratorDemo {
- public static void main(String[] args) {
- Collection c = new ArrayList();
- //将三个元素添加至c中
- c.add("元素1");
- c.add("元素2");
- c.add("元素3");
-
- //通过Collection类的iterator()方法,返回其对应的迭代器
- Iterator it = c.iterator();
-
- //循环
- while(it.hasNext()){ //如果it中还有元素
- String str = (String) it.next(); //将it中的元素向下转换至String(多态)
- System.out.println(str);
- }
- }
- }
复制代码 结果:

在这个程序当中,最后的while循环以it.hasNext(),也就是是否还有元素可以取出为条件。在循环体当中,将it的下一个元素向下转换成String类,这样可行是因为Collection中的元素的编译时类型都是Object,但是运行时类型便是元素的原本类型,也就是多态(如果忘记了可以回去看面向对象(上)的多态和向下转换)。向下转换之后将这个String打印即可。
不过很容易发现,这样向下转换是有安全隐患的。如果Collection中的元素的运行时类型不是String,我们还将其向下转换为String,就会出现ClassCastException。我们以后学习泛型就可以解决这个问题,读者先无视。
10.3.3 迭代器的原理
迭代器的原理其实很简单。在获取到Collection中的元素之后,会有一个指针放置在第一个元素之前。由于指针后面还有元素,hasNext()返回true,next()便会返回指针后面的元素。
每当next()获取指针后面的元素之后,指针会走到第一个元素之后,第二个元素之前。然后重复上述步骤。

本章小结:
- Iterator(迭代器)接口用于遍历Collection中的元素
- Collection提供iterator()方法返回与其对应的迭代器
- 迭代器中的hasNext()方法用于判断还有没有元素可以取出
- next()方法返回下一个元素
- 迭代器相当于一个指针,每当获取一个元素向前移动
10.4 List接口
10.4.1 List接口概述
List接口是Collection接口的子接口,是集合类中必不可少的接口。List字面上指的是“列表”、“清单”等,这个接口的特点也是如此。List中的元素是按顺序一个一个排列的,每一个元素都有自己的下标(和数组差不多),因此,List接口的方法比Set丰富的多。这一节,我们就来重点学习一下List接口的特有方法。
10.4.2 List接口特有方法
因为List接口是Collection的子接口,Collection接口中的方法全都被继承过来了。然而关于Collection接口的方法我们已经讲过了,所以我们今天只讲List的特有方法。
- void add(int index, Object element); //在指定的下标位置插入一个元素
- boolean addAll(int index, Collection c); //在指定的下标位置插入c中的所有元素
- Object get(int index); //获取指定下标位置的元素
- int indexOf(Object o); //获取指定对象在List中的下标位置,如果没有找到这个元素,返回-1
- ListIterator listIterator(); //获取与本List相对应得ListIterator,ListIterator我们以后会讲
- Object remove(int index); //将指定的下标位置的元素删除,并将删除的元素返回
- boolean remove(Object o); //将指定的对象从List中删除,如果找到了这个对象并删除成功,返回true
- Object set(int index, Object element); //将指定的下标位置的元素替换成指定的对象
很容易发现,大多是特有方法都是以重载形式加入的。而且,这些特有方法都跟下标有关联(毕竟只有List有下标)。
我们写一个程序来使用一下这几个方法(并不是全都用上)。使用的实现类是ArrayList类。
- import java.util.*;
- public class ListDemo {
- public static void main(String[] args) {
- List l = new ArrayList();
-
- l.add("element1");
- l.add(2);
- l.add("element3");
-
- //在下标为2的位置插入一个元素
- l.add(2, "插入的元素 ");
-
- //通过get()方法获取下标位置为3的元素
- System.out.println("下标为3的元素是"+l.get(3));
-
- //通过indexOf()方法获取对象"1"所在的下标位置
- System.out.println("元素 '1' 所在的下标位置是"+l.indexOf("element1"));
-
- //删除下标为0的元素
- l.remove(0);
-
- Iterator i = l.iterator();
- System.out.println("\n现在这个集合中的元素有:");
- while(i.hasNext()){
- System.out.println(i.next());
- }
- }
- }
复制代码 结果:

在这个程序当中,我对List的特有方法进行了一个示范。通过这个程序,我们可以发现有下标为我们提供的便利:方便更改元素。
这个接口我就不多赘述了。List接口有三个常见的实现类,下一节中我们会讲解这些实现类的区别。
本章小结
- List中的元素是有序的,每一个元素都有自己的下标
- List中的特有方法都是相关于下标的
10.5 List的实现类特点
10.5.1 List实现类概述
List是一个接口,真正开发的时候肯定不能直接使用这个接口。真正实用的是实现这个接口的类,也就是实现类。关于实现类,我不会再把每一个实现类的特有方法举出来了,毕竟这些方法可以通过API查询。我们重点讲的是实现类的特点。
List接口共有下列三种常用实现类:
- ArrayList类
- LinkedList类
- Vector类
10.5.2 三个实现类存储数据的特点
ArrayList、LinkedList、Vector类对于存储数据的方式是不同的。
ArrayList存储对象的方式是通过像数组一样的下标,对每一个对象进行排列。Vector类也是这样。两者的区别在于,ArrayList是线程异步的,Vector是线程同步的。我们还没有学到多线程,线程同步机制自然也没有学过。简单来说:在多线程环境中,ArrayList不安全,但是性能更高;Vector安全,但是效率低。如果程序是单线程(也就是我们现在的程序),就应当使用ArrayList。如果是多线程,应当使用Vector。
LinkedList比较特别。LinkedList中的元素并不是通过像数组一样的形式排列的。LinkedList中对象的存储是通过链表的形式存储的。通俗的说,每一个相邻的元素之间都是互相连接的,就像是使用链子把相邻的元素扣起来。

那么使用数组形式和链表形式有什好/坏处呢?
10.5.3 数组和链表的性能分析
集合类是容器。容器无非就是增删改查,再抽象一点就是“查询”和“编辑”。数组和链表在查询性能与编辑性能方面都各有优势。
关于查询:数组形式更加优越。数组是有下标的,下标使元素的排列更加清晰。如果需要查询一个元素在哪里,直接访问这个元素对应的下标即可。链表没有下标,只有元素与元素之间的连接。如果需要查询一个元素,需要挨个问每一个元素,“你和XXX元素连接了吗?” 可见,在查询方面,数组形式是效率更高的。
关于编辑:如果我在一个数组中插入一个元素,后面的元素需要顺延,后面的元素的下标也需要跟着顺延。如果我在数组中删除一个元素,被删除的元素后面的所有元素的角标都需要-1。可见,在数组中对元素的插入/删除都需要耗费相当大的资源。可是在链表当中,如果一个元素被插入,和这个元素相邻的元素只需要将他的“链子”和新插入的元素连接即可。不相邻的元素不受影响。删除一个元素同理,被删除的元素相邻的元素只需要互相连接即可。总结一下:链表形式在编辑方面更加有效率。

查询方面数组优越;编辑方面链表优越。可见不同的数据储存形式都是有利有弊的。
话句话说:
- 如果对集合的查询操作较多,使用ArrayList或Vector
- 如果对集合的更改操作较多,使用LinkedList
本章小结
- ArrayList、LinkedList、Vector是List的常用实现类
- ArrayList和Vector使用数组形式存储对象
- LinkedList使用链表形式存储对象
- 查询方面,数组形式更加优越;编辑方面,链表形式更加优越
10.6 ListIterator接口
10.6.1 Iterator接口中的问题
如果我们想要在迭代一个集合的元素时使用集合的方法对集合进行编辑,很容易发生ConcurrentModificationException(并发修改异常)。我们写一个程序验证一下:
- import java.util.ArrayList;
- import java.util.Iterator;
- public class ListIteratorDemo {
- public static void main(String[] args) {
- ArrayList al = new ArrayList();
- al.add("1");
- al.add("2");
- al.add("3");
- Iterator i = al.iterator();
- while (i.hasNext()) {
- al.remove(i.next()); // 在迭代当中,使用集合的remove()方法删除迭代中的下一个元素
- }
- }
- }
复制代码 结果:

在这个 程序当中,我在对集合的迭代中对元素进行了删除的操作。这种操作十分危险:迭代器获取到元素之后进行迭代,而同时,你却又对元素进行了编辑。这样的操作是有安全隐患的,因此,这个程序会抛出并发修改异常。
大家想一下:如果迭代器当中也拥有可以对元素进行操作的方法,那么就只有一个操作者了,就不会发生这个异常。
很遗憾的是,Iterator接口中没有对元素进行增删改查的方法。因此,Java为我们为我们提供了Iterator接口的子接口,ListIterator。
10.6.2 ListIterator接口
ListIterator接口只能被List以及其实现类拥有。这是因为List拥有下标,这使得ListIterator的方法可以有效。
List接口中获取ListIterator接口的方法如下:
- ListIterator listIterator(); //获取该List对应的ListIterator
- ListIterator listIterator(int index); //获取该List对应的ListIterator,并指定ListIterator将会迭代的第一个元素的下标
ListIterator接口提供丰富的方法,这里列举出其的特有方法:
- boolean add(Object obj); //将指定的对象加入其对应的List
- boolean hasPrevious(); //如果ListIterator的指针前面还有元素,返回true
- int nextIndex(); //返回下一个元素的下标
- Object previous(); //返回指针的上一个元素,如果没有,抛出NoSuchElementException
- int previousIndex(); //返回指针的上一个元素的下标
- void remove(); //删除上一个被next()或previous()获取到的元素
- void set(Object obj); //将上一个被next()或previous()获取到的元素更改成指定的对象
可以发现,由于下标的存在带来的便利,ListIterator接口拥有很多的方便的方法。我们写一个程序测试一下。
- import java.util.ArrayList;
- import java.util.ListIterator;
- public class ListIteratorDemo {
- public static void main(String[] args) {
- ArrayList al = new ArrayList();
- al.add("元素1");
- al.add("元素2");
- al.add("元素3");
- ListIterator li = al.listIterator();
-
- System.out.println(li.next());
- System.out.println(li.next());
- System.out.println(li.previous());
- System.out.println(li.nextIndex());
- }
- }
复制代码 结果:

可以发现,在迭代过程中对集合进行编辑没有发生ConcurrentModificationException,所以说ListIterator更加优越。
本章小结
- 如果在迭代的过程中使用集合的方法对集合进行编辑,会发生并发修改异常
- 如果不使用集合的方法,使用迭代器的方法编辑,就不会发生这个异常,但是Iterator没有提供这些方法
- ListIterator是Iterator的子接口,其中的特有方法可以在迭代过程中对集合进行编辑,而且不发生异常
10.7 Set接口与HashSet类
10.7.1 Set接口概述
Set接口是集合类的重要组成部分。这个接口与List接口完全相对。List接口元素有序,可重复;Set接口元素无需,不可重复。这里指的“无序”的意思是取出来的顺序与存进去的顺序不一定相同。然而,这个接口作为Collection接口的子接口,没有任何特有方法。所以说Set接口的方法这里就不说了,直接开始讲实现类。
10.7.2 HashSet类
HashSet是Set接口的经典实现。同样,这个实现类没有任何特有方法,之间使用我们之前学过的Collection接口的方法使用即可。
- import java.util.HashSet;
- import java.util.Iterator;
- public class HashSetDemo {
- public static void main(String[] args) {
- HashSet hs = new HashSet();
-
- hs.add("abc");
- hs.add(4);
- hs.add("233");
- hs.add(new Object());
-
- Iterator i = hs.iterator();
-
- while(i.hasNext()){
- System.out.println(i.next());
- }
- }
- }
复制代码 结果:

可以发现,在这个程序中,我们存入和顺序和取出的顺序是有出入的。
同时,Set接口不允许元素的重复。如果我们存入同一个元素,存储不会成功。
- import java.util.HashSet;
- import java.util.Iterator;
- public class HashSetDemo {
- public static void main(String[] args) {
- HashSet hs = new HashSet();
-
- Object obj = new Object();
-
- hs.add(obj); //存储两次同样的对象
- System.out.println(hs.add(obj)); //由于add()方法有返回值,可以打印输出
-
- Iterator i = hs.iterator();
-
- while(i.hasNext()){
- System.out.println(i.next());
- }
- }
- }
复制代码 结果:

我们在讲add()方法的时候,曾提到过,如果元素添加成功,返回true;没有成功返回false。在这个程序当中,由于我之前已经存入了obj实例,再次存入将不会成功,因此打印add()方法的返回值将会是false。
这是因为每存入一个元素,HashSet集合会将刚刚存入的元素与其已有的元素进行比较,如果已有的元素和刚刚存入的元素相等,存储不成功。
那么,HashSet是如何判断两个对象“相等”呢?
10.7.3 哈希表算法
HashSet,顾名思义,是通过Hash哈希表算法判断两个对象的关系大于还是小于的。我们将Object类的时候,曾经提过Object类有一个hashCode()方法,用于返回对象的哈希码;equals(Object obj)用于判断对象是否相等。
常规情况下,如果两个对象的hashCode()返回的值一样,他们equals()也应当返回true。HashSet就是通过这样方法来判断是否已经有这个对象。
但是问题来了:要是我的类重写了Object类中的hashCode()或equals()方法呢?
- class MyClass1 {
- public int hashCode() {
- return 1; // MyClass1的hashCode()总是返回1
- }
- }
- class MyClass2 {
- public boolean equals(Object obj) {
- return true; //MyClass2的equals()方法总是返回true
- }
- }
- class MyClass3{ //MyClass3的equals()和hashCode()方法的返回值都是固定的
- public int hashCode() {
- return 2;
- }
-
- public boolean equals(Object obj) {
- return true;
- }
- }
复制代码
这三个类当中:MyClass1的hashCode()方法返回值是固定的;MyClass2的equals()方法的返回值是固定的;MyClass3的两个方法的返回值都是固定的。要是把这三个类都储存进HashSet会怎么样?
- import java.util.HashSet;
- import java.util.Iterator;
- public class HashSetDemo {
- public static void main(String[] args) {
- HashSet hs = new HashSet();
-
- hs.add(new MyClass1());
- hs.add(new MyClass1());
-
- hs.add(new MyClass2());
- hs.add(new MyClass2());
-
- hs.add(new MyClass3());
- hs.add(new MyClass3());
-
- Iterator i = hs.iterator();
-
- while(i.hasNext()){
- System.out.println(i.next());
- }
- }
- }
复制代码 结果:

首先我们都知道,直接打印一个实例,实际上是打印这个实例的toString()方法。Object类的toString()方法默认返回“类名+@+哈希值”。
我们先观察前面两个MyClass1,可以发现“@”之后都是1,但是存储照样成功;
MyClass2的equals()方法返回true,但是添加成功,哈希码不一样;
MyClass3的两个方法都复写了,添加不成功。equals()方法既返回true,hashCode()也返回固定的值。
可以发现:equals()方法为true,哈希码也一样,才会被当成“一个对象”。
那么仅复写了一个方法的类HashSet是如何处理的呢?HashSet会尽量将这两个元素放置在不同的位置。这样做虽说可以,但是会降低HashSet的效率。因此一般equals()和hashCode()不能只复写一个,建议两个都复写。
本章小结
- Set接口的特点是元素不重复,无序
- HashSet是Set的实现类,通过哈希表算法判断两个对象是否相等
- HashSet没有特有方法
- 如果equals()方法返回true,hashCode()也一样,会被当做一个对象
10.8 TreeSet实现类以及Comparable接口
10.8.1 TreeSet类概述
TreeSet也是Set接口的最常见实现类之一。这个类和HashSet最大的区别在于排序方面:HashSet类是通过哈希表对元素进行排序的,TreeSet的排序方法是使用者自定的。今天我们重点学习如何自定义如何排序。
10.8.2 已有排序方法的类
观察下列代码:
- import java.util.Iterator;
- import java.util.TreeSet;
- public class TreeSetDemo {
- public static void main(String[] args) {
- TreeSet ts = new TreeSet();
-
- ts.add(6); //添加多个Integer类
- ts.add(2);
- ts.add(3);
- ts.add(7);
- ts.add(8);
- ts.add(1);
-
- Iterator i = ts.iterator();
-
- while(i.hasNext()){
- System.out.println(i.next());
- }
- }
- }
复制代码 结果:

大家可以发现,我在TreeSet中添加了几个没有任何顺序的int(Integer)类型,但是一将元素取出之后这些数字却被从小到大排序了。这是因为Integer类(也就是int类型的包装类)中已经定义了如何去对元素进行排序。有许多的类也定义了如何对对象进行排序。那么如果我想要自定义我的类的排序方式如何实现呢?
10.8.3 Comparable接口
java.lang.Comparable接口包含用于对对象进行排序的方法。TreeSet中存的元素的类都必须实现这个接口。只要让一个类实现这个接口,通过自己想要的方式排序对象来复写这个方法即可。这个方法是:
可以发现,这个方法的返回值是int类型。这个方法的规则是:
- 如果返回值为负数,说明this小于o
- 如果返回值为0,说明this与o相等
- 如果返回值为正数,说明this大于o
注意,如果两个对象之间“相等”,系统就会通过哈希表算法将其中一个添加进容器中。另外一个是添加失败的。
TreeSet会调用这个方法来对元素进行排序。现在我们来写一个实例具体应用一下这个接口。
假如说我有一个工人类,想要通过TreeSet对工人的工号进行排序。
我们先定义一下工人类:
- class Worker{
- private String name; //姓名
- private int age; //年龄
- private int id; //工号
-
- public String toString(){
- return name+"..."+age+"..."+id;
- }
-
- public Worker(String name, int age, int id) {
- this.name = name;
- this.age = age;
- this.id = id;
- }
- /*...省略字段的getXXX()以及setXXX()方法...*/
- }
复制代码 这个类中包含三个字段,分别是姓名、年龄、工号。这些字段已经被封装。构造方法要求三个参数,这些参数分别对应Worker类的三个字段。同时,这个类还复写了Object类的toString方法,让直接打印这个类的实例看的更加直观。
不过,由于我们这个类还没有实现Comparable接口,其还不能被排序。我们让它实现这个接口,并且复写compareTo()方法。
在这个例子中,compareTo()方法中应当如此做:
- 由于compareTo()方法接受的是Object类型,应当使用instanceof运算符判断参数的运行时类型是不是Worker,如果是,向下转换;如果不是,通过throw抛出ClassCastException(如果忘了回去看第五章)
- 向下转换之后,判断这个参数的id字段与this的id字段,如果this.id>obj.id,返回正数(表示this>obj)
- 如果this.id == obj.id,返回0(表示this==obj)
- 如果this.id<obj.id,返回负数(表示this<obj)
- class Worker implements Comparable{
- private String name; //姓名
- private int age; //年龄
- private int id; //工号
-
- public String toString(){ //为了直接打印Worker类的实例更加直观,复写Object类的toString()方法
- return name+"..."+age+"..."+id;
- }
- public Worker(String name, int age, int id){
- this.name = name;
- this.age = age;
- this.id = id;
- }
-
- public int compareTo(Object o) {
- if(!(o instanceof Worker)) //如果参数中的o不是Worker类
- throw new ClassCastException("必须是Worker类的实例才能比较");
-
- //如果能够执行到这里,说明o instanceof Worker == true
- Worker worker = (Worker)o; //向下转换
-
- if(this.id>worker.id) //如果this的id大于worker的id
- return 1; //this>worker
- else if(this.id == worker.id) //如果this的id等于worker的id
- return 0; //this==worker
- else
- return -1; //this<worker
- }
- }
复制代码
好的,现在这个compareTo()方法定义好了。现在我们在主类中添加这个类的对象测试一下:
- import java.util.Iterator;
- import java.util.TreeSet;
- public class TreeSetDemo {
- public static void main(String[] args) {
- TreeSet ts = new TreeSet();
-
- ts.add(new Worker("A",25,1)); //添加Worker类对象
- ts.add(new Worker("B",31,5));
- ts.add(new Worker("C",26,4));
- ts.add(new Worker("D",40,2));
- ts.add(new Worker("E",23,3));
- ts.add(new Worker("F",24,6));
-
- Iterator i = ts.iterator();
-
- while(i.hasNext()){
- System.out.println(i.next());
- }
- }
- }
复制代码 结果:

由于Worker类已经复写了toString()方法,我们直接打印Worker类实例可以很直观的看到当前对象的字段值。
根据结果,我们添加的Worker对象都已经按照工号排序成功。
本章小结
- TreeSet是Set接口的实现类,通过存入对象的类的compareTo()方法对对象进行排序
- 如果想要往TreeSet类添加对象,这个对象的类必须实现Comparable接口
- Comparable接口中包含一个用于比较对象的方法,也就是compareTo()
- 如果compareTo()返回正数,说明this>参数;如果是0,this==参数;如果是负数,this<参数
10.9 Map接口
10.9.1 Map接口概述
Map接口在集合类中有着十分重要的地位。同时,其的特殊之处是它没有继承Collection接口,这是因为其中的数据存储方式和List与Set有着很大的区别。
假如说我们有一个集合,这个集合是一个学生的成绩单。有“语文 80分”、“数学 91分”等。可以发现每一个科目对于其对应有一个分数。在查阅这个成绩单的时候,是按照科目去查询,然后再看与这个科目相对的分值。Map的特点就是像这样一个元素对应一个元素的。用于查找的元素叫做“键(Key)”、用于表示实际数据的元素叫做“值(Value)”。
Map接口有一个内部接口,叫做Entry,也叫映射。一个Entry包含一个键一个值,多个Entry就构成了一个Map。

10.9.2 Map接口方法
Map接口没有继承Collection,所以其中的所有方法都是特有的。
- void clear() //删除所有映射
- boolean containsKey(Object key) //如果有这样的键,返回true
- boolean containsValue(Object value) //如果有这样的值,返回true
- Set entrySet() //返回一个Set集合,这个Set中的元素是Map的所有Entry
- Object get(Object key) //返回指定的键所对应的值,如果没有找到这个键,返回null
- boolean isEmpty() //如果Map中没有任何Entry,返回true
- Set keySet() //返回一个Set集合,这个Set中的元素是Map的所有键
- Object put(Object key, Object value) //在Map中建立一个Entry,这个Entry的键和值对应的是方法的参数,如果这个Entry已存在,覆盖
- void putAll(Map m) //将指定的Map的所有Entry加入在本Map中
- Object remove(Object key) //删除这个键对应的Entry,返回这个Entry的value,如果没有找到这个键,返回null
- boolean remove(Object key, Object value) //删除包含这个键和值的Entry,如果删除成功返回true
- int size() //返回这个Map中的Entry数量
- Collection values() //返回一个Collection集合,这个集合包含Map的所有值
- import java.util.*;
- public class MapDemo {
- public static void main(String[] args) {
- Map map = new HashMap(); //由于Map是接口,不能被实例化。使用实现类HashMap实例化
-
- //put()
- map.put("语文", 86);
- map.put("数学", 76);
- map.put("英语", 92);
- map.put("物理", 88);
- map.put("化学", 76);
-
- //size()
- System.out.println("这个Map中一共有"+map.size()+"个元素");
-
- //containsKey()
- System.out.println("是否有生物科目?"+map.containsKey("生物"));
-
- System.out.println(map); //由于Map复写了Object的toString(),直接打印可以看出Map中的元素
- }
- }
复制代码 结果:

在这个程序中,我是实例化了HashMap之后通过put()方法加入了多个科目以及对应分数。之后使用size()方法返回这个Map的Entry数量。最后使用containsKey()方法判断是否有生物这个科目。
Entry用于封装一对键和值,所以说其也提供一些方法。
10.9.3 Entry接口方法
Entry接口有下列方法:
- Object getKey() //获取该Entry的键
- Object getValue() //获取该Entry的值
- Object setValue(Object value) //将Entry的值设置为指定的Object
- import java.util.*;
- public class MapDemo {
- public static void main(String[] args) {
- Map map = new HashMap();
-
- map.put("语文", 86);
- map.put("数学", 76);
- map.put("英语", 92);
- map.put("物理", 88);
- map.put("化学", 76);
-
- Set set = map.entrySet(); //通过entrySet()返回Map的所有entry
- Iterator i = set.iterator();
- Map.Entry entry; //声明Map.Entry实例,暂不实例化
-
- while(i.hasNext()){
- entry = (Map.Entry)i.next();
- System.out.println(entry.getKey());
- System.out.println(entry.getValue());
- }
- }
- }
复制代码 结果:

在这个程序当中,我首先使用了entrySet()方法返回了Map的所有Entry。在这个Set遍历的时候,将Set中的元素向下转换成Map.Entry,然后打印Entry的getValue()和getKey()方法。
本章小结
- Map接口通过若干个键与值的映射组成
- 每一个映射被封装成Map的内部接口:Entry
- Map没有继承Collection,所以其中的方法都是很特殊的
10.10 Map的实现类
10.10.1 Map实现类概述
Map作为一个接口,不能被直接实例化。Java为我们提供三个常用Map实现类,这三个类是:
值得一提的是,Hashtable其实是两个词(Hash和Table),只不过这个类太古老,所以没有按照Java的类命名规范。之后没有改过来的原因是为了保证新版本兼容旧版本。如果现在的JDK都用Hashtable,新版本用HashTable,你的JDK以更新程序就会报错。
10.10.2 实现类的区别
其实,这三个类的关系和之前List、Map的实现类关系差不多。
HashMap和Hashtable使用的都是哈希表算法来保证键的唯一性。当新添加的键和已有的键相等(算法见Set实现类),那就添加不成功。两者的区别在于HashMap是线程不同步的;Hashtable是同步的。换句话说,在多线程程序中,HashMap性能更高当时不安全;Hashtable性能低但是安全。
此外,HashMap和Hashtable还有一个区别:HashMap的键或值可以存储null;Hashtable不可以。
TreeMap和TreeSet是基本一样的。TreeMap会自动调用键的类的compareTo()方法(因此这个类必须实现Comparable接口)来对键进行排序。
- public class TreeMapDemo {
- public static void main(String[] args) {
- TreeMap map = new TreeMap();
- map.put("B", 2);
- map.put("C", 3);
- map.put("D", 4);
- map.put("A", 1);
- map.put("E", 5);
- System.out.println(map);
- }
- }
复制代码 结果:

由于String类实现了Comparable接口,其可以被排序。String类是按照字典的顺序排序的。
可以发现,其实这三种实现类的区别和之前学习过的实现类的区别是差不多的。本人也就不再赘述。
本章小结
- Java提供了Map接口的三个实现类:HashMap、Hashtable、TreeMap
- HashMap和Hashtable通过哈希表算法保证键的唯一性。HashMap效率高、不安全;Hashtable效率低、安全
- TreeMap按照键的类的compareTo()方法对键进行排序
[groupid=546]Command Block Logic[/groupid]
作者: Jim_game 时间: 2015-11-15 21:25
审核完毕撒花!等了好久总算可以看了
作者: SYS_TEM 时间: 2015-11-16 21:18
审核完毕撒花
我看到题目突然想到了 集合 (数学的的那个)
子集母鸡什么的
还有一个就是矩阵(不知道什么联系)
作者: Jim_game 时间: 2015-11-17 23:43
本帖最后由 Jim_game 于 2015-11-17 23:44 编辑
话说LZ可以试试在二楼发内容。。。不知是否能跳过审核
作者: ufof 时间: 2015-11-18 17:46
在以后的教程中会这样考虑,的确审核太坑爹了。。
作者: 947132885 时间: 2016-1-25 13:00
恩,发现了个问题,为什么两个实例hashCode()返回值一样(重写了hashCode方法,把返回值改为1),但是equals()返回的是false?
作者: ufof 时间: 2016-1-25 14:05
那是因为equals()方法没有被重写。equals()方法内部的算法肯定不是简单的把hashCode()的返回值比较一遍。
作者: 947132885 时间: 2016-1-25 14:33
恩,我看了看object的源码,发现了equals()的算法比我想的更简单。public boolean equals(Object obj) {
return (this == obj);
}
作者: 947132885 时间: 2016-1-25 14:34
所以说他到底是怎么比的啊?
作者: 947132885 时间: 2016-1-25 14:36
恩,我发现TreeSet中,compareTo的值如果返回0的话,那么add方法就加不进去数据,这个应该要写进教程
作者: ufof 时间: 2016-1-25 15:15
直接用比较运算符==比较实例是比较内存地址值。如果地址一样就是true。
作者: 947132885 时间: 2016-1-26 00:10
恩,可能是直接比的一个存放地址的变量吧,不管了,反正现在就一新手,这些方法现在能用就行,等以后学到深处再去思考到底是怎么写的。
作者: 947132885 时间: 2016-1-26 00:19
虽然是个小错误,但是还是说下,新添的教程中“注意,如果两个对象之间“相等”,系统就会通过哈希表算法将其中一个添加进容器中。另外一个是添加失败的。”这里面,应该是通过自定义的排序方法添加进容器中的,顺便,添加进去的那个应该是代码中先写的那个,后写的那个加不进去。恩,别嫌我烦啊,恩,嫌我烦的话,就无视这段话。恩,有错的话指出,不要骂我......恩,好了就是这样....
作者: ufof 时间: 2016-1-26 08:46
TreeSet如果比较相同的话,的确是再通过HashCode比较的,而并不是添加顺序。
作者: 947132885 时间: 2016-1-26 12:07
哦,看来是我理解错了。
作者: 猿汐 时间: 2019-3-8 17:18
提示: 作者被禁止或删除 内容自动屏蔽
作者: 是夜初哇丶 时间: 2019-7-20 03:09
巨无奈,总是看不到图,能不能不用bbs传图了qaq