JavaSE 拾遗(8)——JavaSE 集合框架

时间:2021-11-05 17:23:43

集合概述

这篇博客记录 javase 集合相关的内容。javase 集合部分主要有 javase 集合框架相关的内容和 javase 泛型相关的内容。集合是用来做什么的呢,集合主要是用来对现实世界中多个对象在一起进行统一描述的。在现实世界中,常常我们会对多个在一起的对象进行操作描述,比如1000学生的资料,100个用户的资料等等,基本的操作有增、删、改、查。现实中对这些对象的操作通常都是要进行统计相关的操作,比如排序等操作。数组虽然也是对对象的统一描述,在语义上差不多,但是在java语言使用中数组和集合仍有许多不同,数组的长度是固定的,集合的长度是可变的,数组的可以存储基本数据类型和对象,集合只能存储对象,并且他们在 jvm 底层的实现也是不同的,数组在 jvm 中有专门的指令和类型来支持,数组实例中有包含数组长度 length 这个元素的单元。

集合框架中常用类关系图
JavaSE 拾遗(8)——JavaSE 集合框架

集合框架的子类 ArrayList LinkedList HashSet TreeSet HashMap TreeMap 的主要区别是存储方式(数据结构)不同
Collection 和 Map 的区别是:Collection是单列集合,Map是双列结合,是保存映射关系的集合
List 和 Set 的区别是:List 元素是有序的(存入取出的顺序一致),元素有索引,并且元素可以重复,Set 元素是没有序的,元素没有索引,并且元素不能重复。

Collection <E> 中的共性关系

增:
add( E e)
addAll( Collection<? extends E> c)
删:
clear()
remove( Object o)
removeAll( Collection<?> c) 去除交集
retainAll( Collection<?> c) 保留交集
改:

查:
iterator() 遍历集合,获取集合中的元素的引用
isEmpty()
size()
转成数组:
toArray()

集合中保存的都是对象的引用。

Iterator 接口,迭代器是用于取出集合中的元素,这里迭代器类和集合类是相互依赖的关系,在创建迭代器对象的时候,需要传一个集合类对象的引用,迭代器才能知道遍历那个对象。而且具体的迭代器对象遍历集合类的方法依赖于具体的集合类的数据结构,每个集合类中的迭代器是典型的内部类的例子,把每个集合中迭代器共性抽取出来就有了 Iterator 接口。Iterator 和 Collection 的是典型的双向关联关系。
hasNext() 判断是否有下一个元素
next() 获取元素
remove() 移除元素
Iterator 接口的实现都是在 Collection 子类的内部,是内部类,迭代器使用注意,在迭代过程中不可以使用集合的对象的方法来操作对象。

List <E> 集合的共性关系

增:
add(int index, E element)  把元素添加在 List 中指定索引的位置
addAll(int index, Collection<? extends E> c) 把 Collection 集合添加在 List 中指定索引的位置
删:
remove(int index) 删除指定索引位置的元素
改:
set(int index, E element) 设置 List 中指定索引位置的元素,设置 List 中的元素引用
查:
indexOf( Object o) 获取指定对象的第一个索引
lastIndexOf( Object o) 获取指定对象最后一个索引
get(int index) 获取索引位置的元素
listIterator(int index) 获取指定位置开始的 List 的 ListIterator
subList(int fromIndex, int toIndex) 返回子 List
总结:凡是可以操作角标的方法都是 List 中特有的方法
Listiterator 接口
ListIterator 和 Iterator 的区别主要是, Iterator 只能进行单向迭代,只能进行删除操作,ListIterator 运行双向迭代 和 索引的迭代,并且可以在迭代过程中添加、删除、修改元素
hasNext()
hasPrevious()
next()
nextIndex()
previous()
previousIndex()
add()
remove()
set()

List集合中常见之类对象 ArrayList LinkedList Vector 的特点
ArrayList:底层使用的是数组数据结构,特点是查询和修改速度快,增删比较慢,线程不同步
LinkedList:底层使用的是链表数据结构,特点是增删速度很快,查询和修改速度较慢
Vector:底层是数组数据结构。Vector 是线程同步的。被 ArrayList 替代。Vector 中的 Enumeration 接口的实现,和 Iterator 接口功能是重复的。

LinkedList 特有的方法

addFirst() push
addLast()

getFirst() peek
getLast()
如果集合中没有元素,会出现 NoSuchElementException(),但是 peek 方法不会,这是 JDK1.6 才有的方法,把 LinkedList 对象当做 First 为栈顶的堆栈

removeFirst() pull
removeLast()
如果集合中没有元素,会出现 NoSuchElementException(),但是 pull 方法不会

