一、集合
1.1、集合介绍
回顾:
在第二天学习的变量,变量空间中就可以保存数据。在某个时间点上只能保存具体的一个值。
第四天学习数组,数组中可以保存多个类型相同的数据。数组一旦定义好了,长度就会是固定的。如果数据随着程序的运行会增加,这时我们定义的数组就不能灵活的去变化保存这些可以随着程序运行增加的新的数据。
字符串缓冲区,它中可以保存任何的数据,但是最后把这些数据都变成字符串处理。
现在我们要学习的集合,它也属于容器。也可以存放数据。随着我们学习技术的深入,那么我们在程序中创建的对象也会越来越多。这时在程序中就要想办法先把这些对象给存储起来,然后在需要使用这些对象的时候从容器中把对象取出,再去使用这些对象。
在前面学习的变量,数组,字符串缓冲区中都可以保存对象的。但是前面学习的这些容器都有些问题:
变量:引用变量只能保存一个对象。
数组:需要定义成对象类型的数组,并且只要是数组长度一定是固定的。
字符串缓冲区:它会把其中的数据变成字符串。
而我们在程序到底需要new多少个对象,以及什么类型的对象这些都需要根据程序的运行来决定。而不能提前全部预设好。这时我们这些对象需要保存,就不能使用以前学习过的那些容器保存。必须使用Java中给我们提供集合容器来保存。
集合:是个容器。它可以保存对象。并且集合有优点:随着对象的增加,集合容器会自动的扩容。
集合容器不是一个对象,而是由众多的类组成在一起,形成了一个集合体系。
java中提供的众多的集合容器,集合是保存对象,由于需求不同,程序中对象的多少不同,同时对象需要怎么来存储,以及对象的存储方式也会有所不同,于是Java中就根据不同的需求提供了不同的集合容器。
这样就导致集合中有大量的类需要学习。
而这些所有的集合类他们形成了一个体系:集合框架体系。而Java对这些庞大的集合体系进行共性的功能抽取,形成了集合这类容器最基本的操作规律。
集合(容器)的操作规律: CRUD create read update delete
- 增加(给容器中添加数据) (add)
- 删除(从容器中删除数据)(remove/clear)
- 修改(修改容器中的数据)
- 查看(获取集合中的某个数据)
- 判断(判断某些数据是否在容器)
- 遍历(访问集合中的每个数据)
既然有上述的共性的规律,于是Java就把这些最基本的规律抽取到了一个接口中。
在集合框架体系中用接口来定义所有集合操作的共性规则。
集合框架从JDK1.2才开始存在。早期有集合,但是它们没有形成一个体系。
1.2、Collection接口介绍
Collection接口:它是集合的最顶层接口,它中规定了所有集合中的最基本的操作规律。而Java中提供的所有集合容器都保存在java.util包下。
Collection接口是集合的顶层接口,它下面有子接口或者间接的实现类,而具体的实现类中有些可以保存重复元素,有些不能保存重复元素,有些可以保证数据有序,一些则无序。
在学习集合框架的时候,遇到的所有E类型,全部理解成Object类型。
1.3、Collection中的方法介绍
1.3.1、添加方法
我们知道Collection是接口,它不能new对象。而Collection中定义的是所有集合共性的操作规律。那么我们就可以随便找个Collection的实现类。这样就可以使用多态的方式来操作集合。
add函数可以把一个对象保存在集合中,但是这个函数有返回值boolean,返回的结果是告诉我们是否给集合中添加成功。
1.3.2、删除方法
1.3.3、判断方法
1.3.4、获取元素个数
1.3.5、Collection接口中带All的方法
把c集合中的元素全部添加到当前调用这个方法的集合中
containsAll方法是要求 参数传递的集合 中的元素 需要 全部在 调用这个方法的集合中存在,才会返回true。
public static void method_3() {
// 创建集合对象
Collection coll = new ArrayList();
Collection coll2 = new ArrayList();
coll.add("aaaa");
coll.add("bvvv");
coll.add("dddd");
coll2.add("aaaa");
coll2.add("dddd2");
/*
* coll.removeAll(coll2)
* 是从coll集合中删除 coll与 coll2中相同的元素(交集)
*/
/*
boolean b = coll.removeAll(coll2);
System.out.println(coll);
System.out.println(coll2);
System.out.println(b);
*/
/*
* coll.retainAll(coll2)
* 是从coll中删除 coll与 coll2中不同的元素,也可以理解成保留2个集合的交集
*/
boolean b = coll.retainAll(coll2);
System.out.println(coll);
System.out.println(coll2);
System.out.println(b);
}
1.4、迭代器(遍历器)
1.4.1、迭代器介绍
由于集合框架中的集合容器太多,而每个集合容器中保存的数据存储的方式都不一样。于是导致我们往出取数据的时候方式也完全不相同。
Java针对这些所有集合容器进行共性取出方式的抽取,于是针对所有的集合定义了一个接口,在这个接口中描述了所有集合容器的共性遍历规则。
而这个接口它就是Iterator,它中定义了集合最基本的遍历方式:
Iterator接口的迭代方式:
针对一个集合,需要遍历的时候,应该首先去判断集合中有没有元素(对象),有就取出这个元素,没有就不用再进行遍历了。
判断容器中还有没有原始,如果有返回true,我们就可以根据这个返回的结果确定到底还要不要遍历这个集合容器
取出当前遍历到的那个元素。
1.4.2、迭代器的使用
/*
* 演示迭代器的使用
*/
public class IteratorDemo {
public static void main(String[] args) {
//创建集合对象
Collection coll = new ArrayList();
coll.add("aaaa");
coll.add("dddd");
coll.add("rrrrr");
coll.add("ttttt");
coll.add("abc");
coll.add("AAAAAAAAAA");
//System.out.println(coll);
//根据当前的集合获取到迭代器对象
Iterator it = coll.iterator();
while( it.hasNext() ){
System.out.println(it.next());
}
/*
* 针对集合每次获取到的迭代器,使用完之后,迭代器中的隐式光标就已经到了集合的最后
* 无法在去使用next获取集合中的元素。如果还要获取,需要重新在获取一个迭代器对象
*/
for( Iterator it2 = coll.iterator(); it2.hasNext() ; ){
System.out.println(it2.next());
}
}
}
1.4.3、迭代器注意细节
/*
* 需求:遍历集合,遇到"abc"就把它删除。
*/
public class IteratorDemo2 {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("abc");
coll.add("bbbbb");
coll.add("dddd");
coll.add("xyz");
coll.add("ABC");
/*
* 遍历集合,取出每个元素,进行判断,只要当前取出的这个元素是"abc"
* 就把它从集合中删除
*/
for( Iterator it = coll.iterator(); it.hasNext() ; ){
//取出这个元素进行判断
Object obj = it.next();
if( obj.equals("abc") ){
/*
* 当我们使用迭代器对集合中的元素进行迭代的时候,不允许使用集合自身的增删函数
* 对集合中的元素进行操作。 如果真的需要删除,这时只能使用迭代器自身的remove方法
*/
//coll.remove(obj);
it.remove();
}
}
System.out.println(coll);
}
}
1、使用迭代器对集合进行迭代的时候,不要使用集合自身的功能对集合进行增删操作。
2、所有的迭代器当迭代结束之后,那么这个迭代器就位于集合的最后。
3、使用迭代器迭代集合的时候,每一个hasNext方法都对应一个next,不要一个hasNext方法对应多个next。
1.5、集合的去重问题集合的细节
/**
* 需求:去除集合中的重复元素。
*/
public class CollectionTest {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("aaaa");
coll.add("abc");
coll.add("abc");
coll.add("bbbb");
coll.add("aaaa");
coll.add("xyz");
coll.add("xyz");
coll.add("aaaa");
//新建一个新的集合容器
Collection coll2 = new ArrayList();
for (Iterator it = coll.iterator(); it.hasNext();) {
Object obj = it.next();
//判断从原始集合中取出的元素在新的集合中是否存在
if( !coll2.contains(obj) ){
coll2.add(obj);
}
}
//循环结束之后,coll2集合中一定保存的都是不重复的元素
//把原始集合中的数据清空,把coll2中的不重复元素添加到原始集合中
coll.clear();
coll.addAll(coll2);
System.out.println(coll);
}
}
集合的细节:
集合容器是用来保存对象的。而真正在集合中保存的不是当前那个对象,而是对象在堆内存中的内存地址。
给集合中保存自定义对象:
二、List接口介绍
在学习Collection接口的时候,api中告诉我们,在Collection接口的下面有2个直接的子接口,分别是Set和List。
有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
与 set 不同,列表通常允许重复的元素。更确切地讲,列表通常允许满足 e1.equals(e2) 的元素对 e1 和 e2,并且如果列表本身允许 null 元素的话,通常它们允许多个 null 元素。难免有人希望通过在用户尝试插入重复元素时抛出运行时异常的方法来禁止重复的列表,但我们希望这种用法越少越好。
List接口:是Collection接口的子接口,继承了Collection接口中的所有方法。
List接口定义的所有集合中的元素都可以重复,并且还可以保证存取的顺序一致。List接口下的所有集合全部拥有下标。List接口更像数组。
由于List接口规定自己的真实实现类(集合)都拥有下标,因此我们在操作List接口下的所有集合容器的时候,都可以通过下标操作。
因此在List接口中它不仅仅继承到Collection接口中的所有函数,同时java 还根据List的下标的特性,定义了适合List接口的特有函数。
2.1、List的特有方法
List接口中的特有方法都是围绕集合的下标而设计的。
add(int index , Object element )
List接口拥有下标,因此可以像遍历数组的方式遍历List集合:
List接口中的特有的增删改查:
add( int index , Object element )
get(int index)
remove( int index )
set( int index , Object newElement )
2.2、List的特有迭代器
List接口继承了Collection接口,Collection接口中的获取某个集合对应的迭代器的函数,List一定也继承到了。
但是由于List集合有下标,因此Java中针对List这类集合提供了自己特有的迭代器。
ListIterator接口:
ListIterator接口:它是专门针对List接口而设计的,这个迭代器在遍历List集合的时候,可以对这个集合中的元素进行增 删 改 查操作。并且它还可以逆向遍历。
/*
* 演示List接口的特有迭代器
*/
public class ListIteratorDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add("aaaa");
list.add("abc");
list.add("bbbb");
list.add("cccc");
list.add("xyz");
System.out.println("===============使用Iterator迭代===============");
for (Iterator it = list.iterator(); it.hasNext();) {
System.out.println(it.next());
}
System.out.println("===============使用ListIterator 正向迭代===============");
for( ListIterator lit = list.listIterator(); lit.hasNext() ; ){
System.out.println(lit.next());
}
System.out.println("===============使用ListIterator 逆向迭代===============");
for( ListIterator lit = list.listIterator( list.size() ); lit.hasPrevious(); ){
System.out.println(lit.previous());
}
}
}
2.3、List集合的迭代
/*
* 演示List接口的各种遍历
*/
public class ListIteratorDemo2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("aaaa");
list.add("abc");
list.add("bbbb");
list.add("cccc");
list.add("xyz");
System.out.println("===============使用Iterator迭代===============");
for (Iterator it = list.iterator(); it.hasNext();) {
System.out.println(it.next());
}
System.out.println("===============使用ListIterator 正向迭代===============");
for( ListIterator lit = list.listIterator(); lit.hasNext() ; ){
System.out.println(lit.next());
}
System.out.println("===============使用普通for循环===============");
for( int i=0;i<list.size();i++ ){
System.out.println(list.get(i));
}
System.out.println("===============使用foreach循环===============");
for( Object obj : list ){
System.out.println(obj);
}
}
}
2.4、ArrayList介绍
List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)
ArrayList类(集合):它是List接口的一个直接实现类,因此ArrayList集合拥有List接口的所有特性,可以保存重复元素,有下标,它可以保证元素的存取顺序。ArrayList集合的底层使用的数组(可变数组)。
ArrayList集合的特点:
- 它的底层使用的可变数组
- 它有下标,可以按照数组的方式操作ArrayList集合
- 可以保证数据的存取顺序,同时还可以保存重复元素。
- ArrayList集合它增加和删除效率比较低,而查询(遍历)速度较快。
- 所有JDK1.2出现的集合他们的效率都比JDK1.2之前的集合效率高。但是不安全。
2.5、LinkedList介绍
List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
LinkedList集合:它也是List接口的实现类,它的底层使用的链接列表结构(链表)。
数据结构:数据的存储方式,不同的集合容器它们存储数据的方式都不一样。而我们学习众多的集合容器,重点是知道每个集合存储数据的方式即可。不同集合的存储方式不同,导致集合中的数据存取,以及元素能否重复等都不相同。
LinkedList集合它采用的是数据结构中的链表结构:
由于LinkedList集合底层使用的链表结构:
导致LinkedList集合在存储数据的时候可以根据头和尾进行各种操作。可以使用LinkedList集合模拟常见的2种数据结构:
队列结构:先进的先出,后进的后出。(排队买票)
堆栈结构:先进的后出,后进的先出。(乒乓球筒/羽毛球筒/手枪的弹夹)
经常使用LinkedList模拟上述的2种结构:
LinkedList集合:
- 它的底层使用的链表结构
- 有头有尾,其中的方法都是围绕头和尾设计的。
- LinkedList集合可以根据头尾进行各种操作,但它的增删效率高,查询效率低。
- LinkedList集合底层也是线程不安全。效率高。
2.6、Vector介绍
Vector集合是JDK1.0的时候出现的集合,它在jdk1.2的时候被收编到List接口的下面。而这个集合被JDK1.2中的ArrayList集合代替。(victory 胜利)
Vector
类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector
的大小可以根据需要增大或缩小,以适应创建 Vector
后进行添加或移除项的操作。
Vector集合它就是ArrayList集合,可以使用Enumeration迭代Vector集合,但是由于Enumeration迭代器中的方法的名字太长,被Iterator代替。后期如果需要迭代,Vector应该优先考虑使用Iterator迭代。
Vector是jdk1.0出现的集合,它的增删,查询效率都比较低。由于Vector底层是线程安全的。
ArrayList 它的底层是不安全的,因此ArrayList各方面的效率都比Vector高。
三、SET接口
3.1、Set接口介绍
Collection的接口下面有2个直接的子接口:
List:可以保存重复元素,拥有下标。
ArrayList:底层是可变数组,根据下标操作
LinkedList:底层是链表,根据头尾操作
Set:不能保存重复元素,没有下标。并且不保证存取的顺序。
一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2)
的元素对 e1
和 e2
,并且最多包含一个 null 元素。正如其名称所暗示的,此接口模仿了数学上的 set 抽象。
Set接口中没有自己的特有函数,所有的函数全部来自于Collection接口。
Set接口没有下标,不能使用ListIterator迭代。
3.2、HashSet集合
此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。
HashSet集合的底层使用的哈希表结构。并且HashSet集合不保证元素存取一致(无序)。
3.2.1、哈希表介绍
哈希表:
它是一个数据结构,底层依赖的是数组,只是不按照数组的下标操作数组中的元素。需要根据数组中存储的元素的哈希值进行元素操作。
哈希表:
数组+hashCode函数,哈希表保证对象唯一需要依赖对象的hashCode和equals方法。
3.2.2、HashSet保证对象唯一的原因
HashSet保证对象唯一的原因:
保证对象唯一需要依赖于对象的hashCode和equals方法。在给HashSet中保存对象的时候,会先调用对象的hashCode函数,计算哈希值,如果哈希值相同再调用对象的equals方法。如果equals方法返回的true就放弃当前这个对象。
如果hashCode函数返回的值不同,就直接保存这个对象,不会调用equals方法。
结论:
要求以后给哈希表中保存对象的时候,要求当前这个对象所属的类必须复写Object类中的hashCode和equals方法。
3.2.3、※HashSet保存自定义对象
/*
* 给HashSet集合中存储自定义对象,验证哈希表保证唯一的原因
*
*
* 给哈希表中保存自定义对象,由于每次创建的都是新的Person对象,
* 每个Person对象都有自己的唯一的内存地址,而给哈希表中存放这些对象的时候
* 每个对象都需要调用自己从Object类中继承到的hashCode函数,而hashCode函数
* 会根据每个对象自己内存地址计算每个对象哈希值,每个对象的内存地址不同,计算出来的哈希值一定不同。
* 那么就会导致每个对象都可以正常的保存到哈希表中。
*
* 自己定义的对象,有自己所属的类,而这个类继承到了Object的hashCode函数,而这个函数是根据对象内存地址计算哈希值,
* 而我们更希望根据对象自己的特有数据计算哈希值。这时自己定义的类需要复写Object类中的hashCode函数。
*
*/
public class HashSetDemo2 {
public static void main(String[] args) {
//创建集合对象
HashSet set = new HashSet();
set.add(new Person("huaan1",29));
set.add(new Person("huaan2",29));
set.add(new Person("huaan3",29));
set.add(new Person("huaan4",29));
set.add(new Person("huaan5",29));
for (Iterator it = set.iterator(); it.hasNext();) {
System.out.println(it.next());
}
}
}
3.2.4、HashSet总结
HashSet集合存储对象的时候:
- HashSet集合的底层使用的哈希表结构。那么就要求存放的对象比较具备hashCode功能。由于任何一个类的父类都是Object类,而hashCode函数定义在了Object类中,因此所有的对象都具备hashCode功能。
- 如果我们要把一个对象可以正确的存放在HashSet集合中,这个对象所属的类一般都需要复写hashCode函数。建立本类自己的计算哈希值的方式,一般根据对象自己的特有数据计算哈希值。
- 如果在HashSet集合中要保证对象唯一,不能仅仅依靠hashCode函数,还要依赖于对象的equals函数,当hashCode函数计算出来的哈希值相同的时候,还要调用equals方法比较2个对象是否相同。
- 要求自己定义一个类的时候必须复写Object类中的hashCode和equals函数。
3.2.5、LinkedHashSet介绍
具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。注意,插入顺序不 受在 set 中重新插入的 元素的影响。(如果在 s.contains(e) 返回 true 后立即调用 s.add(e),则元素 e 会被重新插入到 set s 中。)
LinkedHashSet集合:
它的底层使用的链表+哈希表结构。它和HashSet集合的区别是:LinkedHashSet是一个可以保证存取顺序的集合,并且LinkedHashSet集合中的元素也不能重复。
LinkedHashSet集合没有自己的特有函数,所有的功能全部继承父类。
3.3 TreeSet介绍
3.3.1、TreeSet介绍
目前为止我们学习三个集合:
ArrayList:它的底层使用的可变数组,可以根据下标操作集合中的元素,可以重复,保证存取顺序。
LinkedList:它的底层是链表,有头有尾,可以根据头尾操作集合中的数据。也可以保存重复数据。
HashSet:它的底层是哈希表,不能保存重复数据,不保证存取顺序。
TreeSet:它是Set接口下的一个间接实现类。它可以保证对象唯一,同时还可以对存放在其中的对象进行排序。
基于 TreeMap 的 NavigableSet[A1] 实现。使用元素的自然顺序[A2] 对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序(构造函数可以传入一个comparator[A3] 实现对象作为参数),具体取决于使用的构造方法。
3.3.2、TreeSet演示
/*
* 演示TreeSet
*/
public class TreeSetDemo {
public static void main(String[] args) {
method_2();
}
//TreeSet的排序
public static void method_2() {
TreeSet set = new TreeSet();
set.add(1);
set.add(-1);
set.add(11);
set.add(333);
set.add(33);
set.add(3);
set.add(10);
set.add(12);
for (Iterator it = set.iterator(); it.hasNext();) {
System.out.println(it.next());
}
}
//演示TreeSet的排序
public static void method_1() {
//需要一个TreeSet集合对象
TreeSet set = new TreeSet();
set.add("abc");
set.add("abc");
set.add("ABC");
set.add("bca");
set.add("bbc");
set.add("cba");
set.add("cba");
set.add("BCA");
set.add("AAAA");
set.add("AAAA");
for(Iterator it = set.iterator(); it.hasNext(); ){
System.out.println(it.next());
}
}
}
TreeSet集合的底层使用的二叉树(红黑树)结构。
3.3.3、树结构介绍
树结构:它也是数据结构中的一种。在计算机领域中树结构指的是倒立的树。
3.3.4、TreeSet集合存放自定义对象
当我们给TreeSet集合中保存对象的时候,TreeSet集合需要对存放进行的对象进行比较大小。通过比较才知道具体当前这个对象应该保存在二叉树中的具体哪个位置上。
给TreeSet集合中保存的所有对象都应该具备比较大小的功能。只有通过对象的比较大小的功能才能确定2个对象的大小,进而TreeSet的底层才可以保证对象的排序顺序。
当我们需要给TreeSet集合中保存对象的时候,对象就必须具备比较大小的功能, 而不需要给TreeSet中保存的对象它可以不具备比较大小的功能。
Java给我们提供一个接口Comparable,这个接口给所有对象提供了一个额外的功能,只要那个类实现了这个接口,那么这个类就可以具备比较大小的功能了。
上述程序的错误原因:
我们需要把一个Student对象保存到TreeSet集合中,那么Student对象就应该具备比较大小的功能,而一个对象需要具备比较大小的功能,需要实现Comparable的接口,而我们自己定义Student类并没有实现这个接口,因此在TreeSet集合的底层,它无法去调用比较大小的功能,进而导致程序错误。
3.4、Comparable接口介绍
TreeSet集合存放对象时需要注意的问题:
需要保证对象具备比较功能,也就是对象所属的类一定要实现Comparable接口,并且需要实现compareTo函数。在compareTo函数中书写具体的比较方式。
TreeSet集合怎么保证对象的唯一:
当给TreeSet集合中保存对象的时候,会调用对象的比较功能compareTo函数,只要这个函数返回的0,就认为2个对象相同,只保存其中的一个。
3.5、Comparator比较器介绍
给TreeSet集合中保存对象的时候,由于TreeSet集合可以对其中保存的对象进行排序。要求保存的对象必须具备比较功能。因此要求给TreeSet集合中保存的对象所属的类必须实现Comparable接口。
需求:把字符串保存到TreeSet集合中,但是要求按照字符串的长度排序。
分析:String类本身已经具备了比较功能,它的比较功能是按照字符串中字符的字典顺序比较的。而现在我们希望按照字符串的长度进行比较。现在String类中的比较功能不适合当前程序的要求了。
由于String类是final修饰的,我们无法去继承这个类,就无法去复写掉String类中的compareTo方法。
上述的问题,不能使用继承解决。Java给我们提供的相应的解决方案:
当我们要给TreeSet集合中保存对象的时候,如果对象具备的比较功能不适合当前我们的需求时,我们可以给TreeSet集合自身传递一个适合我们需求的比较对象。然后让TreeSet集合按照我们传递进去这个对象(比较器对象)对其中需要保存的数据进行比较。
而这个对象就是Comparator对象。
强行对某个对象 collection 进行整体排序 的比较函数。可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序
Comparator接口可以对象集合中的元素进行排序,当我们把这个Comparator接口的实现类型对象丢给集合之后,那么给集合中存放的对象,不会再使用对象自身的比较功能进行大小的比较,而会使用我们传递的Comparator的实现类对象进行比较。
在Comparator接口中的compare方法中接受2个参数,这2个参数就是集合中需要比较大小的某2个对象。
3.6、Collection下的接口和实现类总结
Collection:集合体系的顶层接口:
List:它中可以保存重复元素有下标。
ArrayList:底层是可变数组,增删慢,查询快,不安全,效率高
LinkedList:底层是链表,有头有尾,增删快,查询慢,不安全,效率高
Vector:底层可变数组,什么都慢。但安全。
Set:不能存放重复元素,不保证存取的顺序。
HashSet:底层哈希表。保证对象唯一依赖于对象的hashCode 和 equals方法
LinkedHashSet:底层是哈希表+链表,可以保证存取顺序,没有自己的特有方法
TreeSet:它底层是二叉树,主要是可以对其中的元素进行排序。
Comparable:
它可以让一个对象自身具备比较功能。哪个对象需要具备比较功能,这个对象所属的类就需要实现 Comparable接口。实现其中的compareTo方法。
Comparator:它是单独的比较器,可以把这个对象单独丢给TreeSet集合,那么这时集合中的元素就可以按照当前指定的这个比较器进行比较大小。开发者如果需要使用比较器的时候,需要定义类实现Comparator接口,同时实现其中的compare方法。
四、Map集合
4.1Map集合
4.1.1、Map集合介绍
Collection接口下的所有集合中保存的对象都是孤立的。对象和对象之间并没有任何关系存在。
在生活中对象和对象之间必然会一定的联系存在。而我们学习的Collection接口下的所有子接口或者实现类(集合)它们中的确可以保存对象,但是没有办法维护这些对象之间的关系。
而学习的Map集合,它也是存放对象的,但是可以对其中的对象进行关系的维护。
把Collection集合称为单列集合。Map被称为双列集合。
在Map集合中保存的key和value这样的具备一定对应关系的一组(一对)数据。而每个key只能对应唯一的一个value值并且所有的key不能重复。
4.1.2、Map集合中的方法
put 方法它会把当前的key和value保存到map集合中。
put 方法可以把key和value保存到map集合中,但是如果当前指定的key已经在map中存在,此时只会用指定的key找到对应的value,然后把value覆盖掉。
remove是根据key删除当前key对应的value这一组数据,而不是只删除value。
注意:只能根据Map集合中的key删除集合中的一组数据,不能根据value来删除数据。
获取也只能根据key获取value,不能通过value获取key。
4.1.3、Map集合的遍历
基于红黑树(Red-Black tree)的 NavigableMap
实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator
进行排序,具体取决于使用的构造方法。
TreeMap:底层是二叉树,二叉树主要作用在key上。
给TreeMap集合中保存自定义对象,自定义对象作为TreeMap集合的key值。由于TreeMap底层使用的二叉树,其中存放进去的所有数据都需要排序,要排序,就要求对象具备比较功能。对象所属的类需要实现Comparable接口。或者给TreeMap集合传递一个Comparator接口对象。
常用的三个集合:ArrayList: HashSet: HashMap:
4.1.4、Hashtable介绍
Hashtable:它是JDK1.0版本中提供的一个双列集合,它后期被HashMap代替。它的使用和HashMap一致。
Hashtable是安全的,但是效率低。HashMap不安全,效率高。
4.1.5、使用entrySet遍历
在Map集合中提供的entrySet方法,它可以获取到当前Map集合中的key和value的对应关系对象(映射对象)。
在Map集合中把key和value看成一组数据,然后Java中把这一组数据又一次封装成了一个新的对象,这个对象就是key和value的对应关系对象。然后把这些对象保存到了Set集合中。Set集合中保存的是key和value对应关系对象。
4.1.6、获取所有的value值
4.1.7、Map的练习
/*
* 需求:统计一个字符串中每个字符的出现次数
* 分析:
* 一个字符串中可以有任意的字符数据,针对每个字符都需要一个计数器,
* 然后从前往后遍历这个字符串,取出每个字符,然后给对应的计数器+1.
*
* 可以使用字符作为Map集合的key值,然后使用字符出现的次数作为Map的value值
* 开始遍历字符串,取出每个字符,然后判断它作为key在map中是否存在,如果不存在,说明
* 当前这个字符是第一次出现,就把这个字符和次数1保存到map中,继续遍历字符串,取出后续的它字符
* 通过判断是否存在,如果存在,就通过当前这个字符取出value值(次数),给次数+1,在保存到map中
*/
public class MapTest {
public static void main(String[] args) {
String s = "sjfljslkdf";
//需要一个Map集合
Map map = new HashMap();
//遍历字符串
for( int i=0;i<s.length();i++ ){
//取出字符串中的每个字符
char ch = s.charAt(i);
//把当前取出的这个字符作为key,判断在map中是否存在
if( !map.containsKey(ch) ){
//判断成立说明当前的字符没有出现过去,目前是第一次出现,就把这个字符和次数1保存到集合中
map.put(ch, 1);
}else{
//执行到else中说明当前的字符一定在map的key中存在,取出对应的value值(次数),在给次数+1
int count = (Integer)map.get(ch);
map.put(ch, count+1);
}
}
//遍历Map集合
Set keySet = map.keySet();
for( Iterator it = keySet.iterator() ; it.hasNext() ; ){
Object key = it.next();
Object value = map.get(key);
System.out.println("\""+key+"\"出现的次数是:"+value);
}
}
}
集合:集合也是对象。集合中本身就是存储对象。集合中也可以继续存储集合。
4.2、HashMap演示
Map接口下有2个实现类:
HashMap:底层一定是哈希表。哈希表主要作用在key上。
HashMap集合中的所有方法全部来自于Map接口,对Map接口中的方法做了全部的实现。
public class HashMapDemo {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("张三", "北京");
map.put("李四", "上海");
map.put("王五", "上海");
map.put("赵柳", "武汉");
map.put("田七", "东莞");
map.put("老王", "日本");
for( Iterator it = map.keySet().iterator() ; it.hasNext() ; ){
Object key = it.next();
Object value = map.get(key);
System.out.println(key+"......"+value);
}
System.out.println("=========================");
Set keySet = map.keySet();
for( Object key : keySet ){
Object value = map.get(key);
System.out.println(key+"============="+value);
}
}
}
自定义对象作为HashMap的key值,需要注意什么问题:
Map集合中保存的一组对象(一对),不管是key还是value都是对象。我们现在可以自己定义一个对象作为HashMap集合的key值。HashMap集合的key是需要使用哈希表来保证唯一。自定义对象就需要复写Object类中的hashCode和equals方法。
4.3、TreeMap演示
基于红黑树(Red-Black tree)的 NavigableMap
实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator
进行排序,具体取决于使用的构造方法。
TreeMap:底层是二叉树,二叉树主要作用在key上。
给TreeMap集合中保存自定义对象,自定义对象作为TreeMap集合的key值。由于TreeMap底层使用的二叉树,其中存放进去的所有数据都需要排序,要排序,就要求对象具备比较功能。对象所属的类需要实现Comparable接口。或者给TreeMap集合传递一个Comparator接口对象。
常用的三个集合:ArrayList: HashSet: HashMap:
4.4、Hashtable介绍
Hashtable:它是JDK1.0版本中提供的一个双列集合,它后期被HashMap代替。它的使用和HashMap一致。
Hashtable是安全的,但是效率低。HashMap不安全,效率高。