Java之路:Java类集框架

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

一、认识类集框架

在基础应用中,通常我们可以通过数组来保存一组具有相同属性的对象或者基本类型的数据,但使用数组的弊端在于其大小是不可更改的,因此出于灵活性的考虑,可以使用链表来实现动态的数组。任何事情都有两面性,灵活性的代价就是操作上的繁琐

在计算机世界里,处理繁琐问题的常用方法就是将其封装,只向外提供可调用的方法视图。

Java类集框架就是对这一方法的一种官方实现—套动态对象数组的操作类。本质上,Java类集框架就是Java对数据结构的一个大体上的封装。

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

在java.util包之中定义了所有与类集有关的操作接口:Collection、List、Set、Map、Iterator、ListIterator及Enumeration。

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

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

二、类集接口

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

支持类集的接口总结在下表中:
在这里插入图片描述
(1)在这些接口中定义了操作该类集的一些方法。
(2)支持这些方法的类集被称为可修改的(modifiable)。
(3)不允许修改其内容的类集被称为不可修改的(unmodifiable)。
(4)所有内置的类集都是可修改的。
(5)如果对一个不可修改的类集使用这些方法,将引发一个UnsupportedOperationException异常。

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

Collection接口是构造类集框架的基础,是单值数据操作的最大父接口,它声明了所有类集都将拥有的核心方法。如下:
在这里插入图片描述
由于所有类集均实现了Collection,所以熟悉它的方法对于清楚地理解框架是必要的。

扫描二维码关注公众号,回复: 4473208 查看本文章

(1)如果对一个不可修改的类集使用这些方法,将引发一个UnsupportedOperationException异常。
(2)当一个对象与另一个对象不兼容,例如企图增加一个不兼容的对象到一个类集中时,将产生一个ClassCastException异常。

在Collection接口之中所提供的方法一定是在日后进行程序开发中使用最多的方法,但是Collection接口很少在开发之中直接去使用,往往都会使用它的子接口:

(1)List(允许有重复元素)
(2)Set(不允许有重复元素)。

四、Collection接口的具体实现类

在这里插入图片描述

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

List(列表)是Collection接口之中最为常用的一个子接口,首先来观察一下List子接口的定义。

public interface List<E> extends Collection<E>

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

一个列表可以包含重复的元素,即可以存在完全相同的两个元素。

除了由Collection定义的方法之外,List还定义了一些它自己的方法,如下:
在这里插入图片描述

需要注意的是,当类集不能被修改时,其中的几种方法将引发UnsupportedOperationException异常。当一个对象与另一个不兼容时,例如企图将一个不兼容的对象加入一个类集中,将产生ClassCastException异常。

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

1、ArrayList类

ArrayList类扩展AbstractList并执行List接口。ArrayList支持可随需要而增长的动态数组。

在Java中,标准类型的数组是定长的。一旦数组被创建之后,它们不能被加长或缩短,这也就意味着开发者必须事先知道数组可以容纳多少元素。但是在一般情况下,只有在运行时才能知道需要多大的数组。为了解决这个问题,类集框架定义了ArrayList。

本质上,ArrayList是对象引用的一个变长数组。 也就是说,ArrayList能够动态地增加或减小其大小。

数组列表以一个原始大小被创建。当超过了它的大小时,类集就会自动增大。当有对象被删除,数组就可以缩小。

注意:动态数组也被从以前版本遗留下来的类Vector所支持。

ArrayList有如下的构造方法:

(1)ArrayList( ) : 构造一个初始容量为 10 的空列表

(2)ArrayList(Collection c) : 建立一个数组列表,该数组列表由类c中的元素初始化

(3)ArrayList(int capacity) :建立一个数组列表,该数组有指定的初始容量(capacity),容量是用于存储元素的基本数组的大小。当元素被追加到数组列表上时,容量会自动增加。

package com.xy.collection;

import java.util.ArrayList;

public class ArrayListDemo1 {
	public static void main(String[] args) {
		ArrayList<String> a1 = new ArrayList<String>();
		System.out.println("a1中初始元素的个数:" + a1.size());
		// 向ArrayList对象中添加新的内容
		a1.add("Hello");
		a1.add("world");
		a1.add("Hello");
		a1.add("Java");
		System.out.println(a1);
		a1.add(1, "Java");
		System.out.println(a1);
	}
}

【结果】
在这里插入图片描述

2、LinkedList类

LinkedList类扩展了AbstractSequentialList类并实现List接口。它提供了一个链接列表的数据结构。

它具有如下的两个构造方法:

(1)LinkedList( ) : 建立一个空的链接列表。
(2)LinkedList(Collection c) : 建立一个链接列表,该链接列表由类c中的元素初始化。

除了它继承的方法之外,LinkedList类本身还定义了一些有用的方法,这些方法主要用于操作和访问列表。

方法 作用
void addFirst(Object obj) 在列表头增加元素
void addLast(Object obj) 在列表的尾部增加元素
Object getFirst( ); 获得第1个元素
Object getLast( ); 得到最后一个元素
Object removeFirst( ); 删除第1个元素
Object removeLast( ); 删除最后一个元素
package com.xy.collection;