练习1,使用 LinkedList 实现堆栈和队列
/**
 * 需求:创建一个队列和堆栈的类
 */
package cn.itcast.others;

import java.util.LinkedList;

/**
 * @class: HeapStackDemo
 * @package: cn.itcast.others
 * @description: TODO
 * @author: vivianZhao
 * @date: 2013-7-17 下午3:13:27
 * @version: 1.0
 */
public class HeapStackDemo {

	/**
	 * @method: main
	 * @description: TODO
	 * @param args
	 * @return: void
	 * @author: vivianZhao
	 * @date: 2013-7-17 下午3:13:27
	 * @version: 1.0
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Stack<String> stack = new Stack<String>();
		stack.push("1");
		stack.push("2");
		stack.push("3");
		System.out.println(stack.pull());
		System.out.println(stack.pull());
		System.out.println(stack.pull());
		
		System.out.println("-----------------------");
		
		Queue<String> queue = new Queue<String>();
		queue.push("1");
		queue.push("2");
		queue.push("3");
		System.out.println(queue.pull());
		System.out.println(queue.pull());
		System.out.println(queue.pull());
	}

}

class Stack<T> {
	private LinkedList<T> linkedList = new LinkedList<T>();

	public void push(T element) {
		linkedList.push(element);
	}

	public T pull() {
		return linkedList.pollFirst();
	}

}

class Queue<T>{
	private LinkedList<T> linkedList = new LinkedList<T>();

	public void push(T element) {
		linkedList.push(element);
	}

	public T pull() {
		return linkedList.pollLast();
	}
}

练习2,去除 ArrayList 对象中的重复元素,List 集合判断元素是否相同,依据的是元素的 equals 方法。
判断 List 中是否包含某个元素可以用 contains 函数,我们可以先新建一个 listA ,再遍历某个 listB,在遍历的时候判断 listA 是否包含该元素,如果没有包含,则添加到 listA 中,否则啥也不做。

Set

Set集合的功能和 Collection 是一致的。

HashSet

HashSet:底层数据结构是 hash 表, hashing定义了一种将字符组成的字符串转换为固定长度(一般是更短长度)的数值或索引值的方法,称为散列法,也叫哈希法; hash表,有时候也被称为散列表,保存 hash 值的表就叫 hash 表。个人认为,hash表是介于链表和二叉树之间的一种中间结构。链表使用十分方便,但是数据查找十分麻烦;二叉树中的数据严格有序,但是这是以多一个指针作为代价的结果。hash表既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。  打个比方来说,所有的数据就好像许许多多的书本。如果这些书本是一本一本堆起来的,就好像链表或者线性表一样,整个数据会显得非常的无序和凌乱,在你找到自己需要的书之前,你要经历许多的查询过程;而如果你对所有的书本进行编号,并且把这些书本按次序进行排列的话,那么如果你要寻找的书本编号是n,那么经过二分查找,你很快就会找到自己需要的书本;但是如果你每一个种类的书本都不是很多,那么你就可以对这些书本进行归类,哪些是文学类,哪些是艺术类,哪些是工科的,哪些是理科的,你只要对这些书本进行简单的归类,那么寻找一本书也会变得非常简单,比如说如果你要找的书是计算机方面的书,那么你就会到工科一类当中去寻找,这样查找起来也会显得麻烦。 上面提到的归类方法其实就是hash表的本质。HashSet 是把对象的 HashCode() 方法返回的 Hash 值作为类别存储在 Hash 表中。HashSet 中的判断是否是相同元素的方法就是先判断元素是否是同一个类别,即 hashCode 值是否相同,再判断元素是否相等,即 equals 方法返回的结果。所以一般使用 HashSet 来保存某对象的集合,那么一般需要复写该对象的 hashCode 方法和 equals 方法。equals 判断的时候,需要遍历,比较低效。查找都是基于比较的基础之上的,hashcode 这种查找元素比较的方法和 许多字符串比较的时候分段比较有异曲同工之妙。
String 常数保存在代码区的常数区,所以如果相同的字符串常数,一定是对应同一个常数区的对象,此时他们地址相同,并且内容相同。

HashSet中保存自定义对象实例
/**
需求:演示 HashSet 中保存自定义对象集合

思路:
1.添加 Person 的对象到 HashSet 对象中
2.添加重复元素试试看,
3.定义 HashSet 对象中 Person 元素名字和年龄相同的元素就为相同元素
4.打印出 HashSet 对象中的元素

步骤:

总结:
1.Set 集合中,不能保存重复的元素,不同的存储结构对于元素是否相同的判断方法不同
2.HashSet 对象中元素相同的定义依赖于元素的 hashCode 和 equals 方法
3.HashSet 中,判断 hash 值是否相同底层实现可以用二叉树结构实现
*/
import java.util.*;

class HashSetDemo
{
	public static void main(String[] args) 
	{
		HashSet personHashSet = new HashSet();

		personHashSet.add(new Person("zhangsan",12));
		personHashSet.add(new Person("lisi",13));
		personHashSet.add(new Person("wangwu",14));
		personHashSet.add(new Person("zhaoliu",15));
		personHashSet.add(new Person("zhaoliu",15));

		Iterator iterator = personHashSet.iterator();

		while(iterator.hasNext())
		{
			Person person = (Person)iterator.next();
			printObject(person);
		}

		
	}

