Java Collection

Java Collection

Table Of Contents

Java Collections Introduction. 2

Overview of Interfaces. 2

Iterable. 4

Collection. 4

Collection子类型... 4

Adding and Removing Elements. 5

Checking if a Collection Contains a Certain Element. 5

Collection Size. 5

Iterating a Collection. 5

Generic Collections. 6

List. 6

List Implementations. 6

Adding and Accessing Elements. 7

Removing Elements. 8

Generic Lists. 8

Map. 8

Map Implementations. 8

Adding and Accessing Elements. 9

Removing Elements. 10

Generic Maps. 10

Queue. 10

Queue Implementations. 10

Adding and Accessing Elements. 11

Removing Elements. 12

Deque. 12

Deque Implementations. 12

Adding and Accessing Elements. 13

Removing Elements. 14

Stack. 14

Searching the Stack. 14

hashCode() and equals() 15

equals() 15

hashCode() 16

 

Java Collections Introduction

Java集合的API给java开发者提供了许多的类和接口,使得我们能够很容易的操作对象的集合。从某种意义上来说,集合有点像数组,除了集合的大小能够动态的改变外,而且集合与数组比起来有更多高级的用法。

因为集合使用的频繁性,java就预先为我们准备了许多集合类供我们调用。今天的课程我们就讲解一下java集合的大纲,不会去详细了解java的所有集合,当你对java集合有一个大概的了解后,再去看java文档的时候就会更容易了。

大部分的java集合都放在java.util包下,还有一些并发控制的集合放在java.util.concurrent包下,今天的课程不会涉及到并发控制的集合,有兴趣的可以去google一下或参考java文档,慢慢发现。

 

Overview of Interfaces

为了有效的理解和使用Java Collections API,首先我们来看一下他所包含的接口,这里的接口可以分为两类,Collection 和 Map。

下面是Collection接口的层次结构:

 

下面是Map接口的层次结构:

 

Iterable

Iterable(java.lang.Iterable)接口是java集合类中的一个根接口,Collection接口继承自Iterable,所有Collection的子类型都实现了Iterable接口。

实现了Iterable接口的类能够用for循环来便利其中的其中的元素。例如:

List list = new ArrayList();

for(Object o : list){

    //do something;   

}

Iterable接口只有一个方法:

public interface Iterable<T> {

  public Iterator<T> iterator();   

}

Collection

Collection接口也java集合类的一个根接口,通常不会直接实例化一个Collection的实例,而是使用其子类型来声明。

Collection子类型

  • List
  • Set
  • SortedSet
  • NavigableSet
  • Queue
  • Deque

Collection接口定义了一组方法供他的子类型使用,这样平时我们就可以忽略我们具体使用的是哪一个子类型,只要将它当做一个Collection就行了。

操作集合示例:

public class MyCollectionUtil{

 

  public static void doSomething(Collection collection) {

   

    Iterator iterator = collection.iterator();

    while(iterator.hasNext()){

      Object object = iterator.next();

 

      //do something to object here...

    }

  }

}

 

调用:

Set  set  = new HashSet();

List list = new ArrayList();

 

MyCollectionUtil.doSomething(set);

MyCollectionUtil.doSomething(list);

Adding and Removing Elements

忽略你正在使用的是Collection的哪一个子类型,这里我们可以使用几个Collection的标准方法:add()和remove(),我们来做个示例:

String     anElement  = "an element";

Collection collection = new HashSet();

 

boolean didCollectionChange = collection.add(anElement);

boolean wasElementRemoved   = collection.remove(anElement);   

Add()方法将添加指定的元素到集合中,如果添加成功就会返回true。

Remove()方法将指定的元素移除集合,成功的时候返回true。

Checking if a Collection Contains a Certain Element

Collection接口有两个方法来检查是否包含一个或多个元素,contains()  and containsAll().示例:

Collection collection   = new HashSet();

boolean containsElement = collection.contains("an element");

 

Collection elements     = new HashSet();

boolean containsAll     = collection.containsAll(elements);

Collection Size

使用size()方法可以返回集合的大小。

Iterating a Collection

Collection collection = new HashSet();

//... add elements to the collection

 

Iterator iterator = collection.iterator();

while(iterator.hasNext()){

    Object object = iterator.next();

    //do something to object;   

}

 

