一、集合的由来:
我们学习的是面向对象语言,而面向对象语言对事物的描述是通过对象体现的,为了方便对多个对象进行操作,我们就必须把这多个对象进行存储。
而要想存储多个对象,就不能是一个基本的变量,而应该是一个容器类型的变量,在我们目前所学过的知识里面,有哪些是容器类型的呢?
数组和StringBuffer。但是呢?StringBuffer的结果是一个字符串,不一定满足我们的要求,所以我们只能选择数组,这就是对象数组。
而对象数组又不能适应变化的需求,因为数组的长度是固定的,这个时候,为了适应变化的需求,Java就提供了集合类供我们使用。
数组和集合的区别?
A:长度区别
数组的长度固定
集合长度可变
B:内容不同
数组存储的是同一种类型的元素
而集合可以存储不同类型的元素
C:元素的数据类型问题
数组可以存储基本数据类型,也可以存储引用数据类型
集合只能存储引用类型
刚说过集合是存储多个元的,但是呢,存储多个元素我们也是有不同需求的:比如说,我要这多个元素中不能有相同的元素,
再比如说,我要这多个元素按照某种规则排序一下。针对不同的需求,Java就提供了不同的集合类,这样呢,Java就提供了很多个集合类。
这多个集合类的数据结构不同,结构不同不重要的,重要的是你要能够存储东西,并且还要能够使用这些东西,比如说判断,获取等。
既然这样,那么,这多个集合类是有共性的内容的,我们把这些集合类的共性内容不断的向上提取,最终就能形成集合的继承体系结构。
数据结构:数据的存储方式。
二、Collection集合
是集合的顶层接口,它的子体系有重复的,有唯一的,有有序的,有无序的。
Collection的功能概述:
1,添加功能
boolean add(Object obj):添加一个元素
booleanaddAll(Collection c):添加一个集合的元素
2,删除功能
void clear():移除所有元素
booleanremove(Object o):移除一个元素
booleanremoveAll(Collection c):移除一个集合的元素(是一个还是所有)
3,判断功能
boolean contains(Object o):判断集合中是否包含指定的元素
booleancontainsAll(Collection c):判断集合中是否包含指定的集合元素(是一个还是所有)
booleanisEmpty():判断集合是否为空
4,获取功能
Iterator<E>iterator()(重点)
5,长度功能
int size():元素的个数
面试题:数组有没有length()方法呢?字符串有没有length()方法呢?集合有没有length()方法呢?
6,交集功能
boolean retainAll(Collection c):两个集合都有的元素?思考元素去哪了,返回的boolean又是什么意思呢?
7,把集合转换为数组
Object[] toArray()
boolean addAll(Collection c):添加一个集合的元素
boolean removeAll(Collection c):移除一个集合的元素(是一个还是所有)
只要有一个元素被移除了,就返回true。
boolean containsAll(Collection c):判断集合中是否包含指定的集合元素(是一个还是所有)
只有包含所有的元素,才叫包含
boolean retainAll(Collection c):两个集合都有的元素?思考元素去哪了,返回的boolean又是什么意思呢?
假设有两个集合A,B。
A对B做交集,最终的结果保存在A中,B不变。
返回值表示的是A是否发生过改变。
集合的遍历。其实就是依次获取集合中的每一个元素。
Object[] toArray():把集合转成数组,可以实现集合的遍历
Iterator iterator():迭代器,集合的专用遍历方式
Objectnext():获取元素,并移动到下一个位置。
NoSuchElementException:没有这样的元素,因为你已经找到最后了。
booleanhasNext():如果仍有元素可以迭代,则返回 true。
8,迭代器的源码解析
public interfaceInteator {
boolean hasNext();
Object next();
}
public interfaceIterable {
Iterator iterator();
}
public interfaceCollection extends Iterable {
Iterator iterator();
}
public interfaceList extends Collection {
Iterator iterator();
}
public classArrayList implements List {
public Iterator iterator() {
return new Itr();
}
private class Itr implements Iterator {
publicboolean hasNext() {}
public Object next(){}
}
}
三、 List集合
1,List的特有功能:
A:添加功能
voidadd(int index,Object element):在指定位置添加元素
B:获取功能
Objectget(int index):获取指定位置的元素
C:列表迭代器
ListIteratorlistIterator():List集合特有的迭代器
D:删除功能
Objectremove(int index):根据索引删除元素,返回被删除的元素
E:修改功能
Objectset(int index,Object element):根据索引修改元素,返回被修饰的元素
列表迭代器:
ListIteratorlistIterator():List集合特有的迭代器
该迭代器继承了Iterator迭代器,所以,就可以直接使用hasNext()和next()方法。
特有功能:
Objectprevious():获取上一个元素
booleanhasPrevious():判断是否有元素
注意:ListIterator可以实现逆向遍历,但是必须先正向遍历,才能逆向遍历,所以一般无意义,不使用。
问题?
我有一个集合,如下,请问,我想判断里面有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现。
ConcurrentModificationException:当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
产生的原因:
迭代器是依赖于集合而存在的,在判断成功后,集合的中新添加了元素,而迭代器却不知道,所以就报错了,这个错叫并发修改异常。
其实这个问题描述的是:迭代器遍历元素的时候,通过集合是不能修改元素的。
如何解决呢?
A:迭代器迭代元素,迭代器修改元素
元素是跟在刚才迭代的元素后面的。
B:集合遍历元素,集合修改元素(普通for)
元素在最后添加的。
2,List集合的特有遍历功能
A:由size()和get()结合。
B:代码演示
创建集合对象
List list =new ArrayList();
创建并添加元素
list.add("hello");
list.add("world");
list.add("java");
遍历集合
Iterator it =list.iterator();
while(it.hasNext()){
Strings =(String) it.next();
System.out.println(s);
}
System.out.println("----------");
for(int x=0;x<list.size(); x++) {
Strings =(String) list.get(x);
System.out.println(s);
}
3,并发修改异常
A:出现的现象
迭代器遍历集合,集合修改集合元素
B:原因
迭代器是依赖于集合的,而集合的改变迭代器并不知道。
C:解决方案
a:迭代器遍历,迭代器修改(ListIterator)
元素添加在刚才迭代的位置
b:集合遍历,集合修改(size()和get())
元素添加在集合的末尾
4 List的子类特点:
ArrayList:
底层数据结构是数组,查询快,增删慢
线程不安全,效率高
Vector:
底层数据结构是数组,查询快,增删慢
线程安全,效率低
LinkedList:
底层数据结构是链表,查询慢,增删快
线程不安全,效率高
4.1 Vector的特有功能:
1:添加功能
public void addElement(Object obj) -- add()
2:获取功能
public Object elementAt(int index) -- get()
public Enumeration elements() -- Iterator iterator()
booleanhasMoreElements() hasNext()
ObjectnextElement() next()
4.2LinkedList的特有功能:
A:添加功能
publicvoid addFirst(Object e)
publicvoid addLast(Object e)
B:获取功能
publicObject getFirst()
publicObejct getLast()
C:删除功能
publicObject removeFirst()
publicObject removeLast()
四、 泛型类
1,泛型介绍
定义:是一种把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。参数化类型,把类型当作参数一样的传递。
格式:
<数据类型>
此处的数据类型只能是引用类型。
好处:
A:把运行时期的问题提前到了编译期间
B:避免了强制类型转换
C:优化了程序设计,解决了黄色警告线
泛型高级(通配符)
?:任意类型,如果没有明确,那么就是Object以及任意的Java类了
? extends E:向下限定,E及其子类
? super E:向上限定,E极其父类
泛型在哪些地方使用呢?
看API,如果类,接口,抽象类后面跟的有<E>就说要使用泛型。一般来说就是在集合中使用。
2,ArrayList遍历
ArrayList存储字符串并遍历。要求加入泛型,并用增强for遍历。
A:迭代器
B:普通for
C:增强for
public classArrayListDemo {
public static void main(String[] args) {
创建集合对象
ArrayList<String> array =new ArrayList<String>();
创建并添加元素
array.add("hello");
array.add("world");
array.add("java");
遍历集合
迭代器
Iterator<String> it =array.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
System.out.println("------------------");
普通for
for (int x = 0; x <array.size(); x++) {
String s = array.get(x);
System.out.println(s);
}
System.out.println("------------------");
增强for
for (String s : array) {
System.out.println(s);
}
}
}
五、Set集合
Set无序(存储顺序和取出顺序不一致),唯一
1,HashSet集合
它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。
注意:虽然Set集合的元素无序,但是,作为集合来说,它肯定有它自己的存储顺序,
而你的顺序恰好和它的存储顺序一致,这代表不了有序,你可以多存储一些数据,就能看到效果。
HashSet:存储字符串并遍历
问题:为什么存储字符串的时候,字符串内容相同的只存储了一个呢?
通过查看add方法的源码,我们知道这个方法底层依赖 两个方法:hashCode()和equals()。
步骤:
首先比较哈希值
如果相同,继续走,比较地址值或者走equals()
如果不同,就直接添加到集合中
按照方法的步骤来说:
先看hashCode()值是否相同
相同:继续走equals()方法
返回true: 说明元素重复,就不添加
返回false:说明元素不重复,就添加到集合
不同:就直接把元素添加到集合
如果类没有重写这两个方法,默认使用的Object()。一般来说不同相同。
而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。
HashSet集合的add()方法的源码
interfaceCollection {
}
interface Setextends Collection {
...
}
class HashSetimplements Set {
private static final Object PRESENT = newObject();
private transient HashMap<E,Object>map;
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {e=hello,world
return map.put(e, PRESENT)==null;
}
}
class HashMapimplements Map {
public V put(K key, V value) {key=e=hello,world
看哈希表是否为空,如果空,就开辟空间
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
判断对象是否为null
if (key == null)
return putForNullKey(value);
int hash = hash(key); 和对象的hashCode()方法相关
在哈希表中查找hash值
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e!= null; e = e.next) {
这次的e其实是第一次的world
Object k;
if (e.hash == hash && ((k =e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
走这里其实是没有添加元素
}
}
modCount++;
addEntry(hash, key, value, i); 把元素添加
return null;
}
transient int hashSeed = 0;
final int hash(Object k) {k=key=e=hello,
int h = hashSeed;
if (0 != h && k instanceofString) {
returnsun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode(); 这里调用的是对象的hashCode()方法
This function ensures that hashCodesthat differ only by
constant multiples at each bitposition have a bounded
number of collisions (approximately 8at default load factor).
h ^= (h >>> 20) ^ (h>>> 12);
return h ^ (h >>> 7) ^ (h>>> 4);
}
}
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("world");
HashSet集合存储自定义对象并遍历。如果对象的成员变量值相同即为同一个对象
注意了:
你使用的是HashSet集合,这个集合的底层是哈希表结构。
而哈希表结构底层依赖:hashCode()和equals()方法。
如果你认为对象的成员变量值相同即为同一个对象的话,你就应该重写这两个方法。
如何重写呢?不同担心,自动生成即可。
2, LinkedHashSet:
底层数据结构由哈希表和链表组成。
哈希表保证元素的唯一性。
链表保证元素有素。(存储和取出是一致)
3,TreeSet:
能够对元素按照某种规则进行排序。
排序有两种方式
A:自然排序
B:比较器排序
TreeSet集合的特点:排序和唯一
通过观察TreeSet的add()方法,我们知道最终要看TreeMap的put()方法。
TreeSet的add()方法的源码解析:
interfaceCollection {...}
interface Setextends Collection {...}
interfaceNavigableMap {
}
class TreeMapimplements NavigableMap {
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); type (and possibly null) check
root = new Entry<>(key,value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw newNullPointerException();
Comparable<? super K> k =(Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = newEntry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
}
class TreeSetimplements Set {
private transientNavigableMap<E,Object> m;
public TreeSet() {
this(new TreeMap<E,Object>());
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
}
真正的比较是依赖于元素的compareTo()方法,而这个方法是定义在Comparable里面的。
所以,你要想重写该方法,就必须是先 Comparable接口。这个接口表示的就是自然排序。
六、Collection集合总结
Collection
|--List 有序,可重复
|--ArrayList
底层数据结构是数组,查询快,增删慢。
线程不安全,效率高
|--Vector
底层数据结构是数组,查询快,增删慢。
线程安全,效率低
|--LinkedList
底层数据结构是链表,查询慢,增删快。
线程不安全,效率高
|--Set 无序,唯一
|--HashSet
底层数据结构是哈希表。
如何保证元素唯一性的呢?
依赖两个方法:hashCode()和equals()
开发中自动生成这两个方法即可
|--LinkedHashSet
底层数据结构是链表和哈希表
由链表保证元素有序
由哈希表保证元素唯一
|--TreeSet
底层数据结构是红黑树。
如何保证元素排序的呢?
自然排序
比较器排序
如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定