	static void printObject(Object o)
	{
		System.out.println(o);	
	}
}

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()
	{
		System.out.println(name + ": hashCode");
		return name.hashCode() + age*21;
	}
	
	public boolean equals(Object o)
	{
		if(o instanceof Person)
		{
			Person person = (Person)o;
			System.out.println(name + ": equals :" + person.getName());
			if(name.equals(person.getName()) && age == person.getAge())
			{
				return true;
			}
		}
		
		return false;	
	}

	public String toString()
	{
		return name + ":" + age;
	}
}

TreeSet

通过把字符串对象集合保存在 TreeSet 对象中,发现 TreeSet 对象可以对字符串对象集合进行自动排序,通过往 TreeSet 对象中保存自定义对象发现,TreeSet 对象中保存的元素必须是 Comparable 接口的对象。就是元素必须是可以比较大小的。当比较结果是相等的时候,TreeSet 就认为这两个对象时相同对象,只保存之前的那个。TreeSet 判断集合中的元素的大小和是否相等,都依赖于元素所属类的 Comparable 接口的实现函数 compareTo(),或者传给 TreeSet 的 Comparator 对象。
TreeSet 保存自定义类型对象的集合实例
/**
需求:演示 TreeSet 中保存自定义对象

思路:
1.往 TreeSet 集合中存储自定义对象学生,按照学生的年龄进行排序
2.打印 TreeSet 集合中的元素

步骤:

总结:
Set:无序,不可以有重复元素
	|--HashSet:底层数据结构是 hash 表,线程非同步
				保证元素唯一性的原理:依赖元素的 hashCode 方法和 equals 方法返回的值

	|--TreeSet:可以对 Set 集合中的元素进行自定义的排序。
				底层数据结构是二叉树
				保证元素唯一性的依据之一:元素的 compareTo 方法

				TreeSet 排序的第一种方式:让元素自身具备比较性,元素需要实现 Comparable 接口
				TreeSet 排序的第二种方式:传一个 Comparator 实例给 TreeSet,让容器自身具备比较性

				就好像一个班上的人可以自动按身高排序,这个可以算作他们自身具备的比较性,也可以
				给老师一个直尺,老师量过每个人的升高后,老师来给这些人排序。
				
				Comparator 和 Comparable 接口使得 TreeSet 集合具有了极好的扩展性。
*/
import java.util.*;

class TreeSetDemo
{
	public static void main(String[] args) 
	{
		//TreeSet studentTreeSet = new TreeSet(); // 使用自身比较性排序
		TreeSet studentTreeSet = new TreeSet(new StudentComparator()); //使用比较器排序

		studentTreeSet.add(new Student("zhangsan", 22));
		studentTreeSet.add(new Student("lisi", 17));
		studentTreeSet.add(new Student("wangwu", 18));
		studentTreeSet.add(new Student("zhaoliu", 18));

		Iterator studentIterator = studentTreeSet.iterator();

		while(studentIterator.hasNext())
		{
			Student student = (Student)studentIterator.next();
			System.out.println(student);
		}
	}
}

class Student implements Comparable
{
	private String name;
	private int age;

	Student(String name, int age)
	{
		this.name = name;
		this.age = age;
	}
	
	public String getName()
	{
		return name;
	}

	public int getAge()
	{
		return age;
	}

	public int compareTo(Object o)
	{
		if(o instanceof Student)
		{
			Student student = (Student)o;
			System.out.println(toString() + "...compareTo:..." + student.toString());
			if(age == student.getAge())
			{
				return name.compareTo(student.getName());
			}
			else
			{
				return ((Integer)age).compareTo(student.getAge());
			}
		}
		return -1;
	}

	public String toString()
	{
		return name + ":" + age;
	}
}

