黑马程序员_Java基础:集合总结

  ------- android培训java培训、期待与您交流! ----------

一、集合概念

  相信大家都知道,java是一门面向对象的编程语言,而对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储进行存储,集合就是存储对象最常用的一种方式,我们可以把集合看成是一个容器。

  同样,数组也是一种容器,那么集合和它有什么不同?

  1.数组虽然也可以存储对象,但是长度是固定的,而集合长度是可变的。
  2.数组中可以存储基本数据类型数据,集合只能存储对象。
  3.数组中储存的基本数据类型数据或对象要类型相同,而集合可以存储不同类型的对象。

  所以,当事先不知道要存放数据的个数,或者需要一种比数组下标存取机制更灵活的方法时,就需要用到集合类。

二、集合的构成与分类

下面是一张网上找到的图片,对于集合中的类和接口有比较清晰的表现:

黑马程序员_Java基础:集合总结

  1.Collection接口:

  Collection是最基本的集合接口,一个Collection代表一组对象,即Collection的元素(Elements)。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java JDK不提供直接继承自Collection的类,Java JDK提供的类都是继承自Collection的”子接口“如List和Set。

  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,另一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。其实后一个构造函数就是复制一个Collection。

  那么如何取出Collection中的元素?无论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代器,使用该迭代器即可逐一访问Collection中每一个元素。典型的用法如下:

     Iterator it = collection.iterator(); // 获得一个迭代器。
    while(it.hasNext()) { //通过迭代器判断集合中是否还有元素。
      Object obj = it.next(); // 得到下一个元素,类型为Object。
    }

  Collection定义了集合框架的共性功能(方法):
  ①添加:add(e);  addAll(collection);
  ②删除:remove(e);  removeAll(collection);  clear();
  ③判断:contains(e);  isEmpty();
  ④获取:iterator();  size();
  ⑤获取交集:retainAll();
  ⑥集合变数组:toArray();

  Collection接口派生的两个接口是List和Set。

  (1)List接口

  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。List允许有相同的元素,List集合判断元素是否相同是通过对象的equals()方法。

  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素, 还能向前或向后遍历。

  实现List接口的常用类有ArrayList,LinkedList,Vector。

  ArrayList类:

  ArrayList实现了可变大小的数组。它允许所有元素,包括null。

  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组(数组结构)的大小。这个容量可随着不断添加新元素而自动增加。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。

  注意ArrayListList是非同步的,如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了该列表,则它必须保持外部同步。一种解决方法是在创建ArrayList时构造一个同步的List来包装它: 

     List list = Collections.synchronizedList(new ArrayList(...));  

  数组结构(每个元素都有角标):

黑马程序员_Java基础:集合总结

  LinkedList类:

  LinkedList实现了List接口,存储数据的方式是双链表,允许null元素。此外LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。

  注意LinkedList是非同步的,如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须保持外部同步。一种解决方法是在创建LinkedList时构造一个同步的List来包装它:

   List list = Collections.synchronizedList(new LinkedList(...));

  双链表结构(每个元素只知道前后的元素):

黑马程序员_Java基础:集合总结

  Arraylist和Linkedlist的不同处:
  ①ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  ②对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
  ③对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

  Vector类:

  Vector非常类似ArrayList,也是数组结构存储数据,但是Vector是同步的。由Vector创建的Iterator,虽然和 ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了 Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出 ConcurrentModificationException,因此必须捕获该异常。同样,当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。另外,Vector还有特有的取出元素的方式,通过它的elements()获取Enumeration枚举来得到元素,实际上Enumeration和Iterator功能是一样的。

  Vector与ArrayList不同之处是:
  ①Vector是线程同步的,所以它也是线程安全的,而Arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。
  ②如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%.如过在集合中使用数据量比较大的数据,用vector有一定的优势。
  ③Vector还有特有的取出元素的方式,Enumeration枚举。

  (2)Set接口  

  Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。

  另外要注意,必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object1.equals(Object2)=true将导致一些问题。

  实现Set接口的常用类有HashSet和TreeSet。

  HashSet类:

  Hashset存储数据的结构是哈希表,它不保证 set 的迭代顺序,特别是它不保证该顺序恒久不变。也就是说,HashSet中的元素是无序的。它是通过元素的hashCode()和equals()方法来保证元素的唯一性。如果元素的hashCode值不同,则不需调用equals()进行比较。如果元素的HashCode值相同,才会调用equals()判断两个元素是否相同。基于这种唯一性标准,HashSet中判断元素是否存在,以及删除等操作,依赖的方法也是元素的hashCode()和equals()方法。

  注意HashSet是非同步的,如果多个线程同时访问一个HashSet,而其中至少一个线程修改了该 set,那么它必须 保持外部同步。一种解决方法是在创建HashSet时构造一个同步的Set来包装它:

     Set s = Collections.synchronizedSet(new HashSet(...));

  哈希表结构(当元素比较hashCode不同时,元素处于不同区域;当元素比较hashCode相同,equals比较不同时,元素会在同个区域):

