Java — 集合(1)(Core Java I)

13.1 集合接口

本节将介绍Java结合框架的基本设计,展示使用他们的方法,并解释一些颇具争议的特性背后的考虑。

13.1.1 将集合的接口与实现分离

队列接口指出可以在队列的尾部添加元素,在队列的头部删除元素,并且可以查找队列中元素的个数

队列的实现方式一般有两种:一种是循环数组;另一种的链表实现

接口集合与实现在概念上的不同:如果需要一个循环数组队列->ArrayDeque类来实现;如果需要一个

链表队列,就直接使用->LinkeList类。这两个类实现了Queue接口。

我们说,接口的本身并不能说明哪种实现的效率高,循环数组要比链表更高效,但是容量有限,链表

相反。如果程序中要收集的对象数量没有上限,最好使用链表来实现。


13.1.2 Java类库中的集合接口和迭代器接口

集合类的基本接口就是Collection接口。

public interface Collection<E>{
boolean add(E element);
Iterator<E> iterator();
...
}



 
 

add 方法用于向集合中添加元素。添加元素成功返回ture,未成功【集合没有发生变化】返回false。例如

向集合中添加一个对象,而这个对象在集合中已经存在,这个添加没有成功,因为集合中不允许有重复的对象。

iterator 方法用于返回一个 实现了Iterator接口的对象。可以使用这个迭代器对象依次访问集合中的元素。

1)迭代器

Iterator接口包含3个方法:

 public interface Iterator<E>{
E next();
boolean hasNext();
void remove();
}



 
  
 next 方法可以逐个访问集合中的每个元素,但是到了集合的末尾,next方法将抛出一个NoSuchElementException。 
 

因此,需要在调用next之前调用hasNext方法。

hasNext 方法 用于检查序列中是否还有元素。

 Collection<String> c = ...;
 Iterator<String> iter = c.iterator();
 while(iter.hasNext()){
String element = iter.next();
do something with element
...
}



 
 

注:

从Java SE 5.0 起,这个循环可以采用一种更加优雅的缩进方法。

 for(String element :c){
do something with element
...
}



 
  
 编译器简单地将"for each" 循环翻译为带有迭代器的循环。 
 

Collection接口扩展了Iterable接口。因此,对于标准库中的任何集合都可以使用"for each" 循环。

元素的访问的顺序取决于集合类型。ArrayList迭代,迭代器从索引 0 开始,每迭代一次,索引值 加1。

HashSet迭代之后,每个元素将会按照 某种随机的次序出现。但是对于计算总和 或者 统计某个条件的

元素个数这类与顺序无关来说不是什么问题。

2)删除元素 

remove 方法用于删除上次调用next方法是返回的元素。如果想要删除指定位置上的元素,仍然需要越过这个元素。

 if.remove();
 it.remove();//错误
//删除两个相邻的元素
 it.remove();
 it.next();
 it.remove();//正确



 
 3)泛型实用方法 
 

下面十一检测任意集合是否包含指定元素的泛型方式

//这样做的好处就是不必自己重新构建这些方法了。contains就是这样一个方法
 public static <E> boolean contains(Collection<E> c,Object obj){
for(E element: c)
if(element.equals(obj))
return true;
return false;
}


