Java学习总结——Java 类集框架

廿——Java 类集框架

Java 的类集框架可以使程序处理对象的方法标准化,类集接口是构造类集框架的基础,使用迭代方法访问类集可以使对类集的操作更高效。

一、认识类集框架

在基础应用中,通常我们可以通过数组来保存一组具有相同属性的对象或者基本类型的数据,但是用数组的弊端在于其大小是不可更改的,因此出于灵活性的考虑,可以使用链表来实现动态的数组。任何事情都有两面性,灵活性的代价就是操作上的繁琐。在计算机世界里,处理繁琐问题的常用方法就是将其封装,只向外提供可调用的方法视图。Java 类集框架就是对这一方法的一种官方实现——套动态对象数组的操作类。本质上,Java 类集框架就是 Java 对数据结构的一个大体上的封装。

类集是在 JDK 1.2 之后正式提出的概念。从类集开始用户就没有必要再像之前自己来编写链表了。但是类集的内部实现原理依然和之前一样,就是一个动态的对象数组,所不同的是,这个动态数组处理的细节,已被包装屏蔽起来了。这个理念和 C++ 中 STL( Standard Template Library,标准模板库 )是一脉相承的。

在 java.util 包之中定义了所有与类集有关的操作接口:Collection、List、Set、Map、Iterator、ListIterator 及 Enumeration,对于所有给出的接口,我们要将其主要的操作方法记下。

在 JDK 1.5 之后,这些接口都增加了泛型的定义,最早的时候这些接口中的内容都使用 Object( 对象 )进行操作。出于安全性的考虑,以及避免转型之间的繁琐,JDK 1.5 以后将整个类集框架都升级为泛型( Generic programming ),极大方便了用户。

在 Java 中,每个变量都有其所属的数据类型,要么是基本的数据类型( int、float、char 等 ),要么是自定义的数据类型——即类,而泛型的本质就是将变量的 “ 类型 ” 参数化,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别被称为泛型类、泛型接口、泛型方法。

二、类集接口

类集框架定义了几个接口。类集接口决定了 Collection 类的基本特性。具体类仅仅是提供了标准接口的不同实现。支持类集的接口总结如下:

接口描述
Collection能操作一组对象,它位于类集层次结构的顶层
List扩展 Collection 支持处理序列( 对象的列表 )
Set扩展 Collection 支持处理集合,集合元素必须唯一
SortedSet扩展 Set 支持处理排序集合

除了类集接口之外,类集也需要使用 Comparator、Iterator 和 ListIterator 等接口。

在这些接口中定义了操作该类集的一些方法。支持这些方法的类集被称为可修改的( modifiable )。不允许修改其内容的类集被称为不可修改的( unmodifiable )而所有内置的类集都是可修改的。如果对一个不可修改的类集使用这些方法,将引发一个 UnsupportedOperationException 异常。

三、单值保存的最大父接口——Collection

Collection 接口是构造类集框架的基础,是单值数据操作的最大父接口,它声明了所有类集都将拥有的核心方法,这些方法的简明含义如下表所示。由于所有类集均实现了 Collection,所以熟悉它的方法对于清楚地理解框架是必要的。其中有几种方法可能会引发一个 UnsupportedOperationException 异常。这些异常将发生在修改不能被修改的类集的时候。当一个对象与另一个对象不兼容,例如企图增加一个不兼容的对象到一个类集中时,将产生一个 ClassCastException 异常。

方法描述
boolean add(Object obj)将 obj 加入到调用类集中。如果 obj 被加入到类集中了,则返回 true;如果此 collection 不允许有重复元素,并且已经包含了 obj,则返回 false
boolean addAll(Collection c)将 c 中的所有元素都加入到调用类集中,如果操作成功( 元素被加入了 ),则返回 true,否则返回 false
void clear()从调用类集中删除所有元素
boolean contains(Object obj)如果 obj 是调用类集的一个元素,则返回 true,否则返回 false
boolean containsAll(Collection c)如果调用类集包含了 c 中的所有元素,则返回 true,否则返回 false
boolean equals(Object obj)如果调用类集与 obj 相等,则返回 true,否则返回 false
int hashCode()返回调用类集的 hash( 哈希 )值
boolean isEmpty()如果调用类集是空的,则返回 true,否则返回 false
Iterator iterator()返回调用类集的迭代器
boolean remove(Object obj)从调用类集中删除 obj 的一个实例。如果这个元素被删除了,则返回 true,否则返回 false
boolean removeAll(Collection c)从调用类集中删除 c 的所有元素。如果类集被改变了( 也就是说元素被删除了 ),则返回 true,否则返回 false
boolean retainAll(Collection c)删除调用类集中除了包含在 c 中的元素之外的全部元素。如果类集被改变了( 也就是说元素被删除了 ),则返回 true,否则返回 false
int size()返回调用类集中元素的个数
Object[ ]  toArray()返回一个数组,该数组包含了所有存储在调用类集中的元素。数组元素是类集元素的备份
Object[ ]  toArray(Object array[ ])返回一个数组,该数组仅仅包含了那些类型与数组元素类型匹配的类集元素。数组元素是类集元素的备份。如果 array 的大小与匹配元素的个数相等,它们被返回 array。如果 array 的大小比匹配与元素的个数小,将分配并返回一个所需大小的新数组。如果 array 的大小比匹配元素的个数大,在数组中,在类集元素之后的单元被置为 null。如果任一类集元素的类型都不是 array 的子类型,则引发一个 ArrayStoreException 异常

在 Collection 接口之中所提供的方法一定是日后进行程序开发中使用最多的方法,但是 Collection 接口很少在开发之中直接去使用,往往都会使用它的子接口:List( 允许有重复元素 )、Set( 不允许有重复元素 )。

四、Collection 接口的具体实现类

一些类提供了完整的可以被使用的工具。另一些类是抽象的,提供主框架工具,作为创建具体类集的起始点。没有一个 Collection 接口是同步的,但是可以通过一些方法获得同步的版本。标准的 Collection 实现类总结如下表所示:

描述
AbstractCollection实现大多数 Collection 接口
AbstractList扩展 AbstractCollection 并实现大多数 List 接口
AbstractSequentialList为了被类集使用而扩展 AbstractList,是连续而不是用随机方式访问其元素
LinkedList通过扩展 AbstractSequentialList 来实现链接表
ArrayList通过扩展 AbstractList 来实现动态数组
AbstractSet扩展 AbstractCollection 并实现大多数 Set 接口
HashSet为了使用散列表而扩展 AbstractSet
TreeSet实现存储在书中的一个集合,扩展 AbstractSet

除了 Collection 接口之外,还有几个从以前版本遗留下来的类,如 Vector、Stack 和 Hashtable 等均被重新设计成支持类集的形式。

五、允许重复的子接口——List

List( 列表 )是 Collection 接口之中最为常用的一个子接口,List 子接口的定义如下:

        public interface List<E> extends Collection<E>

List 子接口对 Collection 接口进行了大量的扩充。List 接口扩展了 Collection 并声明存储一系列元素的类集的特性。使用一个基于零的下标,元素可以通过它们在列表中的位置被插入和访问。

一个列表可以包含重复的元素,即可以存在完全相同的两个元素。除了由 Collection 定义的方法之外,List 还定义了一些它自己的方法,这些方法总结在下表中。需要注意的是,当类集不能被修改时,其中的几种方法将引发 UnsupportedOperationException 异常。当一个对象与另一个不兼容时,例如企图将一个不兼容的对象加入一个类集中,将产生 CLassCastException 异常。

方法描述
void add(int index,Object obj)将 obj 插入调用列表,插入位置的下标由 index 传递。任何已存在的,在插入点以及插入点之后的元素将后移。因此,没有元素被覆写
boolean addAll(int index,Collection c)将 c 中的所有元素插入到调用列表中,插入点的下标由 index 传递。在插入点以及插入点之后的元素将后移。因此,没有元素被覆写。如果调用列表改变了,则返回 true,否则返回 false
Object get(int index)返回存储在调用类集内指定下标处的对象
int indexOf(Object obj)返回调用列表中 obj 第一次出现的下标。如果 obj 不是列表中的元素,则返回 -1
int lastIndexOf(Object obj)返回调用列表中 obj 最后一次出现的下标。如果 obj 不是列表中的元素,则返回 -1
ListIterator listIterator()返回调用列表开始的迭代器
ListIterator listIterator(int index)返回调用列表在指定下标处开始的迭代器
Object remove(int index)删除调用列表中 index 位置的元素并返回删除的元素。删除后,列表被压缩。也就是说,被删除元素后面的元素的下标减1
Object set(int index,Object obj)用 obj 对调用列表内由 index 指定的位置进行赋值
List subList(int start,int end)返回一个列表,该列表包括了调用列表中从 start 到 end -1 的元素。返回列表中的元素也被调用对象引用

对于由 Collection 定义的 add() 和 addAll() 方法,List 增加了方法 add(int,Object) 和 addAll(int,Collection),这些方法在指定的下标处插入元素。由 Collection 定义的 add(Object) 和 addAll(Collection) 的语义也被 List 改变了,以便它们在列表的尾部增加元素。

为了获得在指定位置存储的对象,可以用对象的下标调用 get() 方法。为了给类集中的一个元素赋值,可以调用 set() 方法,指定被改变的对象的下标。调用 indexOf() 或 lastIndexOf() 可以得到一个对象的下标。通过调用 subList() 方法,可以获得列表的一个指定了的开始下标和结束下标的字列表。

由于 List 本身毕竟还属于接口,要想使用接口,就必须知道实现这个接口的子类,在 List 接口中有两个最为常用的子类:ArrayList、Vector。

1.ArrayList 类

ArrayList 类扩展 AbstractList 并执行 List 接口。ArrayList 支持可随需要而增长的动态数组。在 Java 中,标准类型的数组是定长的。一旦数组被创建之后,它们不能被加长或缩短,这也就意味着开发者必须实现知道数组可以容纳多少元素。