Generic Collections

泛型的使用方式类似这样:

Collection<String> stringCollection = new HashSet<String>();

stringCollection引用变量只能包含String类型的实例,你如果想添加其他的类型,编译器就会报错。

使用for循环来遍历上面的结合

Collection<String> stringCollection = new HashSet<String>();

 

for(String stringElement : stringCollection) {

  //do something with each stringElement   

}

List

The java.util.List interface is a subtype of the java.util.Collection interface. It represents an ordered list of objects, meaning you can access the elements of a List in a specific order, and by an index too. You can also add the same element more than once to a List.

List Implementations

Being a Collection subtype all methods in the Collection interface are also available in the List interface.

Since List is an interface you need to instantiate a concrete implementation of the interface in order to use it. You can choose between the following List implementations in the Java Collections API:

  • java.util.ArrayList
  • java.util.LinkedList
  • java.util.Vector
  • java.util.Stack

There are also List implementations in the java.util.concurrent package, but I will leave the concurrency utilities out of this tutorial.

Here are a few examples of how to create a List instance:

 

List listA = new ArrayList();

List listB = new LinkedList();

List listC = new Vector();

List listD = new Stack();   

Adding and Accessing Elements

To add elements to a List you call its add() method. This method is inherited from the Collection interface. Here are a few examples:

List listA = new ArrayList();

 

listA.add("element 1");

listA.add("element 2");

listA.add("element 3");

 

listA.add(0, "element 0");

The first three add() calls add a String instance to the end of the list. The last add() call adds aString at index 0, meaning at the beginning of the list.

The order in which the elements are added to the List is stored, so you can access the elements in the same order. You can do so using either the get(int index) method, or via the Iteratorreturned by the iterator() method. Here is how:

List listA = new ArrayList();

 

listA.add("element 0");

listA.add("element 1");

listA.add("element 2");

 

//access via index

String element0 = listA.get(0);

String element1 = listA.get(1);

String element3 = listA.get(2);

 

 

//access via Iterator

Iterator iterator = listA.iterator();