class StudentComparator implements Comparator
{
	public int compare(Object object1, Object object2)
	{
		Student stu1 = (Student)object1;
		Student stu2 = (Student)object2;

		int num = stu1.getName().compareTo(stu2.getName());

		if(num == 0)
		{
			return ((Integer)stu1.getAge()).compareTo((Integer)stu2.getAge());
		}
		return num;
	}
}

comparable comparator 和 TreeSet 、集合中元素 的关系再次说明,拆分某个功能的时候,有两种方法,一种是以模板方法的模式,让拆分出来的功能在子类中以重写的形式存在,拆分出来的功能可以是父类中的抽象函数,也可以是接口中的抽象函数,和原系统形成父子类的关系,比如 comparable 和 集合中元素的关系;另一种是让拆分出来的功能和原系统以组合的形式存在,比如 comparator 和 TreeSet 的关系。

泛型

JDK1.5 以后出现的新特性,用于解决安全问题的,是一个安全机制。
JDK升级有3种目的,高效、安全、简化书写。
范型,就是带有数据类型参数的数据类型,在类定义的时候,不能明确类中某个部分数据的类型,那么就可以使用泛型机制,用一个标识符来代替这个类型,并用 <> 声明这是一种类型代替机制,当使用这个类的时候才指定这个类型,这就是泛型。

泛型的好处:
1.将运行时期出现的的问题 ClassCastException,转移到编译时期,方便程序猿解决问题,让运行时期问题减少,增加安全性。
2.避免了强制转换的麻烦和不安全问题。

在使用泛型类的时候, < > 就是用来接收类型的,将类中将要使用的数据类型,作为参数传递到 < > 即可,如同函数调用的时候参数传递一样,但是 < > 中只能接收引用数据类型。

泛型的定义可以在两个地方定义,就是 泛型类和泛型方法。

泛型类
/**
需求:演示泛型类的定义和使用

思路:定义一个工具类主要功能是可以保存某个对象实例到成员变量中,还支持
把该对象实例取出来,但是为了能操作多种数据类型的对象,该引用数据类型延
迟到在类使用的时候才确定,而类的使用有两种,一种是使用类定义变量、创建
对象,一种是 extends 该类

步骤:

总结:
1.泛型类就是具有泛型声明的类,也就是泛型定义在类上,使用范围是该类的内部方法、成员变量和局部变量

2.什么时候定义泛型类:在定义一个类的时候,当类中要操作的引用类型数据在
类定义的时候数据类型不能确定(比如想要支持多种数据类型),而在类使用的
时候可以确定,早期使用 Object 来实现,现在定义泛型类来实现。
*/
class GenericClassDemo
{
	public static void main(String[] args) 
	{
		Utils<Worker> util = new Utils<Worker>();

		util.setObject(new Worker());
		//util.setObject(new Student()); // 编译时就会发现问题
		Worker worker = util.getObject();
	}
}

class Utils<T>
{
	private T object;

	public void setObject(T object)
	{
		this.object = object;
	}

	public T getObject()
	{
		return object;
	}
}

class Worker
{
}

class Student
{
}
泛型类和使用 Object 的区别
/**
需求:一个钉子厂,钉子厂可以生产钉子和对外打广告,钉子的原料可
以有3种,铁、铝、铜,生产的时候用一种就行,一个钉子厂在实例化
的时候,才定下来这个钉子厂只能用那一种原料生产。

思路:

步骤:
*/
class NailFactoryDemo
{
	public static void main(String[] args) 
	{
		NailFactory<Aluminum> aluminumNailFactory = new NailFactory<Aluminum>();
		
		aluminumNailFactory.advertise();
		//aluminumNailFactory.makeNail(new Copper());
		aluminumNailFactory.makeNail(new Aluminum());
	}
}

class NailFactory<T>
{
	public void advertise()
	{
		System.out.println("我们厂是世界上最好的钉子厂");
	}

	public void makeNail(T material)
	{
		if(material instanceof Aluminum || material instanceof Copper 
			|| material instanceof Ferrum)
		{
			System.out.println("生产出1吨 " + material + " 钉子");
		}
		else
		{
			return;
		}
		
	}
}

/*
class NailFactory
{
	public void advertise()
	{
		System.out.println("我们厂是世界上最好的钉子厂");
	}

	public void makeNail(Object material)
	{
		if(material instanceof Aluminum || material instanceof Copper 
			|| material instanceof Ferrum)
		{
			System.out.println("生产出1吨 " + material + " 钉子");
		}
		else
		{
			return;
		}
	}
}
//*/

class Aluminum
{
	public String toString()
	{
		return "铝";
	}
}

class Copper
{
	public String toString()
	{
		return "铜";
	}
}