黑马程序员_Java基础:集合总结

  TreeSet类:

  TreeSet存储数据的结果是二叉树(红黑树),基于实现NavigableSet接口,它是使用元素的自然顺序对元素进行排序(当元素自身具有比较性时),或者根据创建 set 时提供的 Comparator进行排序(当元素自身不具备比较性,或者比较性不合需求时),具体取决于使用的构造方法。也就是说,TreeSet中的元素是有序的。它是通过元素的compareTo()或者构造时使用的比较器Comparator中的compare()方法来保证元素的唯一性。

  注意TreeSet是非同步的,如果多个线程同时访问一个TreeSet,而其中至少一个线程修改了该 set,那么它必须 外部同步。一种解决方法是在创建TreeSet时构造一个同步的Set来包装它:

     SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...)); 

  二叉树结构(比较后,小的元素在左子树,大的元素在右子树):

  黑马程序员_Java基础:集合总结

  2.Map接口

  Map没有继承Collection接口,Map提供key(键)到value(值)的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。Map接口提供3种集合的视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。Map集合没有直接取出元素的方法,想获取Map集合中的元素,需要先用entrySet()方法获取键-值映射关系的Set集合,或者使用keySet()方法获取键的Set集合。

  Map集合的共性功能(方法):
  ①添加:put(K key, V value);  putAll(Map<? extends K,? extends V> m);
  ②删除: remove(Object key);  clear();
  ③判断:containsValue(Object value);  containsKey(Object key);  isEmpty();
  ④获取:get(Object key);  size();  values();  entrySet();  keySet();

  实现Map接口的常用类有HashMap,TreeMap。

  HashMap类:

  基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用null键和null值(替代Hashtable)。其键集上的对象是通过元素的hashCode()和equals()方法来保证元素的唯一性。如果元素的hashCode值不同,则不需调用equals()进行比较。如果元素的HashCode值相同,才会调用equals()判断两个元素是否相同。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

  注意HashMap是非同步的,如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。一种解决方法是在创建HashMap时构造一个同步的Map来包装它:

    Map m = Collections.synchronizedMap(new HashMap(...));

  TreeMap类:

  基于二叉树的 Map 接口的实现。它所包含的映射关系是有序的(以键为标准),其键的排序方式和TreeSet中的元素相同。

  注意TreeMap是非同步的,如果多个线程同时访问一个映射,并且其中至少一个线程从结构上修改了该映射,则其必须外部同步。一种解决方法是在创建TreeMap时构造一个同步的Map来包装它:

     SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));

三、集合的应用

1.例子一(将自定义对象作为元素存到ArrayList集合中,并去除重复元素):

 import java.util.ArrayList;
import java.util.Iterator; /*
将自定义对象作为元素存到ArrayList集合中,并去除重复元素。
比如:存人对象。同姓名同年龄,视为同一个人。为重复元素。 思路:
1,对人描述,将数据封装进人对象。
2,定义容器,将人存入。
3,取出。 List集合判断元素是否相同,依据是元素的equals方法。
*/
class Person {
private String name;
private int age;
Person(String name,int age) {
this.name = name;
this.age = age;
} public boolean equals(Object obj) {
if(!(obj instanceof Person))
return false;
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
} public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Person> al = new ArrayList<Person>(); //使用泛型指定集合元素类型。 al.add(new Person("lisi01",30));
al.add(new Person("lisi02",32));
al.add(new Person("lisi02",32));
al.add(new Person("lisi04",35));
al.add(new Person("lisi03",33));
al.add(new Person("lisi04",35)); al = singleElement(al);
System.out.println("remove 03 :"+al.remove(new Person("lisi03",33)));//remove方法底层也是依赖于元素的equals方法。 Iterator<Person> it = al.iterator();
while(it.hasNext()) { //判断集合中是否还有元素。
Person p = it.next();
System.out.println(p.getName()+"::"+p.getAge());
}
} // 去掉重复元素。
public static ArrayList<Person> singleElement(ArrayList<Person> al) {
//定义一个临时容器。
ArrayList<Person> newAl = new ArrayList<Person>();
Iterator<Person> it = al.iterator();
while(it.hasNext()) {
Person p = it.next(); if(!newAl.contains(p))
newAl.add(p);
}
return newAl;
}
}