Collection接口声明了很多有用的方法,所有实现类都必须提供这些方法。下面例举一部分:

 int size();
 boolean isEmpty();
 boolean contains(Object obj);
 boolean containsAll(Collection<?> c)
 boolean equals(Object other);
 boolean addAll(Collection<? extends E> from);
 boolean remove(Object obj);
 boolean removeAll(Collection<?> c>;
 void clear();<pre name="code" class="java">

 
 

 
  
 如果我们在实现每个类都要提供如此多的例行方法将是很麻烦的。为了实现者能够更容易地实 
 

现这个接口,java库类提供了一个类AbstractCollection,它将基础方法size() 、iterator()抽象化。

 public abstract class AbstractCollection<E> implements Collection<E>{
 ...
 public abstract Iterator<E> iterator();
 public boolean contains(Object obj){
for(E element: this)//迭代器 iterator
if(element.equals(obj))
return true;
return false;
}
...
}

boolean retainAll(Collection<?> c);
 Object[] toArray();
 <T> T[] toArray(T[] arrayToFill);



 
  
 一个具体的集合类可以扩展AbstractCollection类。现在要由具体的集合类提供iterator方法,contains方法 
 

是由AbstractCollection超类提供。如果子类有更加有效的方式去实现contains方法也可以由子类提供,这点

没有任何限制。

对于类框架来说,这个设计很好。集合类用户可以使用泛型接口(Collection 、Iterator等)中一组更加丰富的方法,而实际的数据结构

实现者并没有需要实现所有例行方法的负担

13.2.1 链表

    由于最近数据结构老师在将链表,对于c实现的链表的创建、查找、删除、略微懂,这里不说了。

    链表 和 顺序表 相比,优势在于在一组数据中间添加和删除一个元素的代价。

下面代码实例中,先添加3个元素,再将第二个元素删除:

package com.test1;

import java.util.*;
/**
 * linkedList 链表
 * 一些需要注意的点: 
 * 1.链表与泛型集合的重要区别:链表是ordered collection 每个对象的位置很重要
 * 2.链表的缺点是不能高效的随机访问对象元素,虽然LinkedList类提供了get方法,但其效率不敢恭维
 * 3.使用链表的唯一理由就是 减少在列表中间插入或删除元素所付出的代价
 * 4.List 中只有少数几个元素 我们可以使用ArrayList
 * @author PeersLee
 *	该代码首先创建两个链表,合并,然后从第二个链表中每隔一个元素删除一个元素,最后测试removeAll
 */
public class test2 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<String> staff = new LinkedList<>();
		staff.add("A");
		staff.add("B");
		staff.add("C");
		ListIterator<String> iter = staff.listIterator();
		String first = iter.next();
		System.out.println("first:"+first);
		String second = iter.next();
		System.out.println("second:"+second);
		//注意此时挡板在 AB|C
		iter.previous();//此方法和next()一样,返回刚越过的对象
		//A|BC
		String testString = iter.previous();
		//|ABC
		System.out.println("testString:"+testString);
		int ni = iter.nextIndex();
		System.out.println("ni:"+ni);
		int pi = iter.previousIndex();
		System.out.println("pi:"+pi);
		//ni = 0
		//pi =-1
		//说明游标在初始位置
		
	}

}


 
 

链表与泛型集合之间有一个重要的区别。链表有序,对象的位置很重要。LinkedList.add() 方法将对象添加到链表的尾

部。如果将元素添加到链表的中间时,由于迭代器是描述集合中位置的,所以这种依赖位置的add()方法将由迭代器负

责。只有对自然有序的集合使用迭代器添加元素才有实际意义。集(set)类型,其中元素完全无序,因此,在Iterator

接口中就没有add()方法。然而子接口ListIterator包含add()方法[这个方法不返回boolean类型的值,它假定添加操作总

会改变链表],并且还可以反向的遍历链表。

下面的代码实现ListIterrator中的一些方法:

package com.test1;

import java.util.*;
public class test2 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<String> staff = new LinkedList<>();
		staff.add("A");
		staff.add("B");
		staff.add("C");
		ListIterator<String> iter = staff.listIterator();
		String first = iter.next();
		System.out.println("first:"+first);
		String second = iter.next();
		System.out.println("second:"+second);
		//注意此时挡板在 AB|C
		iter.previous();//此方法和next()一样,返回刚越过的对象
		//A|BC
		String testString = iter.previous();
		//|ABC
		System.out.println("testString:"+testString);
		int ni = iter.nextIndex();
		System.out.println("ni:"+ni);
		int pi = iter.previousIndex();
		System.out.println("pi:"+pi);
		//ni = 0
		//pi =-1
		//说明游标在初始位置
		
	}

<span style="font-size:18px;">}</span>

小结:

    虽然链表随机访问元素的效率十分低,但是LinkedList还是提供了一个 get();方法,当然,如果你使用了这个方法

就说明你选择错了数据结构。

    使用链表的唯一理由就是尽可能降低 在列表中间插入或者删除元素 的代价,如果链表只有几个元素,完全可以

使用ArrayList()。

下面的代码是通过调用AbstractCollection类中的toString方法打印出链表a中的所有元素:

package com.test1;

import java.util.*;

public class test3 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<String> a = new LinkedList<>();
		a.add("A");
		a.add("B");
		a.add("C");
		
		List<String> b = new LinkedList<>();
		b.add("D");
		b.add("E");
		b.add("F");
		b.add("G");
	    //将b中的对象移动到 a中
		ListIterator<String> aIter = a.listIterator();
		Iterator<String> bIter = b.iterator();
		 
		while(bIter.hasNext()){
			if(aIter.hasNext()) aIter.next();
			//个人理解 游标在新插入对象右边
			aIter.add(bIter.next());
		}
		System.out.println(a);
		System.out.println(b);
		//删除位置代号是偶数的对象(坐标从‘1’开始)
		bIter = b.iterator();
		while(bIter.hasNext()){
			bIter.next();
			if(bIter.hasNext()){
				bIter.next();
			   bIter.remove();
			}
		}
		System.out.println(b);
		//从链表a中将b中的所有元素删除
		a.removeAll(b);
		System.out.println(a);
	}
	
}

13.2.2 数组列表

  •  ArraysList 封装了一个动态再分配的对象数组。
  •  ArraysList方法不是同步的,在需要同步时建议使用Vector。