class Ferrum
{
	public String toString()
	{
		return "铁";
	}
}

class Gold
{
	public String toString()
	{
		return "金";
	}
}

泛型方法、静态泛型方法
/**
需求:演示泛型方法、静态方法泛型

思路:定义一个工具类,可以打印对象,但是为了能打印所有对象打印的对
象的类型延迟到方法调用的时候才能确定。

步骤:

总结:
1.为了让不同方法可以操作不同引用类型,而且类型还要延迟到使用方法的
时候才能确定,那么泛型可以定义在方法上。
2.在调用泛型方法的时候,不用像使用泛型类一样指定类型,而是根据方法
的参数列表和返回值来确定泛型的类型,泛型方法相当于,方法参数的类型
或者方法返回值类型延迟到方法实际调用的时候才确定,并且不用显式指定
,泛型类型会根据返回值类型和参数类型默认进行类型匹配。
3.泛型类中的泛型类型作用于整个对象生存期,而泛型方法中的泛型类型只
作用于方法的某一次执行期间,和方法的局部变量类似。
4.静态方法不可以访问类上定义的泛型,如果静态方法操作的引用数据类型
不确定可以将泛型定义在方法上。
*/
class GenericMethodDemo
{
	public static void main(String[] args) 
	{
		Utils util = new Utils();

		util.show("泛型");
		util.show(new Integer(15));
		util.print(new Integer(15));
	}
}

class Utils
{
	public <T> void show(T t) //作用范围只在该方法中
	{
		System.out.println("show" + t);
	}

	public <T> void print(T t)
	{
		System.out.println("print" + t);
	}

	public static <T> void out(T t)
	{
		System.out.println("out" + t);
	}
}

泛型接口
/**
需求:演示泛型接口

思路:假设一个妻子在做饭的方法中使用过的原材料一生中都是固定的,并
且在该妻子创建的时候才确定原材料的类型。

步骤:

总结:
1.接口的泛型是延迟接口中使用的引用类型到实现接口或者使用接口
定义变量的时候才确定,如果实现接口的类还不能确定该引用类型,可以
继续定义泛型类,延迟到类创建对象的时候才确定该类型。所以延迟也是
可以传递的。
2.如果泛型类在使用的时候,如果指定泛型的类型,在使用该对象调用类
的函数的时候,才会提示类型不安全。
3.泛型在指定类型的时候,看起来有点重复,因为类上要指定,函数上还
要传对应的参数,如果有类的依赖关系,依赖的类在使用过的时候还要指
定,也就是在每一个使用泛型类的地方都要指定。
4.泛型增加了安全性,但是也增加了表达上的冗余。
*/
class GenericInterfaceDemo 
{
	public static void main(String[] args) 
	{
		//Family family = new Family(new XiaoFang<String>()); //会报操作不安全
		Family<String> family = new Family<String>(new XiaoFang<String>());
		family.cook("米");
	}
}


interface Wife<T> //T符号在此定义
{
	void cook(T material); //T符号在此使用
}

/*
class Family
{
	Wife wife;

	Family(Wife wife)
	{
		this.wife = wife;
	}

	public void cook(Object material)
	{
		wife.cook(material);
	}
}
//*/

class Family<T> //T符号在此定义
{
	Wife<T> wife; //T符号在此使用,使用Family的范型类型指定Wife的范型类型

	Family(Wife<T> wife)
	{
		this.wife = wife;
	}

	public void cook(T material)
	{
		wife.cook(material);
	}
}

class XiaoFang<T> implements Wife<T> //XiaoFang<T>中的符号T是表示声明T是类型,Wife<T>中的符号T是表示使用类型T
{
	public void cook(T material)
	{
		System.out.println("XiaoFang cooking by " + material);
	}
}

泛型通配符合泛型限定
泛型通配符,主要是指在定义类或、接口或者方法的时候,如果需要正在使用泛型,并且需要指定泛型的类型,那么可以不用指定泛型的类型,而是使用通配符 ? 来表示类型。使用通配符 ? 给泛型分配一个不定的类型,在传递参数的时候,根据参数类型自动确定类型,通配符的好处在于自动确定类型,坏处是在定义的函数或者类内部不能使用通配符表达的类型来定义变量,同样只能使用通配符来修饰泛型。
泛型通配符主要是在我们进行泛型传递的时候使用。

泛型限定,主要是指在定义泛型类或者泛型接口的时候,给泛型的类型 T 加上一个可以接受的类型的范围限制,上限使用 extends,比如 <T extends Object>,下限使用 super,比如 <T super ArrayList> 

