java集合类,是用来保存对象的容器,存放于 java.util 包中。
注意:①集合只能存放对象。比如你存一个int型数据1放入集合中,其实它是自动转换成Integer类后存入的,Java中每一种基本类型都有对应的引用类型。
②集合存放的是多个对象的引用,对象本身还是存放在堆内存中。
③集合可以存放不同类型,不限数量的数据类型。
集合框架的完整结构。
- 虚线是接口
- 点线框抽象类
- 实线是实现类
- 粗实线是常用类
集合框架的简化结构
在java的集合框架结构中,集合成两大类
上图集合类,除了Map系列的集合,即左边集合都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要hasNext(),next(),remove()三种方法。它的一个子接口ListIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。也就是说如果实现Iterator接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会再被遍历到,通常无序集合实现的都是这个接口,比如HashSet;而那些有序的集合,实现的一般都是ListIterator接口,实现这个接口的集合可以实现双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList。
注意:我们可以在源码中追溯到集合的顶层接口,比如Collection接口,可以看到它继承的是类Iterable。Iterator是Java集合的顶层接口。(不包含map系列集合,Map接口是map系列集合的顶层接口)
Public interface Collection<E> extends Iterable<E>{...}
那就得说明一下Iterator和Iterable的区别:
Iterable:存放于java.lang包中。
我们可以看到,里面封装了一个Iterator接口。所以只要实现了Iterator接口,就可是使用Iterator迭代器了。
1 collection接口 线性集合的顶层接口,一般不使用这个接口,使用这个接口的子接口List和Set
2 Map接口 键值映射的集合的接口
实头虚线(produces)表示通过方法可以把Map转成Collection
在java的集合中经常使用的接口有三个
1 List接口
Collection接口的子接口,有序的,可重复的线性集合接口。
List接口的三个典型实现:
① List list = new ArrayList();
底层数据结构是数组,查询快,增删慢;线程不安全,效率高
② List list = new Vector();
底层数据结构是数组,查询快,增删慢;线程安全,效率低,几乎已经淘汰了这个集合
③List list = new LinkedList();
底层数据结构是链表,查询慢,增删快;线程不安全,效率高
怎么更好理解记忆呢?
数组就像身上编了号站成一排的人,要找到第10个人很容易,根据人身上编号很快就能找到。但是插入、删除时,要往某个位置插入或删除一个人时,后面的人身上编号都要变。所以比较慢。当然,加入或删除的人在始末也很快。
链表就像手牵着手站成一圈的人,要找第10个人不容易,必须从第一个人一个个数过去。但是插入、删除很快。插入时只要解开两个人的手,并重新牵上新加进来的人的手就可以。删除一样的道理。
2 Set接口
Collection接口的子接口,无序的,不可重复的线性集合接口
//将ArrayList集合作为Collection的实现类
Collection collection = new ArrayList();
//添加元素
collection.add("Tom");
//删除指定元素
collection.remove("Tom");
//删除所有元素
Collection c = new ArrayList();
c.add("Bob");
Collection.removeAll(c);
//检测是否存在某个元素
collection.contains("Tom");
//判断是否为空
collection.isEmpty();
//增强for循环遍历集合
for(Object obj : collection)
System.out.println(obj);
//利用迭代器Iterator
Iterator iterator = collection.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
3 Map接口
键值映射的集合
1.List接口的实现类-ArrayList
ArrayList是一个大小可变的数组的实现方式的集合对象。
创建ArrayList类的实例
使用默认构造方法创建ArrayList实例。
ArrayList list = new ArrayList();//存放Object对象
获得了一个容器对象,叫list.
<E>这个是泛型。泛型作用指定这个集合的容器可以保存的数据类型。
ArrayList<String> list = new ArrayList<String>();//只能存放String类型
list.add();//在ArrayList中add方法按索引保存数据 public boolean add(E e);
list.add(1,"aa");//在索引为1的位置存放”aa” public boolean add(int index,E e);
list.set(2,"bb");//在指定位置替换元素;
现在创建集合时都会指定泛型。
ArrayList<泛型> list = new ArrayList<泛型>();
指定泛型之后,集合对象的add方法的参数都会根据泛型就行变化
在将对象保存到集合中时会自动为对象分配一个索引。
从集合中取对象-get,get按对象的索引进行取值
Public E get(int index); //index是索引。
String s = list.get(1);
Public E remove(int index);
List.remove(1);
Public Boolean remove(Object o);
list.remove(“ad”);
当使用remove(Object)方法移除对象时,如何判断是不是同一个对象?调用equals方法。
Contains(Object o)还是调用equas方法。
indexOf(Object o) 还是调用equas方法。
另外,遍历ArrayList时有两种遍历方式,Iterator迭代器和foreach循环,后者所用居多。
重写equals方法讲解
相等是指两个对象是否具有相同的类型和属性值
同一是指两个引用是否指向同一个对象。
Object类中的equals方法原型为public Boolean equals(Object o),它是比较两个对象是否同一(即比较内存地址),并不是相等。
对象具有哈希码值。
Public int hashCode(); 返回此对象的一个哈希码值。
由Object类定义的hashCode方法会针对不同的对像返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的)
重写equals方法的目的是判断两个对象的内容是否相同。
此方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规协定。该协定声明相等对象(地址相同的对象,如果不相同需另作处理)必须具有相等的哈希码。(重写hashCode方法目的是遵守规定,即即使两个对象地址不相同,重写了equals方法后,具有相同的内容,比较后得出的结果为相同,为了不违反协议,需要重写hashCode方法使他们的hashCode值相同(一般系统可以自动生成[系统也是根据两个对象的内容是否相同而得出是否生成相同的hashCode值,如果相同则生成相同的hashCode值],不需手写))
故判断两个对象是否相同通俗意义上讲是只关注这两个对象之间的内容是否相同,如果是同一地址对象,内容当然相同。如果不是同一地址对象,则equals方法不起作用了,因为equals方法比较的是地址,而要使用equals方法则需要重写Object类的equals方法,即在方法中比较对象的各参数类型和参数值是否相同,这样就可以使用equals方法比较内容了。这样就可以说两个不同地址的对象相同了。但是还需根据java的hashCode方法常规协定,需使这两个对象的hashCode也相同,为此还需重写hashCode方法以遵守这个协定。
特别指出利用equals比较八大包装对象(byte,short,int,long,float,double,boolean,char)和String类时,默认比较的是值,在比较其他自定义对象时都是比较的引用地址。
集合对象的排序
排序是使用Collections类的静态方法sort进行集合的排序,根据元素的自然顺序对指定列表按升序排序。
public static <T extends Comparable<? super T>> void sort (List<T> list) ;//必须实现Comparable接口
public interface Comparable<T>//此接口强行对实现它的每个类进行整体排序。这种排序称为类的自然排序。类的compareTo方法被称为它的自然比较方法。
Collections.sort(list); //Conllections类
问:字符串为什么能够按照自然序排序?因为它实现了Compareble<String>接口
Public static <T> void sort(List<T> list,Comparator<? Super T> c)
根据指定比较器产生的顺序对指定列表进行排序。
提供了两种排序比较方式,提供了两种比较器接口
1自然顺序(自然顺序)
Comparable内置比较器接口
String,和8中数据类型的包装类已经实现了Comparable接口
2外部比较规则(扩展顺序)
Comparator外置比较器接口
Comparable内置比较器接口
就只有一个方法。compareTo方法,返回正数,0,负数
Public interface Comparable<T>{
Public int compareTo(T o);
}
当我们编写的类要使用内置比较器时需实现Comparable接口
Public class Goods implements Comparable<Goods>{
@Override
public int compareTo(Goods o){
return this.goodsId -o.goodsId;//-(this.goodsId-o.goodsId)
}
}
扩展排序规则需靠Comparator外置比较器接口
Public interface Comparator<T>{
Int compare(T o1,T o2);
}
Comparator里面也只有一个抽象方法compare,此方法将决定扩展排序的规则。
Collections.sort(list,new Comparator<String>(){
@Override
public int compare(String o1,String o2){
return o1.compareTo(o2);//-(o1.compareTo(o2)) 反序(降序)
return o1.length()-o2.length();
}
}//匿名内部类
)
2. Set hashSet = new HashSet();
① HashSet不能保证元素的顺序;不可重复;不是线程安全的;集合元素可以为NULL;
② 其底层其实是一个数组,存在的意义是加快查询速度。我们知道在一般的数组中,元素在数组中的索引位置是随机的,元素的取值和元素的位置之间不存在确定的关系,因此,在数组中查找特定值时,需要把查找值和一系列的元素进行比较,此时的查询效率依赖于查找过程中比较的次数。而HashSet集合底层数组的索引和值有一个确定的关系:index=hash(value),那么只需要调用这个公式,就能快速的找到元素和索引。
③ 对于HashSet:如果两个对象通过equals()方法返回true,这两个对象的hashCode值也应该相同。当向HashSet集合存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到对象的hashCode值,然后根据hashCode值决定该对象在HashSet的存储位置。如果hashCode值不同,直接把该元素存储到hashCode()指定的位置。如果hashcode值相同,那么会继续判断该元素和集合对象的equals()做比较,若equals为true,则视为同一个对象,不保存在hashCode中;若equals为false,则存储在之前对象同槽位的链表上,这非常麻烦,我们应该约束这种情况,即保证:如果两个对象通过equals()方法返回true,这两个对象的hashCode值也相同。
注意:每一个存储在哈希表中的对象,都得提供hashCode()和equals()方法的实现,用来判断是否是同一个对象。对于HashSet集合,我们要保证如果两个对象通过equals()方法返回true,这两个对象的hashCode值也应该相同。
3.Set linkedHashSet = new LinkedHashSet();
①不可重复,有序
因为底层采用链表和哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一。
4.Set treeSet = new TreeSet();
TreeSet有序;不可重复,底层采用红黑树算法,擅长于范围查询。