import java.util.LinkedList;

public class LinkedListDemo1 {
	public static void main(String[] args) {
		LinkedList<String> ll = new LinkedList<String>();
		ll.add("Hello");
		ll.add("Java");
		ll.add("!!!");
		ll.add("Hello");	// LinkedList中允许有相同元素
		System.out.println(ll);
		
		System.out.println("在链表最后一个位置加元素!");
		ll.addLast("Hello world!");
		System.out.println(ll);
		
		System.out.println("在链表第一个位置加元素!");
		ll.addFirst("Hello world!");
		System.out.println(ll);
		
		System.out.println("在链表第二个位置加元素!");
		ll.add(1,"The second!");
		System.out.println(ll);
		
		System.out.println("移除链表第二个位置的元素!");
		ll.remove(1);
		System.out.println(ll);
		
		System.out.println("移除链表元素\"java\"!");
		ll.remove("Java");
		System.out.println(ll);
		
		System.out.println("移除链表第一个元素!");
		ll.removeFirst();
		System.out.println(ll);
		
		System.out.println("移除链表最后一个元素!");
		ll.removeLast();
		System.out.println(ll);
		
		Object obj = ll.get(1);
		ll.set(1, (String)obj + "changed");
		System.out.println("改变链表第二个元素!");
		System.out.println(ll);
	}
}

【结果】
在这里插入图片描述

3、Vector类

Vector实现动态数组,这与ArrayList相似,但两者不同的是:Vector是同步的,并且它包含了许多不属于类集框架的从以前版本遗留下来的方法。

随着Java 2的公布,Vector被重新设计来扩展AbstractList和实现List接口,因此现在它与类集是完全兼容的。

下面列出Vector的构造方法:

(1)Vector( ) : 创建一个原始大小为10的默认矢量。

(2)Vector(int size) : 创建一个其原始容量由size指定的矢量。

(3)Vector(int size, int incr) : 创建一个其原始容量由size指定,并且它的增量由incr指定的矢量,增量指定了矢量每次允许向上改变大小的元素的个数。

(4)Vector(Collection c) : 创建一个包含了类集c中元素的矢量。

所有的矢量开始都有一个原始的容量。在这个原始容量达到以后,下一次再试图向矢量中存储对象时,矢量会自动为那个对象分配空间,同时为别的对象增加额外的空间。

通过分配超过需要的内存,矢量减小了可能产生的分配的次数。这种次数的减少是很重要的,因为分配内存是很花时间的。在每一次的再分配中,分配的额外空间的总数由在创建矢量时指定的增量来确定。如果没有指定增量,在每个分配周期,矢量的大小增加一倍。

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

(1)int capacityIncrement;
(2)int elementCount;
(3)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() 返回矢量的第一个元素
int indexOf(Object element) 返回element首次出现的位置下标。如果对象不在矢量中,则返回 -1
int indexOf(Object element, int start) 返回对象在矢量中,在start及其之后第1次出现的位置下标。如果对象不在矢量中,则返回 -1
void insertElement(Object element, int index) 在矢量中,在由index指定的位置处加入element
boolean isEmpty() 如果矢量是空的,则返回true;如果它包含了一个或更多个元素,则返回false
Object lastElement() 返回矢量中最后一个元素
int lastIndexOf(Object element) 返回element在矢量中最后一次出现的位置下标。如果对象不包含在矢量中,则返回 -1
int lastIndexOf(Object element,int start) 返回对象在矢量中,在start之前最后一次出现的位置下标。如果对象不在矢量中,则返回 -1
void removeAllElements() 清空矢量,在这个方法执行以后,矢量的大小为0
boolean removeElement(Object element) 从矢量中删除element。对于指定的对象,矢量中如果有许多个实例,则其中第一个实例被删除。如果成功删除,则返回true,如果没有发现对象,返回false
void removeElementAt(int index) 删除由index指定位置处的元素
void setElementAt(Object element, int index) 将由index指定的位置分配给element
void setSize(int size) 将矢量中的元素个数设置为size。如果新的长度小于旧的长度,元素将丢失;如果新的长度大于旧的长度,则在其后加null元素
int size() 返回矢量当前元素的个数
String toString() 返回矢量的字符串等价形式
void trimToSize() 将矢量的容量设为与当前拥有的元素的个数相等
package com.xy.collection;

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

public class VectorDemo1 {
	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");
		v.add("F");
		Enumeration<String> e = v.elements();
		while(e.hasMoreElements()) {
			System.out.print(e.nextElement() + " ");
		}
		
	}
}

【结果】
在这里插入图片描述

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

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

六、数组操作类——Arrays

在本质上,类集本身是一个对象数组。

那么在之前曾经学习过一个java.util.Arrays类,这个类是可以操作数组的。Arrays类数组操作类,可用来操作数组(如数组元素排序、搜索和填充等)的各种方法。Arrays类的常用方法如下表所示:

在这里插入图片描述