运行结果为:

remove 03 :true
lisi01::30
lisi02::32
lisi04::35

2.例子二(使用LinkList模拟一个堆栈或者队列数据结构):

 import java.util.LinkedList;

 /*
使用LinkList模拟一个堆栈或者队列数据结构。 堆栈:先进后出,如同一个杯子。
队列:先进先出,如同一个水管。
*/
class DuiLie {
private LinkedList link;
private LinkedList link_2; DuiLie() {
link = new LinkedList();
link_2 = new LinkedList();
} public void myAdd(Object obj) {
link.addFirst(obj);
link_2.addFirst(obj);
} public Object myGet() {
return link.removeFirst();
} public Object myGet_2() {
return link.removeLast();
} public boolean isNull() {
if (link.isEmpty()) {
try {
return link.isEmpty();
}
finally {
link.addAll(link_2);
}
}
return link.isEmpty();
}
} public class LinkedListTest {
public static void main(String[] args) {
DuiLie dl = new DuiLie(); dl.myAdd("java01");
dl.myAdd("java02");
dl.myAdd("java03");
dl.myAdd("java04"); //堆栈形式;先进后出
while (!dl.isNull())
System.out.println(dl.myGet());
System.out.println(); //队列形式:先进先出
while (!dl.isNull())
System.out.println(dl.myGet_2());
}
}

运行结果为:

java04
java03
java02
java01

java01
java02
java03
java04

3.例子三(Vector枚举):

 import java.util.Enumeration;
import java.util.Vector; /*
枚举就是Vector特有的取出方式。
发现枚举和迭代器很像。
其实枚举和迭代是一样的。 因为枚举的名称以及方法的名称都过长。
所以被迭代器取代了。
*/
public class VectorDemo {
public static void main(String[] args) {
Vector<String> v = new Vector<String>(); v.add("java01");
v.add("java02");
v.add("java03");
v.add("java04"); Enumeration<String> en = v.elements();
while(en.hasMoreElements()) { //判断集合中是否还有元素。
System.out.println(en.nextElement());
}
}
}

运行结果为:

java01
java02
java03
java04

4.例子四(往HashSet集合中存入自定义对象):

 import java.util.HashSet;