使用通配符的时候还可以指定上下限,格式如下:
<? extends T> 上限
<? super T>  下限

泛型限定实例
/**
需求:演示泛型限定

思路:
1.演示 Comparator 接口中泛型限定的作用
2.定义一个 Person 类和它的两个子类 Student、Worker
3.在两个 TreeSet 集合中分别保存 Student 和 Worker 对象
4.Person Student Worker 对象排序的定义都相同
5.Comparator 接口的是实现类中泛型分别用 Person Student Worker
6.比较三个 Comparator 实现类的优劣

步骤:

总结:
因为 TreeSet 构造函数的泛型限定 new TreeSet<E>(Comparator<? super E> c)
所以可以只定义 PersonComparator 比较器
new TreeSet<Student>(new PersonComparator())
new TreeSet<Worker>(new PersonComparator())
这样就提高了代码的复用性,减少了代码重复
printCollection(Collection<? extends Person> e) 方法也是相同的道理
*/
import java.util.*;

class GenericLimitDemo
{
	public static void main(String[] args) 
	{
		TreeSet<Student> studentTreeSet = new TreeSet<Student>(new PersonComparator());

		studentTreeSet.add(new Student("aaa11"));
		studentTreeSet.add(new Student("aaa13"));
		studentTreeSet.add(new Student("aaa12"));
		studentTreeSet.add(new Student("aaa14"));

		TreeSet<Worker> workerTreeSet = new TreeSet<Worker>(new PersonComparator());

		workerTreeSet.add(new Worker("aaa--11"));
		workerTreeSet.add(new Worker("aaa--14"));
		workerTreeSet.add(new Worker("aaa--12"));
		workerTreeSet.add(new Worker("aaa--13"));

		prinPersontCollection(studentTreeSet);
		prinPersontCollection(workerTreeSet);

	}

	static void printPersonCollection(Collection<? extends Person> e)
	{
		Iterator<? extends Person> iterator = e.iterator();
		while(iterator.hasNext())
		{
			Person person = iterator.next();
			System.out.println(person.getName());
		}
	}
}

class Person
{
	private String name;

	Person(String name)
	{
		this.name = name;
	}

	public String getName()
	{
		return name;
	}
}

class Student extends Person
{
	Student(String name)
	{
		super(name);
	}
}

class Worker extends Person
{
	Worker(String name)
	{
		super(name);
	}
}

class PersonComparator implements Comparator<Person>
{
	public int compare(Person person1, Person person2)
	{
		return person2.getName().compareTo(person1.getName());
	}
}

Map

Map 是保存映射关系的集合,是一种双列集合,当发现有映射关系集合需要描述的时候,选择 Map 集合。
Map
   |——HashTable:底层是 hash 表数据结构,不可以存入 null 键、null 值,该集合是线程同步的,jdk1.0 效率低下。
   |——HashMap:底层是 hash 表数据结构,运行存入 null 键、null 值,该集合是线程不同步的,jdk1.2 效率高。
   |——TreeMap:底层是二叉树数据结构,线程不同步,可以给 map 集合中的映射按键进行排序。

Map 其实和 Set 很像,Set 是特殊的 Map,Set 底层就是用 Map 实现的。

Map 集合的主要操作方法有
增、改
V put(K key, V value) 把指定值和键关联,并该关系保存在Map中,如果 put 的映射中的键已经存在,则覆盖已有的映射,并返回被 覆盖的映射的值。
void putAll(Map<? extends K, ? extends V> m)

void clear() 删除所有映射
V remove(Object key) 删除指定的键的映射


boolean containKey(Object key)
boolean containValue(Object value)
boolean isEmpty()
int size()

V get(Object key) 返回给定的键对应的值
Set<K> keySet() 返回映射集合中所有键的集合
Set<Map.Entry<K, V>> entrySet() 返回所有映射的集合
Collection<V> values() 返回映射集合中包含的所有的值的集合

import java.util.*;
class  MapDemo
{
 public static void main(String[] args) 
 {
  //建立Map集合
  Map<String,String> map=new HashMap<String,String>();
  ////添加元素,添加元素,如果出现添加时,相同的键。那么后添加的值会覆盖原有键对应值。
  map.put("01","zhangsan1");
  map.put("02","zhangsan2");
  map.put("03","zhangsan3");
  map.put("04","zhangsan4");
  
  sop(map.containsKey("02"));//判断键是否存在
  sop(map.get("01"));//可以通过get方法的返回值来判断一个键是否存在。通过返回null来判断。 
  sop(map);
  Collection<String> coll=map.values();//用values获取map集合中所有的值.
  sop(coll);
 }
 public static void sop(Object obj)//为了打印方便建立一个函数
 {
  System.out.println(obj);
 }
}