package com.xy.collection;
import java.util.Arrays;
import java.util.List;

public class ArraysDemo1 {
	public static void main(String[] args) {
	    List<String> all=Arrays.asList("Hello","World","你好","世界");
	    System.out.println(all);
	}
}

【结果】
在这里插入图片描述

七、比较器

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

1、Comparable接口

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

public interface Comparable<T> {
    public int compareTo(T o) ;
}

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

(1)相等:0
(2)大于:1
(3)小于:-1

下面的范例是使用Comparable接口的范例:

package com.xy.test3;

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) {
		super();
		this.title = title;
		this.price = price;
	}
	
	@Override 
	public String toString() {
		return "[title = " + title + ", price = " + price + "]";
	}
	
	@Override
	public int compareTo(Book b) {
		if(this.price > b.price) {
			return 1;
		}
		else if(this.price < b.price) {
			return -1;
		}
		else {
			return 0;
		}
	}
}
public class ComparableDemo1 {
	public static void main(String[] args) {
		List<Book> bookList = new ArrayList<Book>();
		bookList.add(new Book("Oracle开发实践", 60));
		bookList.add(new Book("Java开发实践", 69.8));
		bookList.add(new Book("Java Web开发实践", 59.8));
		bookList.add(new Book("Android开发实践", 58));
		Object[] obj = bookList.toArray();
		Arrays.sort(obj);
		for(Object o : obj) {
			System.out.println(o);
		}
	}
}

【结果】
在这里插入图片描述

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。

package com.xy.test3;

import java.util.Arrays;
import java.util.Comparator;

class Book {
	private String title;
	private double price;
	public Book(String title, double price) {
		super();
		this.title = title;
		this.price = price;
	}
	
	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;
	}

	@Override 
	public String toString() {
		return "[title = " + title + ", price = " + price + "]";
	}
}

class BookComparator implements Comparator<Book> {
	public int compare(Book o1, Book o2) {
		if(o1.getPrice() > o2.getPrice()) {
			return 1;
		}
		else if(o1.getPrice() < o2.getPrice()) {
			return -1;
		}
		else {
			return 0;
		}
	}
}
public class ComparatorDemo1 {
	public static void main(String[] args) {
		Book[] book = new Book[] {
			new Book("Oracle开发实践", 60),
			new Book("Java开发实践", 69.8),
			new Book("Java Web开发实践", 59.8),
			new Book("Android开发实践", 58)
		}; 
		Arrays.sort(book, new BookComparator()); // 为对象数组排序 
		for(Book b : book) {
			System.out.println(b);
		}
	}
}

【结果】
在这里插入图片描述

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

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

集合接口定义了一个集合,并添加了类集中元素不允许重复的特性,即在set中不能出现完全相同的两个元素。

因此,如果试图将复制元素加到集合中时,add()方法将返回false。它本身并没有定义任何附加的方法。在Set接口中也有两个常用的子类:HashSet、TreeSet。

1、HashSet类

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

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

下面的构造方法定义为:

(1)HashSet() :构造一个默认的散列集合
(2)HashSet(Collection c) :用c中的元素初始化散列集合
(3)HashSet(int capacity) :用capacity初始化散列集合的容量
(4)HashSet(int capacity,float fillRatio) :用它的参数初始化散列集合的容量和填充比(也称为加载容量)


填充比必须介于0.0与1.0之间,它决定在散列集合向上调整大小之前,有多少空间被充满。具体来说,就是当元素的个数大于散列集合容量乘以它的填充比时,散列集合将被扩大。对于没有获得填充比的构造方法,默认为0.75。

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

package com.xy.set;

import java.util.HashSet;

public class HashSetDemo1 {
	public static void main(String[] args) {
		HashSet<String> hs = new HashSet<String>();
		hs.add("B");
		hs.add("A");
		hs.add("D");
		hs.add("E");
		hs.add("C");
		hs.add("F");
		System.out.println(hs);
	}
}

【结果】
在这里插入图片描述

2、TreeSet类

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

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

(1)TreeSet( ) : 构造一个空的树集合,该树集合将根据其元素的自然顺序按升序排序。
(2)TreeSet(Collection c) :构造一个包含了c的元素的树集合。
(3)TreeSet(Comparator comp) :构造一个空的树集合,它按照由comp指定的比较方法进行排序。
(4)TreeSet(SortedSet ss) :构造一个包含了ss的元素的树集合。

下面是一个TreeSet的使用范例:

package com.xy.set;

import java.util.TreeSet;

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

【结果】
在这里插入图片描述

3、SortedSet接口

SortedSet接口扩展了Set并说明了按升序排列的集合的特性。除了那些由Set定义的方法之外,由SortedSet接口说明的方法列在下表中:

在这里插入图片描述

当没有项包含在调用集合中时,其中的几种方法会引发NoSuchElementException异常。当对象与调用集合中的元素不兼容时,将引发ClassCastException 异常。如果试图使用null 对象,而集合不允许null 时,会引发NullPointerException异常。

猜你喜欢

转载自blog.csdn.net/qq_43555323/article/details/84944047