13.2.3 散列表

  • 散列表可以用于实现几个重要的数据结构,例如 set类型。
  • set 是没有重复元素的元素集合。
  • set的add方法首先在集合中查找要添加的元素,若不存在则将其加入

package com.test3;

import java.util.*;
/**
 * 散列集
 * 如果不在乎元素的顺序,则有几种能够快速查找元素的数据结构,他们是按照有利于其操作目的的原则组织数据
 * HashTable 散列表 快速查找所需要的对象
 * 散列码 由 hashCode 函数得出  由对象的实例域产生的一个整数
 * 每个列表被称为一个 bucket 桶
 * 要计算出对象的位置就要就算出散列码,与桶的总数取余,所得的结果就是保存这个元素的一个引索
 * 
 * @author PeersLee
 * 
 *该程序实现 读取输入的所有单词,将他们添加到散列集中,遍历,打印出数量
 */
public class SetTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//HashSet 实现Set 其底层实现为HashMap
		Set<String> words = new HashSet<>();
		long totalTime = 0;
		
		Scanner in = new Scanner(System.in);
		/*
		 * hasNext()
		 * Returns true if this scanner has another token in its input.
		*/
		int n = 1;
		while(in.hasNext() && n <= 20) {
			/*
			 * next()
			 * Finds and returns the next complete token from this scanner.
			 */
			String word = in.next();
			/*
			 * currentTimeMillis()
			 * Returns the current time in milliseconds.
			 */
			long callTime = System.currentTimeMillis();
			words.add(word);
			callTime = System.currentTimeMillis() - callTime;
			totalTime += callTime;
			n++;
		}
		Iterator<String> iter = words.iterator();
		for(int i = 1; i <= 20 && iter.hasNext(); i++) {
			System.out.println(iter.next());
			System.out.println("...");
			System.out.println(words.size() + " distinct words(不同的词) " + totalTime + " milliseconds.");
		}
		
	}

}


13.2.4 树集 TreeSet

  • 树集 TreeSet 也是个有序集合,其排序是树(red-black tree)机构完成的。
  • 红黑树(http://blog.csdn.net/v_july_v/article/details/6105630)-> 每次将一个元素添加到树中时,都将其放在正确的排序位置。
  • 将元素加入到TreeSet中要比HashSet中慢,不过TreeSet可以自动的对元素进行排序。

13.2.5 对象的比较

  • 树集假定插入的元素实现了Comparable接口。
  • 树的排序是全序的,任意两个元素必须是可比的。

目录:

Java — 集合(1)(Core Java I)_第1张图片

TreeSetTest:


package com.test1;

import java.util.*;
/**
 * 1.创建了两个Item对象的树集
 * 2.第一个按照部件的编号排序,这是 Item 对象的默认顺序
 * 3.第二个是通过定制的比较器来按照描述信息排序
 * @author PeersLee
 *
 */
public class TreeSetTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		/**
		 * SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。
		 *  TreeSet类实现Set 接口,该接口由TreeMap 实例支持。此类保证排序后的 set 按照升序排列元素
		 */
		SortedSet<Item> parts = new TreeSet<>();
		parts.add(new Item("Toaster[烤面包片机]", 1234));
		parts.add(new Item("Widget[装饰品]", 4567));
		parts.add(new Item("Modem[调制解调器]", 6789));
		System.out.println("parts: " + parts);
		
		SortedSet<Item> sortByDescription = new TreeSet<>(new Comparator<Item>() {
			public int compare(Item a, Item b) {
				String descrA = a.getDescription();
				String descrB = b.getDescription();
				return descrA.compareTo(descrB);
			}
		});
		
		sortByDescription.addAll(parts);
		System.out.println(sortByDescription);
	}

}

Item

package com.test1;

import java.util.*;

public class Item implements Comparable<Item> {
	
	//partNumber -> 零件号码
	private String description;
	private int partNumber;
	
	// construct an item.
	public Item(String aDescription, int aPartNumber) {
		description = aDescription;
		partNumber = aPartNumber;
	}
	
	//Gets
	public String getDescription() {
		return description;
	}
	
	public String toString() {
		return "[description=" + description +", partNumber=" + partNumber +"]";
	}
	
	public boolean equals(Object otherObject) {
		
		if(this == otherObject) return true;
		if(otherObject == null) return false;
		if(getClass() != otherObject.getClass()) return false;
		
		Item other = (Item)otherObject;
		return Objects.equals(description, other.description) && partNumber == other.partNumber;
	}
	
	public int hashCode() {
		return Objects.hash(description, partNumber);
	}
	
	public int compareTo(Item other) {
		return Integer.compare(partNumber, other.partNumber);
	}

}





你可能感兴趣的:(Java — 集合(1)(Core Java I))