但是在一般情况下,只有在运行时才能知道需要多大的数组。为了解决这个问题,类集框架定义了 ArrayList。本质上,ArrayList 是对象引用的一个变长数组。也就是说,ArrayList 能够动态地增长或减少其大小。数组列表以一个原始大小被创建。当超过了它的大小时,类集就会自动增大。当有对象被删除,数组就可以缩小。注意:动态数组也被从以前版本遗留下来的类 Vector 所支持。

ArrayList 有如下的构造方法:

        ArrayList()
        ArrayList(Collection c)
        ArrayList(int capacity)

其中第 1 个构造方法构造一个初始容量为 10 的空列表。第 2 个构造方法建立一个数组列表,该数组列表由类 c 中的元素初始化。第 3 个构造方法建立一个数组列表,该数组有指定的初始容量( capacity ),容量用于存储元素的基本数组的大小。当元素被追加到数组列表上时,容量会自动增加。

下面的程序是一个 ArrayList 的一个简单应用。首先创建一个数组列表,接着添加 String 类型的对象,然后列表被显示出来。将其中的一些元素删除后,再一次显示列表。

import java.util.*;
public class ArrayListDemo
{
	public static void main(String[] args)
	{
		//创建一个ArrayList对象
		ArrayList<String> al = new ArrayList<String>();
		System.out.println("al中元素的个数:" + al.size());
		//向ArrayList对象中添加新内容
		al.add("C");	//在数组列表0位置添加元素C
		al.add("A");	//在数组列表1位置添加元素A
		al.add("E");	//在数组列表2位置添加元素E
		al.add("B");	//在数组列表3位置添加元素B
		al.add("D");	//在数组列表4位置添加元素D
		al.add("F");	//在数组列表5位置添加元素F
		//把A2加在ArrayList对象的第2个位置
		al.add(1,"A2");	//在1位置添加A2,列表中内容现为:C A2 A E B D F
		System.out.println("al加入元素之后的元素的个数" + al.size());
		//显示ArrayList数据
		System.out.println("a1的内容:" + al);
		//从ArrayList中移除数据
		al.remove("F");	//删除元素F,此时数组列表为:C A2 A E B D
		al.remove(2);	//删除下标为2的元素,此时数组列表为:C A2 E B D
		System.out.println("al删除元素之后的元素的个数:" + al.size());
		System.out.println("al的内容:" + al);
	}
}

当对象被存储在 ArrayList 对象中时,其容量会自动增加。然而,也可以通过调用 ensureCapacity() 方法来人工地增加 ArrayList 的容量。如果实现知道将在当前的类集中存储大量数据时,我们可以在开始时,通过一次性地增加它的容量,就能避免后面的在分配。因为在分配是很花时间的,避免不必要的处理可以提高性能。

举例:

import java.util.*;
public class ArrayListToArray
{
	public static void main(String[] args)
	{
		//创建一个ArrayList对象al
		ArrayList<Integer> al = new ArrayList<Integer>();
		//向ArrayList中加入对象
		al.add(new Integer(1));
		al.add(new Integer(2));
		al.add(new Integer(3));
		al.add(new Integer(4));
		System.out.println("ArrayList中的内容:" + al);
		
		//得到对象数组
		Object ia[] = al.toArray();
		int sum = 0;
		//计算数组内容
		for (int i = 0; i < ia.length; i++) {
			sum +=((Integer)ia[i]).intValue();	//将元素转换为Integer类型并取值
		}
		System.out.println("数组累加结果是:" + sum);
	}
}

尖括号内的类型是 Integer,一个整型对象,而不是一个基本数据类型( int 整型 )。

2.LinkedList 类

LinkedList 类扩展了 AbstractSequentialList 类并实现 List 接口。它提供了一个链接列表的数据结构。它有如下的两个构造方法:

        LinkedList()
        LinkedList(Collection c)

第 1 个构造方法建立一个空的链接列表。第 2 个构造方法建立一个链接列表,该链接列表由类 c 中的元素初始化。

除了它继承的方法之外,LinkedList 类本身还定义了一些有用的方法,这些方法主要用于操作和访问列表。使用 addFirst() 方法可以在列表头增加元素,使用 addLast() 方法可以在列表的尾部增加元素。它们的形式如下所示:

        void addFirst(Object obj)
        void addLast(Object obj)

在这里,obj 是被增加的对象。

调用 getFirst() 方法可以获得第 1 个元素,调用 getLast() 方法可以得到最后一个元素。它们的形式如下所示:

        Object getFirst();
        Object getLast();

为了删除第 1 个元素,可以使用 removeFirst() 方法。删除最后一个元素,可以调用 removeLast() 方法。它们的形式如下:

        Object removeFirst();
        Object removeLast();

下面的程序是对 LinkedList 中的这几个方法的使用演示:

//LinkedList类的使用
import java.util.*;
public class LinkedListDemo
{
	public static void main(String[] args)
	{
		//创建LinkedList对象
		LinkedList<String> LL = new LinkedList<String>();
		//加入元素到LinkedList中
		LL.add("F");
		LL.add("F");
		LL.add("D");
		LL.add("E");
		LL.add("C");
		//在链表的最后一个位置加上数据
		LL.addLast("Z");
		//在链表的第一个位置上加入数据
		LL.addFirst("A");
		//在链表第二个元素的位置上加入数据
		LL.add(1,"G");
		System.out.println("LL最初的内容:" + LL);
		//从LinkedList中移除元素
		LL.remove("F");
		System.out.println("删除元素F后的LL内容:" + LL);
		LL.remove(2);
		System.out.println("从LL中移除第2个元素后的内容之后:" + LL);
		//移除第一个和最后一个元素
		LL.removeFirst();
		LL.removeLast();
		System.out.println("LL移除第一个和最后一个元素之后的内容:" + LL);
		//取得并设置值
		Object val = LL.get(2);
		LL.set(2, (String)val + "Changed");
		System.out.println("LL被改变之后:" + LL);
	}
}

3.旧的子类——Vector

Vector 实现动态数组,这与 ArrayList 相似,但两者不同的是:Vector 是同步的,并且它包含了许多不属于类集框架的从以前版本遗留下来的方法。随着 Java 2 的公布,Vector 被重新设计来扩展 AbstractList 和实现 List 接口,因此现在它与类集是完全兼容的。

下面列出 Vector 的构造方法。

        Vector()
        Vector(int size)
        Vector(int size,int incr)
        Vector(Collection c)

第 1 种形式创建一个原始大小为 10 的默认矢量。第 2 种形式创建一个其原始容量由 size 指定的矢量。第 3 中形式创建一个其原始容量由 size 指定,并且它的增量由 incr 指定的矢量,增量指定了矢量每次允许向上改变大小的元素的个数。第 4 种形式创建一个包含类集 c 中的元素的矢量。这个构造方法是在 Java 2 中新增加的。

所有的矢量开始都有一个原始的容量。在这个原始容量达到以后,下一次再试图向矢量中存储对象时,矢量会自动为那个对象分配空间,同时为别的对象增加额外的空间。通过分配超过需要的内存,减小了矢量可能产生的分配的次数。这种次数的而减少是很重要的,因为分配内存是很花时间的。在每一次的再分配中,分配的额外空间的总数由在创建矢量时指定的增量来确定。如果没有指定增量,在每个分配周期,矢量的大小增加一倍。

Vector 定义了下面的保护数据成员。

        int capacityIncrement;
        int elementCount;
        Object elementData[];

增量值被存储在 capacityIncrement 中,矢量中的当前元素的个数被存储在 elementCount 中,保存矢量的数组被存储在 elementData 中。

除了由 List 定义的类集方法之外,Vector 还定义了几个从以前版本遗留下来的方法,这些方法列在下表中。

方法描述
final void addElement(Object element)将由 element 指定的对象加入矢量
int capacity()返回矢量的容量
Object clone()返回调用矢量的一个备份
boolean contains(Object element)如果 element 被包含在矢量中,则返回 true;如果不包含于其中,则返回 false
void copyInto(Object array[])将包含在调用矢量中的元素复制到由 array 指定的数组中
Object elementAt(int index)返回由 index 指定位置的元素
Enumeration elements()返回是两种元素的一个枚举
Object firstElement()返回矢量的第 1 个元素
int indexOf(Object element)返回 element 首次出现的位置下标。如果对象不在矢量中,则返回 -1
int indexOf(Object element,int start)返回 element 在矢量中,在 start 及其之后第 1 次出现的位置下标。如果该对象不属于矢量的这一部分,则返回 -1
void insertElementAt(Object element,int index)在矢量中,在由 index 指定的位置处加入 element
boolean isEmpty()如果矢量是空的,则返回 true;如果它包含了一个或更多个元素,则返回 false
Object lastElement()返回矢量中的最后一个元素
int lastIndexOf(Object element)返回 element 在矢量中最后一次出现的位置下标。如果对象不包含在矢量中,则返回 -1
int lastIndexOf(Object element,int start)返回 element 在矢量中,在 start 之前最后一次出现的位置下标。如果该对象不属于矢量的这一部分,则返回 -1
void removeAllElements()清空矢量,在这个方法执行以后,矢量的大小为 0
boolean removeElement(Object element)从矢量删除 element。对于制定的对象,矢量中如果有许多个实例。则其中的第 1 个实例被删除。如果成功删除,则返回 true;如果没有发现对象,则返回 false
void removeElementAt(int index)删除由 index 制定位置处的元素
void setElementAt(Object element,int index)将由 index 指定的位置分配给 element
void setSize(int size)将矢量中元素的个数设为 size。如果新的长度小于旧的长度,元素将丢失。如果新的长度大于旧的长度,则在其后增加 null 元素
int size()返回矢量中当前元素的个数
Strinng toString()返回矢量的字符串等价形式
void trimToSize()将矢量的容量设为与其当前拥有的元素的个数相等

因为 Vector 实现 List,所以可以像使用 ArrayList 的一个实例那样使用矢量。也可以使用它的从以前版本遗留下来的办法来操作它。例如,在后面实例化 Vector,可以通过调用 addElement() 方法而为其增加一个元素。调用 elementAt() 方法可以获得指定位置处的元素。