while(iterator.hasNext(){

  String element = (String) iterator.next();

}

 

 

//access via new for-loop

for(Object object : listA) {

    String element = (String) object;

}

When iterating the list via its Iterator or via the for-loop (which also uses the Iterator behind the scene), the elements are iterated in the same sequence they are stored in the list.

Removing Elements

You can remove elements in two ways:

  1. remove(Object element)
  2. remove(int index)

remove(Object element) removes that element in the list, if it is present. All subsequent elements in the list are then moved up in the list. Their index thus decreases by 1.

remove(int index) removes the element at the given index. All subsequent elements in the list are then moved up in the list. Their index thus decreases by 1.

Generic Lists

By default you can put any Object into a List, but from Java 5, Java Generics makes it possible to limit the types of object you can insert into a List. Here is an example:

List<MyObject> list = new ArrayList<MyObject>();

This List can now only have MyObject instances inserted into it. You can then access and iterate its elements without casting them. Here is how it looks:

MyObject myObject = list.get(0);

 

for(MyObject anObject : list){

   //do someting to anObject...  

}

 

Map

The java.util.Map interface represents a mapping between a key and a value. The Map interface is not a subtype of the Collection interface. Therefore it behaves a bit different from the rest of the collection types.

Map Implementations

Since Map is an interface you need to instantiate a concrete implementation of the interface in order to use it. You can choose between the following Map implementations in the Java Collections API:

  • java.util.HashMap
  • java.util.Hashtable
  • java.util.EnumMap
  • java.util.IdentityHashMap
  • java.util.LinkedHashMap
  • java.util.Properties
  • java.util.TreeMap
  • java.util.WeakHashMap

In my experience, the most commonly used Map implementations are HashMap and TreeMap.

Each of these Set implementations behaves a little differently with respect to the order of the elements when iterating the Set, and the time (big O notation) it takes to insert and access elements in the sets.

HashMap maps a key and a value. It does not guarantee any order of the elements stored internally in the map.

TreeMap also maps a key and a value. Furthermore it guarantees the order in which keys or values are iterated - which is the sort order of the keys or values. Check out the JavaDoc for more details.

Here are a few examples of how to create a Map instance:

Map mapA = new HashMap();

Map mapB = new TreeMap();

Adding and Accessing Elements

To add elements to a Map you call its put() method. Here are a few examples:

Map mapA = new HashMap();

 

mapA.put("key1", "element 1");

mapA.put("key2", "element 2");

mapA.put("key3", "element 3");

The three put() calls maps a string value to a string key. You can then obtain the value using the key. To do that you use the get() method like this:

String element1 = (String) mapA.get("key1");

You can iterate either the keys or the values of a Map. Here is how you do that:

 

// key iterator

Iterator iterator = mapA.keySet().iterator();

 

// value iterator

Iterator iterator = mapA.values();

Most often you iterate the keys of the Map and then get the corresponding values during the iteration. Here is how it looks:

Iterator iterator = mapA.keySet().iterator();

while(iterator.hasNext(){

  Object key   = iterator.next();

  Object value = mapA.get(key);

}

 

//access via new for-loop

for(Object key : mapA.keySet()) {

    Object value = mapA.get(key);

}

Removing Elements

You remove elements by calling the remove(Object key) method. You thus remove the (key, value) pair matching the key.

Generic Maps

By default you can put any Object into a Map, but from Java 5, Java Generics makes it possible to limit the types of object you can use for both keys and values in a Map. Here is an example:

Map<String, MyObject> map = new HashSet<String, MyObject>();

This Map can now only accept String objects for keys, and MyObject instances for values. You can then access and iterate keys and values without casting them. Here is how it looks:

for(MyObject anObject : map.values()){

   //do someting to anObject...

}

 

for(String key : map.keySet()){

   MyObject value = map.get(key);

   //do something to value

}

 

Queue

The java.util.Queue interface is a subtype of the java.util.Collection interface. It represents an ordered list of objects just like a List, but its intended use is slightly different. A queue is designed to have elements inserted at the end of the queue, and elements removed from the beginning of the queue. Just like a queue in a supermarket.

Queue Implementations

Being a Collection subtype all methods in the Collection interface are also available in theQueue interface.

Since Queue is an interface you need to instantiate a concrete implementation of the interface in order to use it. You can choose between the following Queue implementations in the Java Collections API:

  • java.util.LinkedList
  • java.util.PriorityQueue

LinkedList is a pretty standard queue implementation.

PriorityQueue stores its elements internally according to their natural order (if they implementComparable), or according to a Comparator passed to the PriorityQueue.

There are also Queue implementations in the java.util.concurrent package, but I will leave the concurrency utilities out of this tutorial.

Here are a few examples of how to create a Queue instance:

Queue queueA = new LinkedList();

Queue queueB = new PriorityQueue();

Adding and Accessing Elements

To add elements to a Queue you call its add() method. This method is inherited from theCollection interface. Here are a few examples:

Queue queueA = new LinkedList();

 

queueA.add("element 1");

queueA.add("element 2");

queueA.add("element 3");

The order in which the elements added to the Queue are stored internally, depends on the implementation. The same is true for the order in which elements are retrieved from the queue. You should consult the JavaDoc's for more information about the specific Queue implementations.

You can peek at the element at the head of the queue without taking the element out of the queue. This is done via the element() method. Here is how that looks:

Object firstElement = queueA.element();

To take the first element out of the queue, you use the remove() method which is described later.

You can also iterate all elements of a queue, instead of just processing one at a time. Here is how that looks:

Queue queueA = new LinkedList();

 

queueA.add("element 0");

queueA.add("element 1");

queueA.add("element 2");

 

//access via Iterator

Iterator iterator = queueA.iterator();

while(iterator.hasNext(){

  String element = (String) iterator.next();

}

 

//access via new for-loop

for(Object object : queueA) {

    String element = (String) object;

}

When iterating the queue via its Iterator or via the for-loop (which also uses the Iterator behind the scene, the sequence in which the elements are iterated depends on the queue implementation.

Removing Elements

To remove elements from a queue, you call the remove() method. This method removes the element at the head of the queue. In most Queue implementations the head and tail of the queue are at opposite ends. It is possible, however, to implement the Queue interface so that the head and tail of the queue is in the same end. In that case you would have a stack.

Here is a remove example();

Object firstElement = queueA.remove();

 

Deque

The java.util.Deque interface is a subtype of the java.util.Queue interface. It represents a queue where you can insert and remove elements from both ends of the queue. Thus, "Deque" is short for "Double Ended Queue" and is pronounced "deck", like a deck of cards.

Deque Implementations

Being a Queue subtype all methods in the Queue and Collection interfaces are also available in the Deque interface.

Since Deque is an interface you need to instantiate a concrete implementation of the interface in order to use it. You can choose between the following Deque implementations in the Java Collections API:

  • java.util.ArrayDeque
  • java.util.LinkedList

LinkedList is a pretty standard deque / queue implementation.

ArrayDeque stores its elements internally in an array. If the number of elements exceeds the space in the array, a new array is allocated, and all elements moved over. In other words, theArrayDeque grows as needed, even if it stores its elements in an array.

There are also Queue implementations in the java.util.concurrent package, but I will leave the concurrency utilities out of this tutorial.

Here are a few examples of how to create a Deque instance:

Deque dequeA = new LinkedList();

Deque dequeB = new ArrayDeque();

Adding and Accessing Elements

To add elements to the tail of a Deque you call its add() method. You can also use theaddFirst() and addLast() methods, which add elements to the head and tail of the deque.

Deque dequeA = new LinkedList();

 

dequeA.add     ("element 1"); //add element at tail

dequeA.addFirst("element 2"); //add element at head

dequeA.addLast ("element 3"); //add element at tail

The order in which the elements added to the Deque are stored internally, depends on the implementation. The two implementations mentioned earlier both store the elements in the order (first or last) in which they are inserted.

You can peek at the element at the head of the queue without taking the element out of the queue. This is done via the element() method. You can also use the getFirst and getLast()methods, which return the first and last element in the Deque. Here is how that looks:

Object firstElement = dequeA.element();

Object firstElement = dequeA.getFirst();

Object lastElement  = dequeA.getLast();

Taking elements from the deque is covered later.

You can also iterate all elements of a deque, instead of just processing one at a time. Here is how that looks:

Deque dequeA = new LinkedList();

 

dequeA.add("element 0");

dequeA.add("element 1");

dequeA.add("element 2");

 

//access via Iterator

Iterator iterator = dequeA.iterator();

while(iterator.hasNext(){

  String element = (String) iterator.next();

}

 

//access via new for-loop

for(Object object : dequeA) {

    String element = (String) object;

}

When iterating the deque via its Iterator or via the for-loop (which also uses the Iterator behind the scene, the sequence in which the elements are iterated depends on the deque implementation.

Removing Elements

To remove elements from a deque, you call the remove(), removeFirst() and removeLastmethods. Here are a few examples:

Object firstElement = dequeA.remove();

Object firstElement = dequeA.removeFirst();

Object lastElement  = dequeA.removeLast();

 

Stack

The java.util.Stack class deserves a little explanation on its own. In the text on the Listinterface the Stack class is listed as an implementation. The typical use of a Stack is not as aList though.

A Stack is a data structure where you add elements to the "top" of the stack, and also remove elements from the top again. This is also referred to as the "Last In First Out (LIFO)" principle. In contrast, a Queue uses a "First In First Out (FIFO)" principle.

Stack stack = new Stack();

 

stack.push("1");

stack.push("2");

stack.push("3");

 

//look at top object ("3"), without taking it off the stack.   

Object objTop = stack.peek();

 

Object obj3 = stack.pop(); //the string "3" is at the top of the stack.

Object obj2 = stack.pop(); //the string "2" is at the top of the stack.

Object obj1 = stack.pop(); //the string "1" is at the top of the stack.  

The push() method pushes an object onto the top of the Stack.

The peek() method returns the object at the top of the Stack, but leaves the object on of theStack.

The pop() method returns the object at the top of the stack, and removes the object from theStack.

Searching the Stack

You can search for an object on the stack to get it's index, using the search() method. The object'sequals() method is called on every object on the Stack to determine if the searched-for object is present on the Stack. The index you get is the index from the top of the Stack, meaning the top element on the Stack has index 1.

Here is how you search a Stack for an object:

Stack stack = new Stack();

 

stack.push("1");

stack.push("2");

stack.push("3");

 

int index = stack.search("3");     //index = 3

 

hashCode() and equals()

The methods hashCode() and equals() play a distinct role in the objects you insert into Java collections. The specific contract rules of these two methods are best described in the JavaDoc. Here I will just tell you what role they play. What they are used for, so you know why their implementations are important.

equals()

equals() is used in most collections to determine if a collection contains a given element. For instance:

List list = new ArrayList();

list.add("123");

 

boolean contains123 = list.contains("123");

The ArrayList iterates all its elements and execute "123".equals(element) to determine if the element is equal to the parameter object "123". It is the String.equals() implementation that determines if two strings are equal.

The equals() method is also used when removing elements. For instance:

List list = new ArrayList();

list.add("123");

 

boolean removed = list.remove("123");

The ArrayList again iterates all its elements and execute "123".equals(element) to determine if the element is equal to the parameter object "123". The first element it finds that is equal to the given parameter "123" is removed.

As you can see, a proper implementation of .equals() is essential for your own classes to work well with the Java Collection classes. So how do you implement equals() "properly"?

So, when are two objects equal? That depends on your application, the classes, and what you are trying to do. For instance, let's say you are loading and processing Employee objects stored in a database. Here is a simple example of such an Employee class:

public class Employee {

    protected long   employeeId;

    protected String firstName;

    protected String lastName;

}

You could decide that two Employee objects are equal to each other if just their employeeId's are equal. Or, you could decide that all fields must be equal - both employeeId, firstName andlastName. Here are two example implementation of equals() matching these criterias:

public class Employee {

  ...

  public boolean equals(Object o){

    if(o == null)                return false;

    if(!(o instanceof) Employee) return false;

 

    Employee other = (Employee) o;

    return this.employeeId == other.employeeId;

  }

}

public class Employee {

  ...

  public boolean equals(Object o){

    if(o == null)                return false;

    if(!(o instanceof) Employee) return false;

 

    Employee other = (Employee) o;

    if(this.employeeId != other.employeeId)      return false;

    if(! this.firstName.equals(other.firstName)) return false;

    if(! this.lastName.equals(other.lastName))   return false;

 

    return true;

  }

}

Which of these two implementations is "proper" depends on what you need to do. Sometimes you need to lookup an Employee object from a cache. In that case perhaps all you need is for theemployeeId to be equal. In other cases you may need more than that - for instance to determine if a copy of an Employee object has changed from the original.

hashCode()

The hashCode() method of objects is used when you insert them into a HashTable, HashMap orHashSet. If you do not know the theory of how a hashtable works internally, you can read abouthastables on Wikipedia.org.

When inserting an object into a hastable you use a key. The hash code of this key is calculated, and used to determine where to store the object internally. When you need to lookup an object in a hashtable you also use a key. The hash code of this key is calculated and used to determine where to search for the object.

The hash code only points to a certain "area" (or list, bucket etc) internally. Since different key objects could potentially have the same hash code, the hash code itself is no guarantee that the right key is found. The hashtable then iterates this area (all keys with the same hash code) and uses the key's equals() method to find the right key. Once the right key is found, the object stored for that key is returned.

So, as you can see, a combination of the hashCode() and equals() methods are used when storing and when looking up objects in a hashtable.

Here are two rules that are good to know about implementing the hashCode() method in your own classes, if the hashtables in the Java Collections API are to work correctly:

  1. If object1 and object2 are equal according to their equals() method, they must also have the same hash code.
  2. If object1 and object2 have the same hash code, they do NOT have to be equal too.

In shorter words:

  1. If equal, then same hash codes too.
  2. Same hash codes no guarantee of being equal.

Here are two example implementation of the hashCode() method matching the equals()methods shown earlier:

public class Employee {

  protected long   employeeId;

  protected String firstName;

  protected String lastName;

 

  public int hashCode(){

    return (int) this.employeeId;

  }

}

public class Employee {

    protected long   employeeId;

    protected String firstName;

    protected String lastName;

 

  public int hashCode(Object o){

    return (int)this.employeeId *

                firstName.hashCode() *

                lastName.hashCode();

  }

}

Notice, that if two Employee objects are equal, they will also have the same hash code. But, as is especially easy to see in the first example, two Employee objects can be not equal, and still have the same hash code.

In both examples the hash code is the employeeId is rounded down to an int. That means that many employee id's could result in the same hash code, but these Employee objects would still not be equal, since they don't have the same employee id.

posted @ 2012-11-19 08:52  daveztong  阅读(2397)  评论(0编辑  收藏  举报