import java.util.Iterator; /*
往HashSet集合中存入自定义对象。
以人为例:
姓名和年龄相同视为同一个人,重复元素。
*/
public class HashSetTest {
public static void main(String[] args) {
HashSet<Person> hs = new HashSet<Person>(); hs.add(new Person("a1",11));
hs.add(new Person("a2",12));
hs.add(new Person("a3",13));
hs.add(new Person("a2",12));
// 查看是否包含指定元素。
System.out.println("a1:"+hs.contains(new Person("a2",12)));
// 删除一个元素。
System.out.println(hs.remove(new Person("a3",13))); Iterator<Person> it = hs.iterator();
while (it.hasNext()) {
Person p = it.next();
System.out.println(p.getName()+"..."+p.getAge());
}
}
} class Person {
private String name;
private int age; Person(String name,int age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public int getAge() {
return age;
} public int hashCode() {
return name.hashCode()+age*39;
} public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
}

运行结果为:

a1:true
true
a2...12
a1...11

5.例子五(使用TreeSet添加字符串,字符串按长度排序):

 import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet; /*
按照字符串长度排序。 字符串本身具备比较性,但是它的比较方式不是所需要的。
这时只能使用比较器。
*/
public class TreeSetTest {
public static void main(String[] args) {
// 元素本身的比较性不合需求,构造时加上比较器。
TreeSet<String> ts = new TreeSet<String>(new StrLenCompare()); ts.add("abcd");
ts.add("cc");
ts.add("cba");
ts.add("aaa");
ts.add("z");
ts.add("hahaha"); Iterator<String> it = ts.iterator();
while (it.hasNext())
{
String s = (String)it.next();
System.out.println(s+"...length..."+s.length());
}
}
} //比较器比较内容。
class StrLenCompare implements Comparator<String> {
public int compare(String s1,String s2) {
int num = new Integer(s1.length()).compareTo(new Integer(s2.length()));
if (num==0)
return s1.compareTo(s2);
return num;
}
}

运行结果为:

z...length...1
cc...length...2
aaa...length...3
cba...length...3
abcd...length...4
hahaha...length...6

6.例子六(使用HashMap添加不重复的元素(映射关系)):

 import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set; /*
每一个学生都与对应的归属地。
学生Student,地址String。
学生属性:姓名,年龄。
注意:姓名和年龄相同的视为同一个学生。
保证学生的唯一性。 思路:
1,描述学生。
2,因为学生和地址存在映射关系。定义Map容器,将学生作为键,地址作为值存入。
3,获取Map集合中的元素。
*/
class Student implements Comparable<Student> {
private String name;
private int age;
Student(String name,int age) {
this.name = name;
this.age = age;
} public int compareTo(Student s) {
int num = new Integer(this.age).compareTo(new Integer(s.age));
if (num==0) {
return this.name.compareTo(s.name);
}
return num;
} public int hashCode() {
return this.name.hashCode()+this.age*34;
} public boolean equals(Object obj) { //这里重写的是父类Object的equals方法,参数类型必须Object,否则重写失败。
if(!(obj instanceof Student))
throw new ClassCastException("类型不匹配");
Student s = (Student)obj;
return this.name.equals(s.name) && this.age==s.age;
} public String getName() {
return name;
} public int getAge() {
return age;
} public String toString() {
return name+":::"+age;
}
} public class HashMapTest {
public static void main(String[] args) {
HashMap<Student,String> hm = new HashMap<Student,String>(); hm.put(new Student("lisi1",21),"beijing");
hm.put(new Student("lisi1",21),"tianjin");
hm.put(new Student("lisi2",22),"shanghai");
hm.put(new Student("lisi3",23),"nanjing");
hm.put(new Student("lisi4",24),"wuhan"); //第一种取出方式:keySet
Set<Student> keySet = hm.keySet();
Iterator<Student> it = keySet.iterator(); while (it.hasNext()) {
Student s = it.next();
String addr = hm.get(s);
System.out.println(s+"..."+addr);
}
System.out.println(); /* //第二种取出方式:entrySet
Set<Map.Entry<Student,String>> entrySet = hm.entrySet();
Iterator<Map.Entry<Student,String>> iter = entrySet.iterator(); while (iter.hasNext()) {
Map.Entry<Student,String> me = iter.next();
Student s = me.getKey();
String addr = me.getValue();
System.out.println(s+"....."+addr);
}*/
}
}

运行结果为:

lisi3:::23...nanjing
lisi1:::21...tianjin
lisi2:::22...shanghai
lisi4:::24...wuhan

7.例子七(使用TreeMap添加有序的元素(映射关系)):

 import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap; /*
需求:每一个学生都与对应的归属地。
学生Student,地址String。
学生属性:姓名,年龄。
对学生对象的年龄进行升序排序。 因为数据是以键值对形式存在的。
所以要是哟昂可以排序的Map集合,TreeMap。
*/
class StuNameComparator implements Comparator<Student> {
public int compare(Student s1,Student s2) {
int num = s1.getName().compareTo(s2.getName());
if (num==0)
return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));
return num;
}
} public class TreeMapTest {
public static void main(String[] args) {
//因为Student本身定义有比较性,可以直接满足需求。如果是按姓名排序,则需要加上StuNameComparator比较器。
TreeMap<Student,String> tm = new TreeMap<Student,String>(/*new StuNameComparator()*/); tm.put(new Student("blisi3",23),"nanjing");
tm.put(new Student("lisi1",21),"beijing");
tm.put(new Student("alisi14",24),"wuhan");
tm.put(new Student("lisi1",21),"tianjin");
tm.put(new Student("alisi11",21),"tianjin");
tm.put(new Student("alisi12",22),"shanghai"); Set<Map.Entry<Student,String>> entrySet = tm.entrySet();
Iterator<Map.Entry<Student,String>> it = entrySet.iterator();
while (it.hasNext()) {
Map.Entry<Student,String> me = it.next();
Student s = me.getKey();
String addr = me.getValue();
System.out.println(s+"..."+addr);
}
}
} class Student implements Comparable<Student> {
private String name;
private int age;
Student(String name,int age) {
this.name = name;
this.age = age;
} public int compareTo(Student s) {
int num = new Integer(this.age).compareTo(new Integer(s.age));
if (num == 0) {
return this.name.compareTo(s.name);
}
return num;
} public int hashCode() {
return this.name.hashCode()+this.age*34;
} public boolean equals(Object obj) {
if(!(obj instanceof Student))
throw new ClassCastException("类型错误");
Student s = (Student)obj;
return s.name.equals(this.name) && this.age == s.age;
} public String getName() {
return name;
} public int getAge() {
return age;
} public String toString() {
return name+":::"+age;
}
}

运行结果为:

alisi11:::21...tianjin
lisi1:::21...tianjin
alisi12:::22...shanghai
blisi3:::23...nanjing
alisi14:::24...wuhan

四、总结

  如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
  如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类或者在类意外加上同步。
  要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
  尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

上一篇:Python的设计哲学


下一篇:重写TextField Rect 改变显示位置