Map集合两种遍历取出方式 
1.Set<K> keySet:将 Map 中所有的键存入到 Set 集合中,因为 Set 具备迭代器,所以可以根据迭代方式取出所有的键,再使用 get 方法,根据键获取每一个键对应的值。
是将 Map 集合中的 key 转换为 Set 集合,再通过迭代器取出每个 key,再通过 key 取出每个映射的 value。
JavaSE 拾遗(8)——JavaSE 集合框架
import java.util.*;
class MapDemo2
{
 public static void main(String[] args)
 {
  Map<String,String> map=new HashMap<String,String>();

  map.put("01","zhansan01");
  map.put("02","zhansan02");
  map.put("03","zhansan03");
  map.put("04","zhansan04");

  Set<String> s=map.keySet();//先获取map集合的所有键的Set集合,keySet();
  System.out.println(map);//有了Set集合。就可以获取其迭代器。

  for(Iterator<String> it=s.iterator(); it.hasNext();)
  {
   String key=it.next();
   //有了键可以通过map集合的get方法获取其对应的值。
   String value=map.get(key);
   System.out.println(key+"..."+value);
  }
 }
}


2.Set<Map.Entry<K, V>> entrySet:将 Map 集合中的映射关系存入到了 Set 集合中,而这个关系的数据类型就是 Map.Entry。Map.Entry 也是一个接口,它是 Map 接口的一个内部接口。
Map 和 Entry 的关系就是,外部类和静态成员内部类的关系,因为 Entry 是 Map 的组成元素,总数伴随着 Map 出现,所以把 Entry 放入了 Map 内部。
interface Map<K, V>
{
	public static interface Entry<K, V>
	{
		public abstract K getKey();
		public abstract V getValue();
		...
	}
	...
}
JavaSE 拾遗(8)——JavaSE 集合框架
/**
 1.描述学生.
 2.定义map容器.将学生作为键.住址作为值,存入.
 3.获取map集合中的元素
 */
import java.util.*;

class MapDemo3
{
 public static void main(String[] args)
 {
  Map<Integer,String> map=new HashMap<Integer,String>();

  map.put(01,"java01");
  map.put(02,"java02");
  map.put(03,"java03");
  map.put(04,"java04");

  //将Map集合中的映射关系取出。存入到Set集合中。
  Set<Map.Entry<Integer,String>> entrySet=map.entrySet();

  for(Iterator<Map.Entry<Integer,String>> it=entrySet.iterator();it.hasNext();)
  {
   Map.Entry<Integer,String> me=it.next();
   Integer key=me.getKey();
   String value=me.getValue();
   System.out.println(key+"...."+value);
  }
 }
}

Map 中保存自定义类键值对
import java.util.*;

class Student implements Comparable<Student>// 为了以后方便可能存进去TreeSet集合中去实现Comparable.将学生具备比较性
{
	private String name;
	private int age;

	Student(String name, int age)// 将学生name和age初始化
	{
		this.name = name;
		this.age = age;
	}

	public int compareTo(Student s)// 覆盖Comparable中的compareTo方法.来比较,先比较age在比较name.
	{
		int num = new Integer(this.age).compareTo(new Integer(s.age));// Integer是因为age是基本数据类型不具备比较.要转成Integer来实现compareTo方法
		if (num == 0)
			return this.name.compareTo(s.name);// name是字符串本身就比较比较性.直接使用compareTo方法就哦了
		return num;
	}

	public int hashCode()// 复写hashCode.来让存进去的学生保证唯一.为什么要覆盖因为默认比较是比较hash值.和内容是否一样.因为存的是hash值所以每次建立对象的时候都不一样
	// 所以要复写hashCode,来比较年龄和姓名是否是相同.这样就能保证学生的唯一性了.
	{
		return name.hashCode() + age * 34;
	}

