看到集合大家可能第一时间想到的是数学中的集合---由一个或多个确定元素所构成的整体。两者之间其实也没有多大的区别,但因为java的编程思想是面向对象的,因此java中的集合是一种工具类,就像是容器,用来存放java类的对象。集合和数组的区别在于数组长度是固定的,且数组里面的数据是基本数据类型的。而集合的长度可变,且里面的数据可以是对象类型,集合的操作比数组更加灵活。

下面的图片是从网上的找来的,虽然仅从图上来看,集合的实现类很少,但其实java中集合的实现类有很多个,因为我们一般用得最多的就是下面这几种,因此我们就来先了解集合的这几种实现类

一,Collection接口

Collection接口没有直接的实现子类,通常情况下不被直接使用,而是通过子接口实现(即List接口或者是Set接口)。Collection还继承自Iterable类,在Iterable类里面有迭代器(Iterator)--迭代器对象主要用于遍历Collection集合中的元素,其本身并不存放对象。因此只要是实现了Collection接口的集合类都有一个iterator()方法,用来返回一个迭代器。在java.util包中提供了我们所需要的一些集合类,如Set,List和Map。在遍历集合时,我们也可以使用加强for循环。但是不能使用普通for循环,因为普通for循环需要得到索引才能输出元素,而对于Collection这个接口来说,是没有获取索引的方法的,因此不使用普通的for循环来进行遍历。在遍历集合前我们可以先在集合类里面添加对象或者是元素。对于Collection集合来说,添加元素的方法为add()而与Collection接口并排的Map接口添加元素的方法为put()。Collection作为接口,不能被实例化,因为ArrayList是其间接实现类,所以我们可以通过ArrayList类实例化Collection接口(多态)。 

 Collection接口实例化后常用的方法如下:

  • add()---添加单个元素
  • add()---添加另外一个集合对象
  • remove()---删除指定元素
  • removeAll()---从指定集合中删除(移除)包含在另外一个集合中的元素,返回值boolean(即,我有你居然有,删除相同元素
  • retainAll()---仅保留该集合中同包含在指定中的对象(即你有我也有,保留相同元素
  • contains()---查找单个元素是否存在,返回值boolean
  • containsAll()---查找在该集合中是否存在指定集合中的所有对象,返回值boolean
  • toArray()---将一个集合转变成数组【参数必须为数组类型的实例,并且已经被实例化。】
  • size()---获取集合中元素的个数(即得到集合的长度)
  • isEmpty()---判断集合是否为空
  • clear()---清空集合内的所有元素

 add()和addAll()方法

现在来我们创建Collection接口的实例化对象,并向该集合添加数据类型和对象。

 使用加强for循环遍历Collection集合中的元素:

 相信大家也看到了我们使用的是Object类的变量来接收集合中的元素,之所以使用Object类是因为集合就像个容器一样,啥都可以往里面放(即上面类型的元素都有),在我们不知道从集合(泛型集合)中取出来的元素是什么类型时,就可以使用Object类的变量来接收。在java中Object类是所有子类的超类。当然了,我们也可以指定集合的类型,也就可以使用确定的数据类型变量来接收集合中的元素。如下:

 我们在Collection接口名的后面加上双尖括号,里面写上我们要存放的数据类型,于是我们可以看到第七行代码爆红了,那是因为我们添加的是对象,而不是String类型的数据。提示如下:

The method add(String) in the type Collection<String> is not applicable for the arguments (AAA)

 我们虽然指定了集合的add()方法的添加数据类型,但是我们可以使用addAll()方法添加其他集合对象,当然了,遍历时的接收变量类型就要改为Objet类。如下:

remove与removeAll方法 

接下来我们remove与removeAll方法在Collection接口实现类中的使用:

 从上图中我们可以看到,因为对collection使用了remove方法删除了元素“张三”,此时collection中的元素为[托马斯沃森,马丁奥德斯基,尼古拉斯沃斯,666,true],之后使用了removeAll方法删除了在collection与collection01中都出现的元素666和true,因此结果就为上图所示。

retainAll方法

retainAll方法的作用和上面removeAll方法刚刚好相反,该方法保留了两个集合中都有的元素。

contains和containsAll方法

 接下来我们看看contains和containsAll方法的使用。 在下图中,我们先执行了remove操作将‘张三’删除,之后才使用contains方法判断是否有“张三”这个元素,因此返回为false。

collection集合中的元素不包含collection01集合中的对象,因此返回值为false。

import java.util.*;
public class ForCollection01 {
	public static void main(String[] args) {
	Collection<String> collection=new ArrayList();
	Collection collection01=new ArrayList();
	collection.add("托马斯沃森");   	//在只支持添加String数据类型的集合collection中添加String数据类型的字符串
	collection.add("马丁奥德斯基");
	collection.add("尼古拉斯沃斯");
	collection.add("张三");
	collection01.add(true);		    //在支持泛型的集合collection01中添加布尔型的数据类型
	collection01.add(666);		    //在支持泛型的集合collection01中添加整型的数据类型
	collection.addAll(collection01);   //在collection集合中添加另外一个集合collection01
	
	collection.remove("张三");      //删除指定元素
	collection.removeAll(collection01);    //保留collection中独有的元素
	System.out.println(collection.contains("张三"));
	System.out.println("collection:"+collection);
	System.out.println("collection01:"+collection01);
	System.out.println(collection.containsAll(collection01));
	collection.retainAll(collection01);    //保留两个集合中都有的元素
	for(Object  obj:collection) {     //使用Iterator遍历collection
	System.out.println(obj);
	}
	
}
}
class AAA{
	String name;
	int age;
	public AAA(String name,int age) {
		this.name=name;
		this.age=age;
	}
	public String toString() {
		return "姓名是:"+name+",年龄为:"+age;
	}

1,List接口

List接口继承自Collection接口,因此也可以使用迭代器来进行遍历集合中的元素,在Collection中可以使用的方法在List接口中的实现类中都可以使用。而且List接口的实现类也添加了一些自己的常用方法,如下:

  • set()---修改集合中的元素
  • get()---得到对应索引号的元素

set和get方法 

List接口常用实现有ArrayList与LinkedList。List集合类似于数组,可以使用索引号访问集合中的元素,因此可以使用普通for循环来遍历元素,而这个在其父类Collection中是没有的。  

接下来我们使用代码来查看两个方法具体的实现效果:

ArrayList实现类

该类实现了可变数组,允许所有的元素,包括null,通过索引位置对数组进行快速的随机访问

缺点:相对于LinkedList,ArrayList指定索引位置的对象插入和删除速度比较慢。ArrayList的底层结构是可变数组。基本上对List集合的使用时用来查询的,因此大部分选择的是ArrayList

案例一:使用ArrayList创建集合,添加姓名,性别,出生日期,并分别打印输出。

首先创建集合

 接着为了使main函数看起来清爽(干净)我们就在main函数外写一个处理list集合的方法。

import java.util.*;
public class ForArrayList01 {
public static void main(String[] args) {
	List arrayList=new ArrayList();
	arrayList.add("张三,男,2000-1-20");
	arrayList.add("李四,男,2010-11-10");
	arrayList.add("王五,男,2001-11-30");
	System.out.println(arrayList.size());
	getElement(arrayList);
}
public static List  getElement(List list) {
	for(int i=0;i<list.size();i++) {   //使用for循环遍历列表集合
		String str=(String) list.get(i);  //将遍历到的放入str中
		String[] split=str.split(",");    //将得到的字符串根据逗号分隔
		System.out.println("name="+split[0]+",sex="+split[1]+",birthday="+split[2]);
	}
	return list;
}
}

 如上我们通过get,split等方法将一个集合转换成数组,并将里面的字符串分开打印输出。

ArrayList案例

现在我们有三本书,使用list集合将三本书添加进去,将书价格低的往前排输出 

 代码:

import java.util.*;
public class TestList {
public static void main(String[] args) {
	List list=new ArrayList();
	list.add(new Book("水浒传",55,"施耐庵"));
	list.add(new Book("西游记",66,"吴承恩"));
	list.add(new Book("红楼梦",44,"曹雪芹"));
	sortArrayList(list);   
	for(Object o:list) {   //使用加强for循环将对象打印出来
		System.out.println(o);
	}
}
public static void  sortArrayList(List list) {
	for(int i=0;i<list.size();i++) {
		for(int j=0;j<list.size()-1;j++) {
			Book book1=(Book) list.get(j);
			Book book2=(Book) list.get(j+1);
			if(book1.price >book2.price) {
				list.set(j, book2);    //价格小的向前排
				list.set(j+1, book1);
			}
		}
	}
}
}

class Book{
	String name;
	double price;
	//private double price;
	 String author;
	/* public void setPrice(double price) {
		 this.price=price;
	 }
	 public double getPrice() {
		 return price;
	 }*/
	public Book() {}
	public Book(String name,double price,String author) {
		this.name=name;
		this.price=price;
		this.author=author;
	}
	public String toString() {
		return "书名:"+name+"   价格:"+price+"   作者:"+author;
	}
}

LinkedList实现类

该类采用链表结构保存对象,其优点就是ArrayList的缺点,LinkedList便于对象的插入和删除,实现集合的效率更高。LinkedList的缺点是随机访问集合中元素效率低。其实一般情况来说,我们在平常的时候使用是不用在意这个的。两者使用的方法都一样的(因为都继承自List)。只是底层结构不同:LinkedList实现了双向链表---维护了两个属性first和last,分别指向首节点和尾结点。每个节点对象里面又维护了三个属性prev,next和item。其中通过prev指向前一个元素,通过next指向下一个元素,最终实现双向链表的结构。和双端对列特点,线程不安全,没有实现同步。那么双向链表用代码是怎么实现的呢?下面我们使用一个简单的程序代码来实现一个简单的双向链表:

public class TestLinkedList {
	public static void main(String[] args) {
		Node node1=new Node("node1");   //创建第一个节点
		Node node2=new Node("node2");   //创建第二个节点
		Node node3=new Node("node3");   //创建第三个节点
        //正向链表
		node1.next=node2;                //使第一个节点指向第二个节点
		node2.next=node3;                //使第二个节点指向第三个节点
        //反向链表        
		node3.prev=node2;                 //使第三个节点指向第二个节点
		node2.prev=node1;                 //使第二个节点指向第一个节点
        //创建头结点和尾结点
		Node first=node1;
		Node last=node3;
		System.out.println("========正向打印======");
       //正向打印就是从第一个节点到最后一个节点,按正序输出打印,
		while(true) {                      //设置一个循环语句,用来遍历链表
			if(first==null) {              //给定一个判断条件,当条件不满足时,while循环终止
				break;
			}
			System.out.println(first);      //打印我们的头结点(头指针),
			first=first.next;
//随着遍历,头指针会指向下一个节点,直到返回值为null(即没有元素时,停止遍历)
		}
		
		System.out.println("========反向打印======");
//反向打印的原理和正向打印刚好相反。
		while(true) {
			if(last==null) {
				break;
			}
			System.out.println(last);
			last=last.prev;
//随着遍历,头指针会指向上一个节点,直到返回值为null(即没有元素时,停止遍历)
		}
		
		System.out.println("===在node1与node2之间插入node12节点===");
		Node  node12=new Node("node12");
		node12.next=node2;
		node12.prev=node1;
		node2.prev=node12;
		node1.next=node12;
	    first =node1;     //因为之前遍历过链表,指针指向的位置下一个元素为null,因此我们需要重新将指针指向第一个节点
	    while(true) {
	    	if(first==null) {
	    		break;
	    	}
	    	System.out.println(first);
	    	first=first.next;
	    }
		
	}
}
//创建Node节点对象,包含有三个属性,item(对象),prev(指针指向上一个节点),next(指针指向下一个节点)
class Node{
	Object item;
	public Node next;
	public Node prev;
	public Node(Object name) {this.item=name;}
	public String toString() {
		return "Node's name is   "+item;
	}
}

从上面代码实现我们可以看出链表的对象添加和删除都比较方便。添加只需要当前对象的prev指向上一个对象,next指向下一个对象,以及上一个对象的next指向当前对象,下一个对象的prev指向当前对象,四行代码即可。

因此在LinkedList中的add不仅可以在集合的最后添加元素(对象),也可以在指定索引号的位置添加add(int,Object),如下:

Vector(向量,矢量)实现类

vector底层结构为可变数组,是在jdk1.0的时候出现的,vector是线程同步的,即线程安全,vector类的操作方法带有synchronized(同步的),在开发中需要线程同步时,考虑使用vector,但是其同步效率不高,因为每次都要进行安全核验。

2,Set接口

Set接口不同于List接口,它是无序的(即元素放入和取出的顺序不一致,但是只要一取出,取出的顺序就是不变的),且Set接口没有索引,不允许有重复的元素。JDK AP中Set接口的实现类主要有:HashSet,TreeSet。因为元素不能重复,因此add方法在添加时,会有一个boolean值的返回,如果添加成功了就返回true,反之false。我们主要来认识HashSet

HashSet实现类

HashSet的add方法底层代码(源码)中有hashCode方法的使用,该方法主要是用于无序且不能有元素重复的集合,我们的集合会根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,使得元素看起来是以插入顺序保存的(这就是为什么元素只要一取出就不变换位置的原因)在HashSet中添加两个属性值相同的对象是可以的,因为两个对象所在的堆不同,只是值相同而已,但是有些时候我们想要这两个属性值相同的对象为同一个元素的话,就需要去重写equals方法和hashCode方法,如下:

 重写两个方法后,属性值相同的两个对象只会出现一个。【重写hash值后,如果对象的属性值相同,则返回相同的hash值

 这个有点难,我们不用一行一行的敲出来,可以鼠标右击,找到‘source’之后点击,找的‘generate hashCode() and equals()’选择即可快速生成重写方法。

 HashMap添加元素【hash()+equals()

  1. 先得到hash值,会转成索引值。
  2. 找到存储数据表的table,看这个索引位置是否已经存放有元素,如果没有,直接加入,如果有则调用equals方法进行比较,如果相同,就放弃添加,如果不相同,就添加到最后。
import java.awt.*;
import java.util.*;
public class TestHashSet {
	public static void main(String[] args) {
		Set set=new HashSet();
		set.add(new Employee("马丁奥德斯基",63));
		set.add(new Employee("尼古拉斯沃斯",87));
		set.add(new Employee("马丁奥德斯基",63));
		System.out.println(set);
	}
}
class Employee{
	private String name;
	private int age;
	public Employee(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	} 
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Employee [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Employee other = (Employee) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
}

 hashSet的底层是hashMap,而hashMap底层是数组+链表+红黑树。

TreeSet实现类

TreeSet类不仅实现了Set接口,还实现了java.util.SortedSet接口,从而保证了在遍历集合时按照递增的顺序获得对象。

SortedSet新增的方法如下:

  1. first()---返回在集合中的第一个元素(对象)
  2. last()---返回在集合中的最后一个元素(对象)
  3. headSet(Object)---截取位于该对象前的所有对象(不包含该对象),重新生成一个集合并返回
  4. tailSet(Object)---截取位于该对象后的所有对象(包含该对象),重新生成一个集合并返回
  5. subSet(Object1,Object2)---截取在集合中排序位于Object1对象和Object2对象之间的所有对象(不包括Object2对象),重新生成一个集合并返回。
  6. comparetor()---对集合采用的比较器,返回值Comparator类型,未采用比较器则返回null

import java.util.*;
public class ForTreeSet {
	@SuppressWarnings("all")   //抑制java警告
	public static void main(String[] args) {
		TreeSet  treeSet=new TreeSet();
		for(int i=0;i<=20;i++) {
			treeSet.add(i);
		}		
		System.out.println(treeSet);
		System.out.println("first()---返回在集合中的第一个元素(对象)");
		System.out.println(treeSet.first());
	    System.out.println("last()---返回在集合中的最后一个元素(对象)");
	    System.out.println(treeSet.last());
	    System.out.println("headSet(18)---截取位于18前的所有对象(不包含18),重新生成一个集合并返回");
	    System.out.println(treeSet.headSet(18));
	    System.out.println("tailSet(18)---截取位于18后的所有对象(包含18),重新生成一个集合并返回");
	    System.out.println(treeSet.tailSet(18));
	    System.out.println("subSet(18,20)---截取在集合中排序位于18和20之间的所有对象(不包括20),重新生成一个集合并返回。");
	    System.out.println(treeSet.subSet(18,20));
	}
}

二,Map接口

Map接口与Collection接口并列存在,用于保存具有映射关系的数据--key-value(双链),Map中的key和value可以是任何引用类型的数据,都对封装到HashMap$Node对象中。在Map中的key不允许重复可以为null--但是只能有一个null,其值value可以重复,也可以为null,并且可以是多个值都是null。常用String类作为Map的key,key和value之间存在单向的一对一关系,通过指定的key找到相对应的value。在Map接口的实现类中put方法既可以是添加元素,也可以是对元素的修改替换---当出现相同(重复)的key时。

Map接口中的常用方法如下:

  1.  put(key,value)---向集合中添加指定的key与value的映射关系
  2. containsKey(Object)---当传入的参数为key时,表示是否包含该键的映射关系,如果有则返回true。
  3. containsValue(Object)---当传入的参数为value时,表示是否包含有映射指定值,如果有则返回true。
  4. get(Object)---如果存在指定的键对象,则返回该对象对应的值,否则为false。
  5. keySet()---返回该集合中的所有键对象形成的Set集合
  6. values()---返回该集合中所有值对象形成的Collection集合。

import java.util.*;
public class ForMap02 {
	@SuppressWarnings("all")   //抑制java警告
	public static void main(String[] args) {
		Map map=new HashMap();
		System.out.println("put(key,value)---向集合中添加指定的key与value的映射关系");
		 map.put(666,999);
		 map.put("a",97);
		System.out.println(map);
		System.out.println("containsValue(Object)");
		System.out.println("当传入的参数为key--'a'时,表示是否包含该键的映射关系,如果有则返回true。");
		System.out.println(map.containsValue("a"));
		System.out.println("当传入的参数为value--999时,表示是否包含有映射指定值,如果有则返回true。");
		System.out.println(map.containsValue(999));
		System.out.println("get(666)---如果存在指定的键对象666,则返回该对象对应的值,否则为false。");
		System.out.println(map.get(666));
		System.out.println("keySet()---返回该集合中的所有键对象形成的Set集合");
		Set  mapKeySet=map.keySet();
		System.out.println(mapKeySet);
		System.out.println("values()---返回该集合中所有值对象形成的Collection集合。");
		System.out.println(map.values());
	}
}

1,HashMap实现类

一般建议使用HashMap实现类实现Map接口,因为由HashMap类实现的Map集合对于添加和删除映射关系的效率比另外一个实现类TreeMap更高。HashMap是基于哈希表的Map接口的实现,HashMap通过哈希码对其内部的映射关系进行快速查找。该类中有一个方法entrySet(),该方法可以将Map集合转换成EntrySet集合,该集合存放的元素类型为Map.Entry,一个Map.Entry对象包含有多个k,v键值对,我们一般使用了entrySet()创建了Entry集合后,不会直接使用它来进行操作,因为此时的EntrySet集合HashMap$Node,HashMap$Node没有提供我们遍历元素所使用的方法,因此我们要将得到的对象向下转型,转成Map.Entry后才能对集合可能的键值对进行访问和查询。可以使用getKey()和getValue()方法来分别获取键和值。如下我们通过案例来感受一下:

案例一:使用entrySet或keySet方法打印工资在18000以上的员工类对象。

我们使用HashMap实现类来进行创建集合对象。并在集合对象内添加员工类对象,键为员工id,值为员工的id,姓名,工资。

如下,我们先创建员工类

 之后我们使用entrySet()方法进行相应的创建转换,并使用加强for循环进行遍历:

import java.util.*;
public class EntrySetAndKeySet {
	@SuppressWarnings("all")
	public static void main(String[] args) {
		Map map=new HashMap();
		map.put(new Employees("001","张三",5000).id,new Employees("001","张三",5000));
		map.put(new Employees("002","李四",19000).id,new Employees("002","李四",19000));
		Set entrySet=map.entrySet();  //将Map集合转换成EntrySet集合(即HashMap&Node)
		for(Object obj:entrySet) {    //遍历集合中的对象
			Map.Entry entry =(Map.Entry) obj;
			Employees emp=(Employees)entry.getValue();
			if(emp.wage>18000) {       //进行判断,如果工资大于1万八的就打印出来
				System.out.println(emp);   
			}
		}
	}
}
class Employees{
	String id;
	String name;
	int wage;
	public Employees(String id,String name,int wage){
		this.id=id;
		this.name=name;
		this.wage=wage;
	}
	@Override
	public String toString() {
		return "Employee01 [id=" + id + ", name=" + name + ", wage=" + wage + "]";
	}
}

如下为第二种方法,就是使用Map接口自带的常用方法之一:keySet()

import java.util.*;
public class TestHashMap_Employee {
	public static void main(String[] args) {
		Map map=new HashMap();
		map.put(new Employee01("001","张三",5000).id,new Employee01("001","张三",5000));
		map.put(new Employee01("002","李四",19000).id,new Employee01("002","李四",19000));
		
		Set mapKeySet=map.keySet();   
		for(Object obj:mapKeySet) {
		Employee01 emp=(Employee01)map.get(obj);	
		if(emp.wage>18000) {
			System.out.println(emp);
		}
		}
	}
}
class Employee01{
	String id;
	String name;
	int wage;
	public Employee01(String id,String name,int wage){
		this.id=id;
		this.name=name;
		this.wage=wage;
	}
	@Override
	public String toString() {
		return "Employee01 [id=" + id + ", name=" + name + ", wage=" + wage + "]";
	}
}