调用 firstElement() 方法可以得到矢量的第 1 个元素,调用 lastElement() 方法可以检索到矢量的最后一个元素,使用 indexOf() 和 lastIndexOf() 方法可以获得元素的下标,调用 removeElement() 或 removeElementAt() 方法可以删除元素。

下面的程序使用矢量存储不同类型的数值对象。程序说明了几种由 Vector 定义的从以前版本遗留下来的方法,同时也说明了枚举( Enumeration )接口。

//使用矢量存储不同类型的数值对象
import java.util.*;
public class VectorDemo
{
	public static void main(String[] args)
	{
		Vector<String> v = new Vector<String>();
		v.add("A");
		v.add("B");
		v.add("C");
		v.add("D");
		v.add("E");
		v.add("F");
		Enumeration<String> e = v.elements();
		while (e.hasMoreElements()) {
			System.out.println(e.nextElement() + " ");
		}
	}
}

这里用到了枚举的方法 nextElement()——用于获得枚举中的下一个元素。

在 Java 2 之后,Vector 增加了对迭代( Iterator )方法的支持。现在可以使用迭代方法来替代枚举去遍历对象。例如,下面的基于迭代方法的程序代码可以被替换到上面的程序中。

        Iterator<String> i = v.iterator();
        while (i.hasNext()) {
			System.out.println(i.next() + "\t");
		}

六、数组操作类——Arrays

在本质上,类集本身是一个对象数组。Array 类数组操作类,可用来操作数组( 如数组元素排序、搜索和填充等 )的各种方法。

Array 类的常用方法如下表所示:

序号方法名称类型功能简述
1static<T> List<T> asList(T…a)静态将多个元素变为 List 集合
2static int binarySearch(int[] a,int key)静态使用二分搜索法,查询 key 元素值是否在 a 数组中,弱不存在则返回负数,调用此方法前要求数组以升序排列。此方法可以被多次重载
3static int[] copyOf(int[] original,int newLength)静态复制指定的数组。original 表示源数组,newLength 表示需要复制的长度,默认从第 1 个元素开始赋值。此方法可以被多次重载
4static boolean equals(int[] a1,int[] a2)静态比较两个数组是否相等,若相等则返回 true,否则返回 false。此方法可以被多次重载
5static void fill(int[] a,int val)静态将指定的 int val 分配给指定 int 型数组的每个元素。此方法可以被多次重载
6static void sort(int[] a)静态对指定的数组按升序进行排序。此方法可以被多次重载
7static String toString(int[] a)静态返回指定数组内容的字符串表示形式。此方法可以被多次重载

举例:

//使用数组操作类Arrays的使用
import java.util.Arrays;
import java.util.List;
public class ArrayDemo
{
	public static void main(String[] args)
	{
		List<String> all = Arrays.asList("Hello","World","你好","世界");
		System.out.println(all);
	}
}

在 Arrays 类之中定义了许多的数组操作方法,例如:二分查找 binarySearch()、( 并行 )排序 parallelSort() / sort()、比较两个数组是否相等 equals()、填充 fill() 等。

举例:

//数组操作类Arrays排序及二分查找方法的使用
import java.util.Arrays;
import java.util.Scanner;
public class ArrayDemo
{
	public static void main(String[] args)
	{
		int arrInt[] = {11,22,33,44,55,66,77,88,99,01};
		//升序排序数组arrInt
		Arrays.sort(arrInt);
		Scanner scan = new Scanner(System.in);
		System.out.println("请输入需要查找的整数:");
		//获取输入的整数
		int search = scan.nextInt();
		//输出排序后的数组
		for (int i = 0; i < arrInt.length; i++) {
			System.out.println(arrInt[i] + " ");
		}
		System.out.println();
		//利用二分查找法查找指定的整数
		int seaInt = Arrays.binarySearch(arrInt, search);
		if (seaInt >= 0) {
			System.out.println(search + "是数组的第" + (seaInt+1) + "位元素");
		}else{
			System.out.println(search + "不是数组的元素");
		}
		scan.close();	//关闭输入流
	}
}

此代码中使用了 Scanner 类( 此类为 SDK 1.5 后增加的一个类 )定义一个读取控制台输入的对象 scan。然后 scan 对象调用下列方法( 函数 ),读取用户在命令行输入的各种数据类型:nextByte()、nextDouble()、nextFloat()、nextInt()、nextLin()、nextLong() 及 nextShot() 等。当通过 new Scanner(System.in) 创建一个 Scanner 后,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给 Scanner,作为扫描对象。

七、比较器

需要为多个对象排序时必须设置排序规则,而排序规则就可以通过比较器进行设置,而在 Java 之中比较器提供了两种:Comparable 和 Comparator。

1.Comparable 接口

Comparable 是一个要进行多个对象比较的类需要默认实现的一个接口。这个接口的定义如下:

        public interface Comparable<T>{
            public int compareTo(T o);//这里的T为类型,而o是对类型T所定义的对象
        }

从接口的定义格式上来看,可以发现如果想实现对象属性的排序功能,要实现 Comparable 接口,并覆写 compareTo(T o) 方法。此方法返回的是一个 int 类型的数据,该返回值只能是以下三种情况之一。

(1)相等 :0;

(2)大于 :1;

(3)小于 :-1;

举例:

//使用Comparable接口解决问题
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Book implements Comparable<Book>{
	private String title;
	private double price;
	public Book(String title,double price) {
		// TODO Auto-generated constructor stub
		this.title = title;
		this.price = price;
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "书名:" + this.title + ",价格:" + this.price + "\n";
	}
	public int compareTo(Book b) {
		// TODO Auto-generated method stub
		if (this.price > b.price) {
			return 1;
		}else if (this.price < b.price) {
			return -1;
		}else {
			return 0;
		}
	}
}

public class comparableDemo{
	public static void main(String[] args)
	{
		List<Book> bookList = new ArrayList<Book>();
		bookList.add(new Book("重构1",40));
		bookList.add(new Book("重构2",50));
		bookList.add(new Book("重构3",60));
		bookList.add(new Book("重构4",70));
		Object obj[] = bookList.toArray();
		Arrays.sort(obj);	//为对象数组排序
		System.out.println(Arrays.toString(obj));
	}
}

@Override 是 Java 的注解,标识下面的方法为覆写。

2.挽救的比较器接口——Comparator

Comparable 接口是在一个类定义的时候就已经默认实现好的功能了。但是,现在假设有一个类已经开发完,在此类是用了很久之后,忽然有一天需要实现对这个类对象数组的排序,但是由于此类并没有实现 Comparable 接口,所以现在一定无法利用 Arrays 类的 sort() 方法完成排序,并且这个类由于某些原因也无法再进行修改了。在这种情况下如果还想完成排序的功能,就必须要进行另外一种比较规则的设置,即:挽救比较规则,利用 java.util.Comparator 接口实现,Comparator 接口定义如下:

        public interface Comparator<T>
        {
            public int compare(T o1,T o2);
            public boolean equals(Object obj);
        }

Comparator 接口定义了两个方法。compare() 和 equals()。这里的 compare() 方法按顺序比较了两个元素。如下:

        int compare(Object obj1,Object obj2)

obj1 和 obj2 是被比较的两个对象。当两个对象相等时,该方法返回 0;当 obj1 大于 obj2 时,返回一个正值;否则,返回一个负值。如果用于比较的对象的类型不兼容的话,该方法会引发一个 ClassCastException 异常。通过改写 compare(),可以改变对象排序的方式。例如,通过创建一个颠倒比较输出的比较方法,可以实现按逆向排序。

这里的 equals() 方法,用于测试一个对象是否与调用比较方法相等。

        boolean equals(Object obj)

obj 是被用来进行相等测试的对象。如果 obj 和调用对象都是 Comparator 的对象,并且使用相同的排序,该方法则返回 true,否则返回 false。

假设,一个定义完整但没有实现的 Comparable 接口的 Book 类如下所示:

class Book{
	private String title;
	private double price;
	public Book(String title,double price) {
		// TODO Auto-generated constructor stub
		this.title = title;
		this.price = price;
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "书名:" + this.title + ",价格:" + this.price + "\n";
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
}

现在需要单独定义一个类,来实现 Comparator 接口,专门为 Book 类对象数组提供比较规则。这个额外提供的类 BookComparator 定义如下所示:

class BookComparator implements Comparator<Book>{
	@Override
	public int compare(Book o1,Book o2) {
		// TODO Auto-generated method stub
		if (o1.getPrice() > o2.getPrice()) {
			return 1;
		}else if (o1.getPrice() < p2.getPrice()) {
			return -1;
		}
		return 0;
	}
}

在 BookComparator 类中,覆写了 compare(),在这个方法里,有两个 Book 对象,用于读取这两个对象的价格,然后进行比较,该方法的返回值依然限制为 1、-1 和 0。

如果现在要使用以上的规则,则必须使用 Arrays 类的另外一个对象数组排序方法。

        public static <T> void sort(T[] a,Comparator<? super T> c);

结合以上的铺垫,我们就可以完成 Book 对象数组的排序了。

//Comparator实现对象数组排序
import java.util.Arrays;
import java.util.Comparator;
//实际运行需要添加之前的类Book和BookComparator
public class comparatorDemo{
	public static void main(String[] args){
		Book book[] = new Book[]{
				new Book("重构1",40);
				new Book("重构2",50);
				new Book("重构3",60);
				new Book("重构4",70);
		}
		Arrays.sort(book,new BookComparator());	//为对象数组排序
		System.out.println(Arrays.toString(book));
	}
}

八、不允许重复的子接口——Set

Set 也是一个非常重要的接口,但是 Set 接口并不像 List 接口那样,对 Collection 接口进行了大量的扩充,而是完整地继承下了 Collection 接口。

集合接口定义了一个集合,并添加了类集中元素不允许重复的特性,即在 set 中不能出现完全相同的两个元素。因此,如果试图将复制元素加到集合中时,add() 方法将返回 false。它本身并没有定义任何附加的方法。在 Set 接口中也有两个常用的子类:HashSet、TreeSet。

1.HashSet 类

HashSet 扩展自 AbstraSet 并且实现了 Set 接口。它创建一个类集,该类集使用散列表进行存储,而散列表则通过使用称之为散列法的机制来存储信息。HashSet 里面所保存的数据是不能够有重复的,并且没有顺序。

在散列( hashing )中,一个关键字的信息内容被用来确定唯一的一个值,称为散列码( hashcode ),而散列码则被用来当做与关键字相连的数据的存储下标。关键字到其散列码的转换是自动执行的——看不到散列码本身。程序代码也不能直接所以散列表。散列法的优点在于即使对大的集合,它也允许一些基本操作,如 add()、contains()、remove() 和 size() 等方法的运行时间保持不变。HashSet 没有定义任何超类和接口之外的其他方法。

下面的构造方法定义为:

        HashSet()
        HashSet(Collection c)
        HashSet(int capacity)
        HashSet(int capacity,float fillRatio)

第 1 种形式构造一个默认的散列集合,第 2 种形式用 c 中的元素初始化散列集合,第 3 种形式用 capacity 初始化散列集合的容量,第 4 种形式用它的参数初始化散列集合的容量和填充比( 也称为加载容量 ),填充比必须介于 0.0 与 1.0 之间,它决定在散列集合向上调整大小之前,有多少空间被充满。具体来说,就是当元素的个数大于散列集合容量乘以它的填充比时,散列集合将被扩大。对于没有或的填充比的构造方法,默认为 0.75。

重要的是,注意散列集合并不能确定其元素的排列顺序,因为散列法的处理通常不让自己参与创建排序集合。如果需要排序存储,另一种类集——TreeSet 将是一个更好的选择。

举例:

//HashSet类的使用
import java.util.*;
public class HashSetDemo{
	public static void main(String[] args)
	{
		//创建HashSet对象
		HashSet<String> hs = new HashSet<String>();
		//加入元素到HashSet中
		hs.add("C");
		hs.add("B");
		hs.add("A");
		hs.add("D");
		hs.add("E");
		hs.add("F");
		hs.add("G");
		System.out.println(hs);
	}
}

其中添加的元素不会按顺序进行存储。

2.TreeSet 类

如果现在需要为保存的数据进行排序,那么就使用 TreeSet 子类完成。TreeSet 为使用树来进行存储的 Set 接口提供了一个工具,对象按升序存储。访问和检索是很快的。在存储了大量的需要进行快速检索的排序信息的情况下,TreeSet 是一个很好的选择。

下面的构造方法定义如下所示:

        TreeSet()
        TreeSet(Collection c)
        TreeSet(Comparator comp)
        TreeSet(SortedSet ss)

第 1 种形式构造一个空的树集合,该树集合将根据其元素的自然顺序按升序排列。第 2 种形式构造一个包含了 c 的元素的树集合。第 3 种形式构造一个空的树集合,它按照由 comp 指定的比较方法进行排序。第 4 种形式构造一个包含 ss 的元素的树集合。

举例:

//TreeSet的使用
import java.util.*;
public class TreeSetDemo{
	public static void main(String[] args)
	{
		//创建TreeSet对象
		TreeSet<String> ts = new TreeSet<String>();
		//加入元素到TreeSet中
		ts.add("C");
		ts.add("B");
		ts.add("A");
		ts.add("D");
		ts.add("E");
		ts.add("F");
		ts.add("G");
		System.out.println(ts);
	}
}

因为 TreeSet 按树存储其元素,因此它们被按照排序次序自动排序。

3.SortedSet 接口

SortedSet 接口扩展了 Set 并说明了按升序排列的集合的特性。除了那些由 Set 定义的方法之外,由 SortedSet 接口说明的方法列在下表中。当没有项包含在调用集合中时,其中的几种方法会引发 NoSuchElementException 异常。当对象与调用集合中的元素不兼容时,将引发 ClassCastException 异常。如果试图使用 null 对象,而集合不允许 null 时,会引发 NullPointerException 异常。

方法描述
Comparator comparator()返回调用被排序集合的比较方法,如果对该集合使用自然顺序,则返回 null
Object first()返回调用被排序集合的第 1 个元素
SortedSet headSet(Object end)返回一个包含那些小于 end 的元素的 SortedSet,那些元素包含在调用被排序集合中。返回被排序集合中的元素也被调用被排序集合所引用
Object last()返回调用被排序集合的最后一个元素
SOrtedSet subSet(Object start,Object end)返回一个 SortedSet,它包括了从 start 到 end-1 的元素。返回类集中的元素也被调用对象所引用
SortedSet tailSet(Object start)返回一个 SortedSet,它包含了那些包含在分类集合中的大于等于 start 的元素。返回集合中的元素也被调用对象所引用

SortedSet 定义了几种方法,使得对集合的处理更加方便。调用 first() 方法,可以获得集合中的第一个对象。调用 last() 方法,可以获得集合中的最后一个元素。调用 subSet() 方法,可以获得排序集合的一个指定了第一个和最后一个对象的子集合。如果需要得到从集合的第 1 个元素开始的一个子集合,可以使用 headSet() 方法。如果需要获得集合尾部的一个子集合,可使用 tailSet() 方法。

九、类集的输出

通常开发者希望通过循环输出类集中的元素。例如,可能会希望显示每一个元素。处理这个问题最简单的方法是使用 iterator(),该方法返回一个对象,或用于实现一个单向迭代输出的 Iterator,或用于实现双向迭代输出的 ListIterator 接口。

1.迭代器

泛型编程( Generic Programming )倡导用通用的方式进行编程。Java 通过泛型机制实现了算法与数据类型的无关性以及容器( 数据结构 )与数据类型的无关性,但是泛型机制无法解决算法与容器的分离问题。为此,Java 中引入了迭代器技术。迭代器( Iterator )是一种抽象的设计概念,它提供了一种允许依序访问某个容器所含的各个元素的方法,而无需暴露该容器的内部结构。迭代器又称迭代子,提供了对一个容器内对象的访问方法,并且定义了容器中对象的范围。

迭代器( Iterator )是一种设计模式,在 Java 中,它是一个对象,它的任务就是遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。只要拿到这个对象,使用迭代器就可以遍历这个对象的内部。迭代器通常被称为 “ 轻量级 ” 对象,因为创建它的代价很小。

2.单向迭代输出——Iterator

Iterator 可以完成通过循环输出类集内容,容而获得或删除元素。Iterator 是在进行集合输出的过程之中最为常见的一种输出接口,这个接口的定义如下:

        public interface Iterator<E>{
            public boolean hasNext();
            public E next();
            public void remove();
        }
方法描述
boolean hasNext()判断是否有下一个元素,如果存在更多的元素,则返回 true,否则返回 false
Object next()取得下一个元素。如果没有下一个元素,则引发 NoSuchElementException 异常
void remove()删除当前元素,如果试图在调用 next() 方法之前你调用 remove() 方法,则引发 IllegalStateException 异常

Iterator 本身属于一个接口,如果想要取得这个接口的实例化对象,则必须依靠 Collection 接口中定义的一个方法。

        public Iterator<E> iterator()

在通过迭代方法访问类集之前,必须得到一个迭代方法。每一个 Collection 类都提供一个 iterator() 方法,该方法返回一个对类集的迭代方法。通过使用这个迭代方法对象,可以一次一个地访问类集中的每一个元素。通常,使用迭代方法通过循环输出类集的内容,具体的操作步骤如下。

(1)通过调用类集的 iterator() 方法获得对类集的迭代方法。

(2)建立一个调用 hasNext() 方法的循环,只要 hasNext() 返回 true,就进行循环迭代。

(3)在循环内部,通过调用 next() 方法来得到每一个元素。

//通过迭代方法访问类集
import java.util.*;
public class IteratorDemo{
	public static void main(String[] args)
	{
		//创建一个ArrayList数组
		ArrayList<String> al = new ArrayList<String>();
		//加入元素到ArrayList中
		al.add("C");
		al.add("B");
		al.add("A");
		al.add("D");
		al.add("E");
		al.add("F");
		al.add("G");
		//使用iterator显示al中的内容
		System.out.println("al中的内容是:");
		Iterator<String> itr = al.iterator();
		while (itr.hasNext()) {
			Object element = itr.next();
			System.out.println(element + " ");
		}
		System.out.println();
	}
}

这里迭代器的作用类似于 C 语言中的指针,可以逐一顺序用于访问数组中的每个元素。如果存在下一个元素,hasNext() 方法会返回 true,否则返回 false。当 Iterator 到达列表末端时,hasNext() 方法会返回 false,while循环条件就会终止。

3.双向迭代——ListIterator

ListIterator 扩展了 Iterator,允许双向遍历列表,并且可以修改单元。对于执行 List 的类集,也可以通过调用 ListIterator 来获得迭代方法。列表迭代方法提供了前向或后向访问类集的能力,并且可以修改元素,否则 ListIterator 如同 Iterator 功能一样。ListIterator 接口说明的方法总结如下:

方法描述
void add(Object obj)将 obj 插入列表中,该元素在下一次调用 next() 方法时被返回
boolean hasNext()如果存在下一个元素,则返回 true,否则返回 false
boolean hasPrevious()如果存在前一个元素,则返回 true,否则返回 false
Object next()返回下一个元素,如果不存在下一个元素,则引发一个 NoSuchElementException 异常
int nextIndex()返回下一个元素的下标,如果不存在下一个元素,则返回列表的大小
Object previous()返回前一个元素,如果前一个元素不存在,则引发一个 NoSuchElementException 异常
int previousIndex()返回前一个元素的下标,如果前一个元素不存在,则返回 -1
void remove()从列表中删除当前元素。如果 remove() 方法在 next() 方法或 previous() 方法调用之前被调用,则引发一个 IllegalStateException 异常
void set(Object obj)将 obj 赋给当前元素。这是上一次调用 next() 方法或 previous() 方法最后返回的元素

举例:

//通过双向迭代方法访问类集
import java.util.*;
public class ListIteratorDemo{
	public static void main(String[] args)
	{
		//创建一个ArrayList数组
		ArrayList<String> al = new ArrayList<String>();
		//加入元素到ArrayList中
		al.add("C");
		al.add("B");
		al.add("A");
		al.add("D");
		al.add("E");
		al.add("F");
		al.add("G");
		//在ListIterator中修改内容
		ListIterator<String> itr = al.listIterator();
		while (itr.hasNext()) {
			Object element = itr.next();
			//用set方法修改其内容
			itr.set(element + "+");
		}
		//下面是将列表中的内容反向输出
		System.out.println("将列表反向输出:");
		//hasPrevious由后向前输出
		while (itr.hasPrevious()) {
			Object element = itr.previous();
			System.out.println(element + " ");
		}
		System.out.println();
	}
}

第一个 while 循环除了逐个修改了 ArrayList 中元素,还将双向迭代器 itr 移到了 ArrayList 的尾部,这样才能完成第二个 while 循环中的反向输出。也就是说,如果现在想要完成由后向前的输出操作,那么首先必须完成由前向后的输出( 或操作 )——即保证反向输出前,迭代器要位于 ArrayList 的尾部。

4.废弃的枚举输出——Enumeration

如果按照历史来讲,Enumeration 属于最古老的输出接口之一,在 JDK 1.0 时就已经提出了,并一直延续着使用了很长时间,直到今天还有许多的类只支持 Enumeration 输出。

Enumeration 接口定义了可以对一个对象的类集中的元素进行枚举( 一次获得一个 )的方法。这个接口尽管没有被摒弃,但已经被 Iterator 所替代。Enumeration 对新程序来说是过时的。然而它仍被几种从以前版本遗留下来的类( 例如 Vector 和 Properties )所定义的方法使用,被几种其他的 API 类所使用的,以及被目前广泛使用的应用程序所使用。

在 JDK 1.5 之后为 Enumeration 增加了泛型的定义,此接口定义如下:

    public interface Enumeration<E>
    {
            public boolean hasMoreElements();
            public E nextElement();
    }

这个接口只是负责输出两个方法,作用如下:

(1)判断是否有下一个元素:public boolean hasMoreElements();

(2)取得下一个元素:public E nextElement()。

执行后,当仍有更多的元素可提取时,hasMoreElement() 方法一定返回 true。党所哟与元素都被枚举了,则返回 false。nextElement() 方法将枚举中的下一个对象作为一个类属 Object 的引用而返回。也就是每次调用 nextElement() 方法获得枚举中的下一个对象。

可是如果想要取得 Enumeration 接口对象并不是依靠 Collection、List、Set 这样的接口,只能够依靠 Vector 子类,在这个类中定义了一个方法:public Enumeration <E> elements()。

举例:

//通过双向迭代方法访问类集
import java.util.Enumeration;
import java.util.Vector;
public class EnumerationDemo{
	public static void main(String[] args)
	{
		Vector<String> all = new Vector<String>();
		//加入元素到ArrayList中
		all.add("C");
		all.add("B");
		all.add("A");
		all.add("D");
		all.add("E");
		all.add("F");
		all.add("G");
		Enumeration<String> enu = all.elements();
		while (enu.hasMoreElements()) {
			String str = enu.nextElement();
			System.out.println(str);
		}
	}
}

在大部分情况下,推荐使用迭代器 Iterator,而 Enumeration 的使用是少数的,只在不得已时才会使用它。

5.for-each 输出

使用 for-each 不仅可以进行数组的输出,也可以进行集合的输出。

举例:

//使用foreach输出
import java.util.ArrayList;
import java.util.List;
public class foreachDemo{
	public static void main(String[] args)
	{
		List<String> all = new ArrayList<String>();
		//加入元素到ArrayList中
		all.add("C");
		all.add("B");
		all.add("A");
		all.add("D");
		all.add("E");
		all.add("F");
		all.add("G");
		for (String str : all) {
			System.out.println(str);
		}
	}
}

推荐遇到集合还是考虑使用 Iterator 较为方便。

十、偶对象保存接口——Map

在 java.util 中还增加了映射( Map )。映射是一个存储关键字和值的关联,或者说是 “ 关键字/值 ” 对的对象,即给定一个关键字,可以得到它的值。关键字和值都是对象,关键字必须是唯一的,但是可以存在相同的值。有的映射可以接收 null 关键字和 null 值,有的则不能。

可以将 Map 视为偶对象保存接口。Collection 每一次只保存一个对象,而 Map 保存的是一对对象,而且这一对对象一定是按照 “ Key = Value ” 的形式保存,也就是说,可以通过 Key 找到对应的 Value,那么这就好比使用电话本一样:

(1)Key = 张三,Value = 1;

(2)Key = 李四,Value = 2;

如果说想在想要找到张三的电话,那么首先应该通过张三这个 Key,然后找到其对应的 Value——1,如果现在保存的数据之中没有对应的 Key,那么就返回 null。

1.映射接口

映射接口定义了映射的特征和本质,下表所列为支持映射的接口:

接口描述
Map映射唯一关键字到值
Map.Entry描述映射中的元素( “ 关键字/值 ” 对 ),是 Map 的一个内部类
SortedMap扩展 Map 以便关键字按升序排序

下面依次简述每个接口:

(1)Map 接口

Map 接口映射唯一关键字到值。关键字( Key )是用于检索值的对象。给定一个关键字和一个值,可以存储这个值到一个 Map 对象中。当这个值被存储以后,就可以使用它的关键字来检索它。Map 的方法总结在下表中。当调用的映射中没有项存在时,其中的几种方法会引发一个 NoSuchElementException 异常。而当对象与映射中的元素不兼容时,则会引发一个 ClassCastException 异常。如果试图使用映射不允许使用的 null 对象,则会引发一个 NullPointerException 异常。当试图改变一个不允许修改的映射时,则会引发一个 UnsupportedOperationException 异常。

方法描述
void clear()从调用映射中删除所有的 关键字/值 对
boolean containsKey(Object k)如果调用映射中包含了作为关键字的k,则返回 true,否则返回 false
boolean containsValue(Object v)如果映射中包含了作为值的 v,则返回 true,否则返回 false
Set<Map.Entry<K,V>> entrySet()将 Map 集合变为 Set 集合返回
boolean equals(Object obj)如果 obj 是一个 Map 并包含相同的输入,则返回 true,否则返回 false
Object get(Object k)返回与关键字 k 相关联的值
int hashCode()返回调用映射的散列码
boolean isEmpty()如果调用映射是空的,则返回 true,否则返回 false
Set<K> keySet()返回一个包含调用映射中关键字的集合( Set )。这个方法为调用映射的关键字提供了一个集合 “ 视图 ”
Object put(Object k,Object v)讲一个输入加入调用映射,改写原先与关键字相关联的值。关键字和值分别为 k 和 v。如果关键字已经不存在了,则返回 null;否则,返回原先与关键字相关联的值
void putAll(Map m)将所有来自 m 的输入加入调用映射
Object remove(Object k)删除关键字等于 k 的输入
int size()返回映射中 关键字/值 对的个数
Collection values()返回一个包含了映射中的值的类集。这个方法为映射中的值提供了一个类集 “ 视图 ” 映射循环使用两个基本操作:get() 和 put()。使用 put() 的方法可以将一个指定了关键字和值的值加入映射。为了得到值,可以通过将关键字作为参数来调用 get() 方法,调用返回该值

需要注意的是,映射不是类集,但可以获得映射的类集 “ 视图 ”。为了实现这种功能,可以使用 entrySet() 方法,它返回一个包含了映射中元素的集合( Set )。为了得到关键字的类集 “ 视图 ”,可以使用 keySet() 方法。为了得到值的类集 “ 视图 ”,可以使用 value() 方法。类集 “ 视图 ” 是将映射继承到类集框架内的手段。

(2)SortedMap 接口

SortedMap 接口扩展了 Map,它确保了各项按关键字升序排序。由 SortedMap 说明的方法总结在下表中。当调用映射中没有的项时,其中的几种方法将引发一个 NoSuchElementException 异常。当对象与映射中的元素不兼容时,则会引发一个 ClassCastException 异常。当试图使用映射不允许使用的 null 对象时,则会引发一个 NullPointerException 异常。

方法描述
Comparator comparator()返回调用排序映射的比较方法。如果调用映射使用的是自然顺序的话,则返回 null
Object firstKey()返回调用映射的第 1 个关键字
SortedMapheadMap(Object end)返回一个排序映射,该映射包含了那些关键字小于 end 的映射输入
Object lastKey()返回调用映射的最后一个关键字
sortedMapsubMap(Object start,Object end)返回一个映射,该映射包含了那些关键字大于等于 start 同时小于 end 的输入
SortedMaptailMap(Object start)返回一个映射,该映射包含了那些关键字大于等于 start 的输入排序映射允许对子映射( 或者说是映射的子集 )进行高效的处理。使用 headMap()、tailMap() 或 subMap() 方法可以获得子映射。调用 firstKey() 方法可以获得集合的第 1 个关键字,而调用 lastKey() 方法则可获得集合的最后一个关键字

(3)Map.Entry 接口

Map.entry 接口使得可以操作映射的输入。由 Map 接口说明的 entrySet() 方法,调用该方法可返回一个包含映射输入的集合( Set ),这些集合中的每一个元素都是一个 Map.Entry 对象。下表总结了由该接口说明的方法。

方法描述
boolean equals(Object obj)如果 obj 是一个关键字和值斗鱼调用对象相等的 Map.Entry,则返回 true
Object getKey()返回该映射项的关键字
Object getValue()返回该映射项的值
int hashCode()返回该映射项的散列值
Object setValue(Object v)将这个映射输入的值赋给 v。如果 v 不是映射的正确类型,则引发一个 ClassCastException 异常。如果 v 存在问题,则引发一个 IllegalStateException 异常。如果 v 是 null,而映射又不允许是 null 关键字,则引发一个 NullPointerException 异常。如果映射不能被改变,则会引发一个 UnsupportedOperationException 异常

2.映射类

有几个类提供了映射接口的实现。可以被用作映射的类如下表所示:

描述
AbstractMap实现大多数的 Map 接口
HashMap将 AbstractMap 扩展到使用散列表
TreeMap将 AbstractMap 扩展到使用树

AbstractMap 对 3 个具体的映射实现来说,是一个超类。AbstractMap 的另一个子类——WeakHashMap 实现一个使用 “ 弱关键字 ” 的映射,它允许映射中的元素,当该映射的关键字不再被使用时,被放入回收站。

(1)HashMap 类

HashMap 类时候用散列表实现 Map 接口,它是 Map 接口中最为常用的子类。HashMap 允许一些基本操作,如 get() 和 put() 的运行时间保持恒定,即便对大型的集合也是这样的。下面的构造方法定义为:

        HashMap()
        HashMap(Map m)
        HashMap(int capacity)
        HashMap(int capacity,float fillRatio)

第 1 种形式构造一个默认的散列映射。第 2 种形式用 m 的元素初始化散列映射。第 3 种形式将散列映射的容量初始化为 capacity。第 4 种形式用它的参数同时初始化散列映射的容量和填充比。容量和填充比的含义与 HashSet 中的容量和填充比相同。

HashMap 实现 Map 并扩展 AbstractMap。它本身并没有增加任何新的方法。应该注意的是:散列映射并不保证它的元素的顺序。因此,元素加入散列映射的顺序并不一定是它们被迭代方法读出的顺序。

举例:

import java.util.*;
import java.util.Map.Entry;
public class HashMapDemo{
	public static void main(String[] args)
	{
		//创建HashMap对象
		HashMap<String,Double> hm = new HashMap<String, Double>();
		//加入元素到HashMap中
		hm.put("1", new Double(11.1));
		hm.put("2", new Double(22.2));
		hm.put("3", new Double(33.3));
		hm.put("4", new Double(44.4));
		hm.put("5", new Double(55.5));
		//返回包含映射中项的集合
		Set<Entry<String,Double>> set = hm.entrySet();
		//用Iterator得到HashMap中的内容
		Iterator<Entry<String,Double>> i = set.iterator();
		//显示元素
		while (i.hasNext()) {
			Map.Entry<String,Double> me = (Map.Entry<String,Double>) i.next();
			System.out.print(me.getKey()+".");
			System.out.println(me.getValue());
		}
		System.out.println();
		//让1中的值增加100
		double d = ((Double)hm.get("1")).doubleValue();
		//用新的值替换掉旧的值
		hm.put("1", new Double(d+100));
		System.out.println("1现在的对应的值为:"+hm.get("1"));
	}
}

(2)TreeMap 类

TreeMap 类是基于红黑树( Red-Black tree )实现 Map 接口。TreeMap 提供了按排序顺序存储 关键字/值 对的有效手段,同时允许快速检索。应该注意的是,不同散列映射,树映射保证它的元素按照关键字升序排序。下面的 TreeMap 构造方法定义为:

        TreeMap()
        TreeMap(Comparator comp)
        TreeMap(Map m)
        TreeMap(SortedMap sm)

第 1 种形式构造一个空的树映射,该映射使用其关键字的自然顺序来排序。第 2 种形式构造一个空的基于树的映射,该映射通过使用 Comparator comp 来排序。第 3 种形式用从 m 的输入初始化树映射,该映射使用关键字的自然顺序来排序。第 4 种形式从 sm 的输入来初始化一个树映射,该应该将按与 sm 相同的顺序来排序。

TreeMap 实现 SortedMap 并且扩展 AbstractMap,而它本身并没有另外定义其他的方法。

举例:

//TreeMap的使用
import java.util.*;
public class TreeMapDemo{
	public static void main(String[] args)
	{
		//创建TreeMap对象
		TreeMap<Integer,String> tm = new TreeMap<Integer, String>();
		//加入元素到TreeMap中
		tm.put(new Integer(11), "1");
		tm.put(new Integer(22), "2");
		tm.put(new Integer(33), "3");
		tm.put(new Integer(44), "4");
		tm.put(new Integer(55), "5");
		Collection<String> col = tm.values();
		Iterator<String> i = col.iterator();
		System.out.println("从低到高的顺序输出:");
		while (i.hasNext()) {
			System.out.println(i.next());
		}
	}
}

这里默认是对关键字( Key )——此处指的是Integer类元素进行了排序。然而在某些特殊情况下,我们可能需要用哈希表的 Value 来排序,这时就需要制定一个比较方法来改变这种排序。

3.比较方法

TreeSet 和 TreeMap 都安排序顺序存储元素。然而,更为 “ 个性化 ” 的排序顺序则需使用特定的比较方法。通常在默认的情况下,这些类通过使用被 Java 称之为 “ 自然顺序 ” 的顺序存储它们的元素,而这种顺序通常也是我们需要的( 例如 A 在 B 前面,1 在 2 前面等)。如果需要用不同的方法对元素进行排序,可以在构造集合或映射时,指定一个 Comparator 对象。这样做为开发者提供了一种精确控制将元素储存到排序类集和映射中的方案。

下面是一个说明定制的比较方法能力的例子。该例子实现 compare() 方法以便它按照正常顺序的逆向进行操作。因此,它是的一个树集合按逆向的顺序进行存储。

//定制的比较方法能力
import java.util.*;
class MyComp implements Comparator<Object>
{
	public int compare(Object o1,Object o2)
	{
		String aStr,bStr;
		aStr = (String)o1;
		bStr = (String)o2;
		return bStr.compareTo(aStr);
	}
}

public class ComparatorDemo
{
	public static void main(String[] args)
	{
		//创建一个TreeSet对象
		TreeSet<String> ts = new TreeSet<String>();
		//向TreeSet对象中加入内容
		ts.add("C");
		ts.add("A");
		ts.add("B");
		ts.add("D");
		ts.add("E");
		//得到Iterator的实例化对象
		Iterator<String> i = ts.iterator();
		//显示全部内容
		while (i.hasNext()) {
			Object element = i.next();
			System.out.println(element + " ");
		}
	}
}

在 compare() 方法内部,String 方法 compareTo() 比较两个字符串。然而由 bStr,而不是 aStr 调用 compareTo() 方法,会导致比较的结果被逆向。

4.旧的子类——Hashtable

哈希表( Hashtable,又称散列表 )是早期 java.util 包中的一部分,同时也是 Dictionary 接口的一个具体实现。然而,自 Java 2 起,重新设计了哈希表( Hashtable ),以便它也能实现映射( Map )接口。因此现在 Hashtable 也被集成到类集框架中。它与 HashMap 相似,但它是同步的。和 HashMap 一样,Hashtable 将关键字/值对存储到散列表中。使用 Hashtable 时,指定一个对象作为关键字,同时指定与该关键字相关联的值。接着,该关键字被散列,而把得到的散列值作为存储在表中的值的下标。

哈希表紧紧可以存储重载由 Object 定义的 hashCode() 和 equals() 方法的对象。hashCode() 方法计算和返回对象的散列码。当然,equals() 方法比较两个对象。幸运的是,许多 Java 内置的类已经实现了 hashCode() 方法。例如,大多数常见的 Hashtable 类型使用字符串( String )对象作为关键字。String 实现 hashCode() 和 equals() 方法。

Hashtable 的构造方法如下:

        Hashtable()
        Hashtable(int size)
        Hashtable(int size,float fillRatio)
        Hashtable(Map m)

第 1 种形式是默认的构造方法。第 2 种形式创建一个哈希表,该散列表具有由 size 指定的原始大小。第 3 种形式创建一个哈希表,该哈希表具有由 size 指定的原始大小和由 fillRatio 指定的填充比。填充比必须介于 0.0 和 1.0 之间,它决定了在散列表向上调整大小之前的充满度。具体地说,当元素的个数大于散列表的容量乘以它的填充比时,哈希表将被扩展。如果没有指定填充比,则默认使用 0.75。最后,第 4 种形式创建一个哈希表,该哈希表用 m 中的元素初始化。哈希表的容量被设为 m 中元素的个数的两倍,默认的填充因子设为 0.75。第 4 种方法是在 Java 2 中新增加的。

举例:

//Hashtable类的使用
import java.util.*;
public class HashtableDemo
{
	public static void main(String[] args)
	{
		Hashtable<String,Integer> numbers = new Hashtable<String, Integer>();
		numbers.put("1", new Integer(1));
		numbers.put("2", new Integer(2));
		numbers.put("3", new Integer(3));
		numbers.put("4", new Integer(4));
		numbers.put("5", new Integer(5));
		Integer n = (Integer)numbers.get("2");
		if (n != null) {
			System.out.println("2 = " + n);
		}
	}
}

5.关于 Map 集合的输出问题

若想输出集合的元素,可直接使用 Iterator,可 Map 接口中并没有定义 iterator() 方法,所以现在如果要使用 Iterator 接口进行 Map 接口输出的话,就必须清楚 Collection 和 Map 接口保存对象的形式上的区别。

(1)Collection 中的每一个元素都是一个独立的对象;

(2)Map 中的每一个元素都是 Key 和 Value 的组合对象——就是所谓的 “ 偶对象 ”。

Map.Entry 是 Map 中定义的一个内部接口,而且这个接口是一个使用了 static 定义的外部接口,在这个接口之中定义了两个非常重要的方法。

(1)取得对应的 Key 的方法。public K getKey();

(2)取得对应的 Value 的方法。public V getValue();

清楚了 Map.Entry 的作用之后,下面就可以采用如下的步骤进行 Map 的 Iterator 输出了。

(1)通过 Map 借口之中 entrySet() 方法将 Map 集合变为 Set 集合,Set 之中的泛型类型为 Map.Entry;

(2)利用 Set 接口之中的 iterator() 方法取得 Iterator 接口对象,此时的泛型类型依然为 Map.Entry;

(3)利用 Iterator 迭代出每一个 Map.Entry 对象,再使用 getKey() 和 getValue() 方法取出内容。

举例:

//利用Iterator输出Map接口
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class iteratorMapDemo
{
	public static void main(String[] args)
	{
		Map<Integer,String> map = new HashMap<Integer, String>();
		map.put(1, "1");
		map.put(2, "2");
		map.put(3, "3");
		Set<Map.Entry<Integer, String>> set = map.entrySet();
		Iterator<Map.Entry<Integer, String>> iter = set.iterator();
		while (iter.hasNext()) {
			Map.Entry<Integer, String> me = iter.next();
			System.out.println(me.getKey() + "→" + me.getValue());
		}
	}
}

十一、前期版本遗留下来的类和接口

java.util 的最初版本中不包括类集框架。取而代之的是,它定义了几个类和接口提供专门的方法用于存储对象。随着在 Java 2 中引入类集,有几种最初的类被重新设计成支持类集接口,因此它们与框架完全兼容。尽管没有类被摒弃,但其中某些方法仍被认为是过时的。当然,在那些重复从以前版本遗留下来的的类的功能性的地方,通常都愿意用类集编写新的代码程序。一般来说,对从以前版本遗留下来的类的支持是因为仍然存在着大量使用它们的基本代码,包括现在仍在被 Java 2 的应用编程接口 ( API )使用的程序。

另一点,没有一个类集类是同步的。但是所有的从以前版本遗留下来的类都是同步的。这一区别在有些情况下是很重要的。当然,通过使用由 Collection 提供的算法也很容易实现类集同步。

由 java.util 定义的从以前版本遗留下来的类如下:Dictionary、Hashtable、Properties、Stack 和 Vector。下面是部分从以前版本遗留下来的类。

1.Stack 类

Stack 是 Vector 的一个子类,它实现标注的呢后进先出堆栈。Stack 仅仅定义了创建空堆栈的默认构造方法。Stack 包括了由 Vector 定义的所有方法,同时增加了几种它自己定义的方法,具体总结如下表:

方法描述
boolean empty()如果堆栈是空的,则返回 true;当堆栈包含有元素时,则返回 false
Object peek()返回位于栈顶的元素,但是并不在堆栈中删除它
Object pop()返回位于站定的元素,并在进程中删除它
Object push(Object element)将 element 压入堆栈,同时也返回 element
int search(Object element)在堆栈中搜索 element,如果发现了,则返回它相对于栈顶的偏移量

下例是一个创建堆栈的例子,将几个整型( Integer )对象压入堆栈,然后再将它们弹出。

//创建堆栈
import java.util.*;
public class StackDemo{
	static void showpush(Stack<Integer> st,int a){
		st.push(new Integer(a));
		System.out.println("→入栈(" + a + ")");
		System.out.println("Stack:" + st);
	}
	static void showpop(Stack<Integer> st){
		System.out.println("出栈→");
		Integer a = (Integer)st.pop();
		System.out.println(a);
		System.out.println("Stack:" + st);
	}
	public static void main(String[] args){
		Stack<Integer> st = new Stack<Integer>();
		System.out.println("Stack:" + st);
		showpush(st, 11);
		showpush(st, 22);
		showpush(st, 33);
		showpop(st);
		showpop(st);
		showpop(st);
		//出栈的时候会有一个EmptyStackException的异常,需要进行异常处理
		try {
			showpop(st);
		} catch (EmptyStackException e) {
			// TODO: handle exception
			System.out.println("异常:栈中内容为空");
		}
	}
}

因为完成了 3 个数组的出栈,由于此时堆栈 st 中没有数据了,所以再次出栈操作,就会引发 EmptyStackException 异常。

2.Dictionary 类

字典( Dictionary )是一个表示关键字/值存储库的抽象类,同时它的操作也很像映射( Map )。给定一个关键字和值,可以将值存储到字典( Dictionary )对象中。一旦这个值被存储了,就能够用它的关键字来检索它。因此与映射一样,字典可以被当做关键字/值对列表来考虑。尽管在 Java 2 中并没有摒弃字典( Dictionary ),但由于它已经被映射( Map )所取代,因而被认为是过时的。然而目前 Dictionary 仍然被广泛地使用。但还是推荐使用 Map 接口去获得关键字/值存储的功能。

3.属性操作类——Properties 类

属性( Properties )是 Hashtable 的一个子类。但是 Properties 与 Hashtable 最大的不同在于:它所能够操作的数据全部是 String,Key 和 Value 的类型全部是字符串。Properties 类被许多其他的 Java 类所使用。例如,当获得系统环境值时,System.getProperties() 返回对象的类型。

Properties 定义了下面的实例变量。

        Properties defaults;

这个变量包含了一个与属性( Properties )对象相关联的默认属性列表。Properties 定义了如下的构造方法:

        Properties()
        Properties(Properties propDefault)

第 1 种形式创建一个没有默认值的属性( Properties )对象,第 2 种形式创建一个将 propDefault 作为其默认值的对象。在这两种情况下,属性列表都是空的。

除了 Properties 从 Hashtable 中继承下来的方法之外,Properties 自己定义的方法列在下表中。Properties 也包含了一个不被推荐使用的方法——save()。它被 store() 方法所取代,因为它不能正确地处理错误。

方法描述
String getProperty(String key)返回与 key 相关联的值。如果 key 既不在列表中,也不在默认属性列表中,则返回一个 null 对象
String getProperty(String key,String defaultProperty)返回与 key 相关联的值。如果 key 既不在列表中,也不在默认属性列表中,则返回 defaultProperty
void list(PrintStream streamOut)将属性列表发给与 streamOut 相连接的输出流
void list(PrintWriter streamOut)将属性列表发给与 streamOut 相连接的输出流
void load(InputStream streamIn) throws IOException从与 steamIn 相连接的输入数据流输入一个属性列表 EnumerationpropertyNames() 返回关键字的枚举,也包括那些在默认属性列表中找到的关键字
Object setProperty(String key,String value)将 value 与 key 关联,返回与 key 关联的前一个值,如果不存在这样的关联,则返回 null( 为了保持一致性,在 Java 2 中新增加的 )
void store(OutputStream streamOut,String description)在写入由 description 指定的字符串之后,属性列表被写入与 streamOut 相连接的输出流( 在 Java 2 中新增加的 )

Properties 类的一个有用功能是可以指定一个默认属性,如果没有值与特定的关键字相关联,则返回这个默认属性。例如,默认值可以与关键字一起在 getProperty() 方法中被指定——如 getProperty("name","default value")。如果 “ name ” 值没有找到,则返回 “ default value ”。当构造一个 Properties 对象时,可以传递 Properties 的另一个实例作为新实例的默认值。在这种情况下,如果对一个给定的 Properties 对象调用 getProperty("foo"),而 “ foo ” 并不存在时,Java 在默认 Properties 对象中寻找 “ foo ”。它允许默认属性的任意层嵌套。

举例:

//Properties的使用
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

public class PropertiesDemo{
	public static void main(String[] args){
		Properties capitals = new Properties();
		Set<Object> states;
		String str;
		capitals.put("1", "0.1");
		capitals.put("2", "0.2");
		capitals.put("3", "0.3");
		capitals.put("4", "0.4");
		capitals.put("5", "0.5");
		//返回包含映射中项的集合
		states = capitals.keySet();
		Iterator<Object> itr = states.iterator();
		while (itr.hasNext()) {
			str = (String)itr.next();
			System.out.println("名称:"+str+"对应的值:"+capitals.getProperty(str)+".");
		}
		System.out.println();
		//查找列表,如果没有则显示为"没发现"
		str = capitals.getProperty("6","没发现");
		System.out.println("6对应的值:" + str + ".");
	}
}

由于 6 不在列表中,所以使用了默认值。尽管当调用 getProperty() 方法时 ,使用默认值是十分有效的,但对大多数属性列表的应用来说,有更好的方法去处理默认值。为了展现更大的灵活性,当构造一个属性( Properties )对象时,可指定一个默认的属性列表。如果在主列表中没有发现期望的关键字,则会搜索默认列表。这种方式只能够操作 String 型数据,不能够保存对象。

4.在 Properties 类中使用 store() 和 load() 方法

Properties 类的一个最有用的特征是可以利用 store() 和 load() 方法方便地对包含在属性( Properties )对象中的信息进行存储或从盘中装入信息。在任何时候,都可以将一个属性( Properties )对象写入流或从中将其读出。这使得属性列表特别便于实现简单的数据库。

//在Properties类中使用store()和load()方法
import java.io.*;
import java.util.*;
public class PropertiesFile{
	public static void main(String[] args){
		Properties settings = new Properties();
		try {
			settings.load(new FileInputStream("d:\\text.txt"));
		} catch (Exception e) {
			// TODO: handle exception
			settings.setProperty("text", new Integer(0).toString());
		}
		int a = Integer.parseInt(settings.getProperty("text")) + 1;
		System.out.println("第"+ a + "次使用");
		settings.put("text", new Integer(a).toString());
		try {
			settings.store(new FileOutputStream("d:\\text.txt"),"PropertiesFile use it.");
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println(e.getMessage());
		}
	}
}

程序每次启动时都回去读取那个记录文件,直接取出文件中所记录的运行次数并加 1 后,又重新将新的运行次数存回文件。由于第 1 次运行时硬盘上还没有那个记录文件,程序去读取那个记录文件时会抛出一个异常,就在处理异常的语句中将属性的值设置为 0,表示程序以前还没有被运行过。如果要用到 Properties 类的 store() 方法进行存储,每个属性的关键字和值都必须是字符串类型的,所以上面的程序没有从父类 HashTable 继承到 put、get 方法进行属性的设置与读取,而是直接用了 Properties 类的 setProperty()、getProperty() 方法进行属性的设置与读取。

十二、Collections 类

Collections 类和 Collection 接口没有任何的继承关系。它是集合类的一个工具类/辅助类,此类的主要目的是提供了一系列的静态方法,用于对集合中元素进行排序( Sort )、混排( Shuffling )、反转( Reverse )、复制( Copy )以及线程安全等各种操作。

举例:

import java.util.ArrayList;
import java.util.Collections;
public class CollectionsDemo{
	public static void main(String[] args){
		double array[] = {12.34,56.7,78.9,10};
		ArrayList<Double> list = new ArrayList<Double>();
		
		for (int i = 0; i < array.length; i++) {
			list.add(new Double(array[i]));
		}
		
		Collections.sort(list);	//对list列表实施排序
		System.out.println("数组排序后");
		
		for (int i = 0; i < array.length; i++) {
			System.out.println(list.get(i));
		}
		
		System.out.println("数组反转后");
		Collections.reverse(list);	//对排序后的list列表实施反转
		
		for (int i = 0; i < array.length; i++) {
			System.out.println(list.get(i));
		}
	}
}

在这两个简单方法的使用上,可以发现,Collections 在辅助操作类集元素上还是非常方便的。

类集框架为程序员提供了一个功能强大的设计方案,以完成编编程过程中面临的大多数人武。下一次当开发者需要存储和检索信息时,可以考虑使用类集。类集不仅仅是专为那些 “ 大型作业 ”,例如联合数据库、邮件列表或产品清单系统等所专用的,它们对于一些小型作业也是很有效的。例如,TreeMap 可以给出一个很好的类集,以保留一组文件的字典结构。TreeSet 在存储工程管理信息时是十分有用的。可以说,对于采用基于类集的解决方法而受益的问题种类,只受限于开发者的想象力。

十三、Java 8 中的泛型与 Lamda 表达式

1.泛型

为了区分不同类型的数据,并实施不同的操作方式,就有了数据类型的概念。Java 的类集框架在本质上就相当于容器,而容器中装的是什么东西需要程序员指定。为了实现算法与数据类型的无关性,以及容器( 数据结构 )与数据类型的无关性,于是就引出了泛型的概念。

泛型是 Java SE 1.5 之后引入的特性,泛型的本质是数据类型参数化,也就是说,所操作的数据类型也被设定为一个可变的参数。这种类型变量可以用在类、接口和方法的创建中。

例如:

        List<Apple> box = new ArrayList<Apple>();
        box.add(new Apple());
        Apple apple = box.get(0);

在这里,第 1 行表示,box 是一个装有 Apple 对象的 List。第 2 行表示在这个 List 中增加一个新对象。第 3 行的 get 方法返回的就是 Apple 类型,而不是通常的 Object 类型。在这个过程中,并不需要进行类型转换。但没有泛型,第 3 行的代码不得不改写成:

        Apple apple = (Apple)box.get(0);

泛型的优点在于提供了容器( 数据结构 )与数据类型的无关性,并且可以向后兼容,但也有不便的地方,就是每次定义时都要写明泛型的类型,这样显式指定泛型类型不仅感觉有些冗长,最主要的是很多程序员不熟悉泛型,因此有时不能给出正确的类型参数。如果能通过编译器自动推断泛型的参数类型,这样就能够减少这样的情况,并提高代码可读性。

2.新内容

在 JDK 7 中,添加了尖括号 < > 操作符,表示是自动类型推断,所以上面的代码在 JDK 7 中的写法为:

        List<Apple> box = new ArrayList< >();    //注意()前面的“<>”里面什么也没有指定
        box.add(new Apple());
        Apple apple = box.get(0);

上面第 1 行代码中,编译器自动推断等号右边的尖括号中的泛型为 Apple。只有在 new ArrayList 后面加上 “ < > ” 才表示是自动类型推断,否则就是非泛型类型的 ArrayList,在编译源代码时会得到一个警告提示。

在 Java 8 中,进一步强化了泛型的推断能力。Java 8 里面泛型的目标类型推断主要有 2 个:

(1)支持通过方法上下文推断泛型目标类型。

(2)支持在方法中调用链路当中,泛型类型推断传递到最后一个方法。

在甲骨文公司有关 Java 的官方文档,提供的 List 类的声明,如下所示:

        class List<E>{
            static <Z> List<Z> nil(){……};
            static <Z> List<Z> cons(Z head,List<Z> tali){……};
            E head(){……}
        }

根据 JEP 101 的特性( 访问链接:http://openjdk.java.net/jeps/101 ),上述类中的通用方法如 List.nil(),可以从赋值语句的右边,自动推断泛型的类型,如:

        List<String> ls = List.nil();

Java 8 的类型推断机制,可以推断出 List.nil() 方法返回的是 String 类型,而无需像下面显示地指定类型:

        List<String> ls = List.<String>nil();

3.引入 Lambda 表达式

Java 8 的最大的亮点之一,是新引入了 Lambda 表达式。使用 Lambda 表达式,会使设计的代码更加简洁,且具有可读性。

那什么是 Lambda 表达式( Lambda Expression )呢?

维基百科上对 Lambda 表达式的解释是:“ 一个被定义的且可能被调用的函数或子程序,但其却没有像普通函数或子程序那样,拥有一个固定的标识符。” 换句话说,Lambda 表达式,可理解为,是一个包含若干表达式和语句的匿名函数( 方法 )。

在某些场合下,可能我们需要一个功能块,但又不想去命名一个函数或方法,这时候 Lambda 表达式就可以用上了。在 Java 8 中,Lambda 表达式就是一个带有输入参数的可执行语句块,一个 “ 深藏功与名 ” 的匿名方法。

当开发者在编写 Lambda 表达式时,也会随之被编译成一个函数式接口。而 “ 函数式接口 ” 是指,仅仅只包含一个抽象方法的接口,每一个该类型的 Lambda 表达式,都会被匹配到这个抽象方法。

举例:

        Runnable runnable = new Runnable(){
            @Override
            public void run(){
                System.out.println("Running without Lambda");
            }
        }

如果使用 Lambda 表达式,其代码可以表示为:

        Runnable runnable = ()->{    //定义一个匿名方法
                System.out.println("Running from Lambda");
        }

使用 Lambda 表达式,可读性增强了,代码量也随之减少。在某种程度上,这些代码简化功能在已诸如 Scala 等 JVM 语言里被广发使用。不断自我完善的 Java,也向这些 JVM-hosted 语言学习。

那么,在 Java 8 中,如何定义一个 Lambda 表达式呢?简单来说,就是以下三条:

(1)方法无名

(2)参数可有

(3)箭头 “ -> ” 分割

格式如下:

        (Type1 param1,Type2 param2,……TypeN paramN)->{
            语句1;
            语句2;
            //…………
            return value;
        }

例如求两个整数之和,可以用 Lambda 表达式表示为:

        (int x,int y)->{ return x+y; }

对应上述三句简单的总结,详细解释说明如下:

(1)可把 Lambda 表达式想象为一个没有名称的方法,该有的参数列表( Type1 param1,Type2 param2,…)和方法体 { … },如同普通方法一样,这些都可以有。

(2)参数列表中的参数,根据需要,可有可无。如果没有参数,就是一对空括号;

(3)毕竟 Lambda 表达式不是普通方法,它有自己特殊的 Logo( 标记 ),就是一个箭头 “ -> ”,它用来分割参数和主体部分,主体部分用花括号 { } 括起来,它可以是一个表达的是或者是一个语句块。

上面的 Lambda 表达式语法是完整版的,写起来比较繁琐。事实上,根据不同情况, Lambda 表达式可以有各种简化版。

(1)省略参数类型

在很多情况下,编译器可从上下文环境中,推断出 Lambda 表达式的参数类型,这正是 “ 泛型 ” 完成的功能。这样 Lambda 表达式就简化为:

        (param1,param2,…,paramN)->{
            语句1;
            语句2;
            //…………
            return value;
        }

上面那个计算两数之和的例子可用简化版的 Lambda 表达式,表示为:

        (x,y)->{ return x+y; }    //x和y的数据类型,可根据这条语句的上下文,自动推断得知

(2)省略参数表小括号

当 Lambda 表达式的参数只有一个时,可进一步省略参数表的小括号 ( )。于是, Lambda 表达式可进一步简写为:

        param1->{
            语句1;
            语句2;
            //…………
            return value;
        }

如果求某个值的 x 平方,用 Lambda 表达式表示为:

        x->{ return x*x; }    //箭头“->”前的x是参数

(3)省略方法体大括号

当 Lambda 表达式只包含一条语句时,还可进一步省略方法体大括号、return 和语句结尾的分号。 Lambda 表达式简化为:

        param1 -> statement

这里的 statement,在编译语言中表示语句的含义。再次用上面的 “ 某个值 x 的平方 ”,来举例说明,用 Lambda 表达式可表示为:

        x -> x*x    //箭头“-.”前的x是参数,箭头后面的是返回值

举例:

//使用Lambda表达式
import java.util.*;
public class List_lambda{
	public static void main(String[] args){
		String[] Persons = {"张三","李四","王五"};
		List<String> players = Arrays.asList(Persons);
		//以前的循环方式
		for (String player : players) {
			System.out.print(player + ";");
		}
		System.out.println("——————After Lambda——————");
		//使用Lambda表达式以及函数操作
		players.forEach((player) -> System.out.print(player + ";"));
	}
}

使用 Lambda 表达式以及函数操作,代码非常简洁,仅需一个语句。

事实上,Lambda 表达是的功能和内涵,远远不止这些简单的描述,更多有关 Lambda 表达式的知识,我们可以参阅甲骨文官方文献( 访问链接:http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html )。

十四、本文注意事项

1.List 接口扩展了 Collection 接口,里面的内容是允许重复的。List 接口的常用子类是 ArrayList 和 Vector,在开发中 ArrayList 性能较高,属于异步处理,而 Vector 性能较低,属于同步处理。

2.Collection 和 Collections 的区别( 面试题 )

Collection 是集合操作的接口,而 Collections 是一个类,专门提供了各个集合接口的操作方法。

3.请解释两种比较器 Comparable 和 Comparator 的区别( 面试题 )

如果要进行对象数组的排序,那么需要比较器的支持,Java 有两种比较器:Comparable、Comparator。

(1)java.lang.Comparable:只有一个 compareTo() 方法,是在类定义的时候默认实现好的接口。

(2)java.util.Comparator:有两个方法 compare()、equals(),需要单独编写一个排序规则类。

4.Collection 和 Map 的区别( 面试题 )

(1)Collection 之中保存数据的目的是输出;

(2)Map 之中保存数据的目的是为了查找。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值