	public boolean equals(Object obj)// 如果年龄相同的话在比较年龄.
	{
		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 getInt() {
		return age;
	}

	public String toString() {
		return name + ".." + age;
	}
}

class MapTest {
	public static void main(String[] args) {
		HashMap<Student, String> hm = new HashMap<Student, String>();// 穿件Map集合中的
																		// HashMap集合

		hm.put(new Student("zhangsan01", 21), "beijing");// 往里面添加键值对.将学生作为键.住址作为值
		hm.put(new Student("zhangsan02", 22), "tianjing");
		hm.put(new Student("zhangsan03", 23), "sahgnhan");
		hm.put(new Student("zhangsan01", 23), "sahgnhan");

		// 第一种去取出方式
		Set<Student> s = hm.keySet();
		for (Iterator<Student> it = s.iterator(); it.hasNext();) {
			Student key = it.next();
			String value = hm.get(key);
			System.out.println(key + "...." + value);
		}
		// 第二种取出方式
		System.out
				.println("-----------------------------------------------------");
		Set<Map.Entry<Student, String>> entrySet = hm.entrySet();
		for (Iterator<Map.Entry<Student, String>> it = entrySet.iterator(); it
				.hasNext();) {
			Map.Entry<Student, String> me = it.next();
			Student key1 = me.getKey();
			String value1 = me.getValue();
			System.out.println(key1 + "..." + value1);
		}
	}
}

字母个数统计Map描述实例

集合框架的工具类
Collections :给 Collection 提供工具方法,类中没有对象特有数据,所以都是静态方法,主要是给 List 提供众多集合操作工具。

sort:对List集合排序,支持可重复元素的排序
public static <T extends Comparable<? super T>> void sort(List<T> list)
根据元素的自然顺序 对指定列表按升序进行排序。列表中的所有元素都必须实现 Comparable 接口
 
Collections.sort(list);
不可以给Set排序因为Set里面有TreeSet.用它也没什么用
如果出现了重复元素也可以排序,因为是数组结构有索引.

Collections.sort(list,new 比较器Comparator);
如果不喜欢默认的排序可以自定义比较器(比如最大长度)

max(Collection c) 按元素默认比较性取出集合中的最大元素
public static <T> T max(Collection<? extends T> coll,Comparator<? super T> comp)
根据指定比较器产生的顺序,返回给定 collection 的最大元素。collection 中的所有元素都必须可通过指定比较器相互比较

binarySearch(List list, K key) 对列表进行二分查找,获取指定元素,返回排序后该元素的index,

public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)

使用二分搜索法搜索指定列表,以获得指定对象。在进行此调用之前,必须根据列表元素的自然顺序

对列表进行升序排序(通过 sort(List) 方法)。如果没有对列表进行排序,则结果是不确定的。如果列
表包含多个等于指定对象的元素,则无法保证找到的是哪一个。
 
如果搜索键包含在列表中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
插入点 被定义为将键插入列表的那一点:即第一个大于此键的元素索引;如果列表中的所有元素都
小于指定的键,则为 list.size()。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
Collection.sort(list);//对list集合进行排序
int index =Collection.binarySearch(list,"aaa");//查找"aaa"所在位置.返回插入点-1
Collection.sort(list,new 比较器Comparator);//对list集合进行长度排序
int index =Collection.binarySearch(list,"aaa"new 比较器Comparator);//查找"aaa"所在位置.(按长度来了)返回插入点-1

copy(List dest, List src) 复制一个 List 集合到另一个 List 集合

public static <T> void fill(List<? super T> list,T obj) 使用指定元素替换 List 集合中所有元素

public static <T> boolean replaceAll(List<T> list,T oldVal,T newVal) 将集合中某个值元素全部替换为新值

swap(List l,int x, int y)

reverse(List list) 反转 list 中元素的顺序

shuffle(List l) 随机排列 List 中的元素

reverseOrder() 返回逆向比较器

同步集合类对象
synchronizedCollection (Collection c)
synchronizedList(List c)
synchronizedSet(Set c)
synchronizedMap (Map c)

Arrays
用于操作数组的工具类,里面都是静态方法
binarySearch() 二分查找
copyOf() 复制数组
equals() 比较数组是否相同
fill() 替换数组中的值
sort() 给数组中元素排序
toString()
asList() 把数组转为 List 集合,把数字变为 List 集合的好处是,可以用集合的思想和方法来操作数组中的元素,比如 contains() 方法判断元素是否存在。数组变集合后不支持 add 函数,因为数组长度是固定的。如果你增删。那么发生 UnsupportedOperationException。
 
数组变集合,如果数组中是基本类型元素,则把数组作为集合的元素,如果是数组是引用类型元素,则直接把数组元素作为集合元素。

集合变数组用集合的 toArray() 函数。
contains
get
indexOf()
subList() 等集合的操作方法。

集合变数组:
Collection接口中的toArray方法
1.指定类型的数组到底是要定义多长呢?
  当指定类型的数组长度小于集合的size.那么该方法内部会创建一个新的数组.长度为集合的size.
  当指定类型的数据长度大于集合的size.就不会创建新的数组.而是使用传递进来的数组.
  所以创建一个刚刚好的数组最优.

 2.为什么要将集合变成数组呢?
  为了限定对元素的操作, 不需要进行增删了.