day18
一、增加一个接口:java.lang.Iterable接口
JDK1.5增加
它有一个抽象方法:Iterator iterator()
实现这个接口允许对象成为 "foreach" 语句的目标
Collection从JDK1.5之后开始继承Iterable接口。
二、java.util.Iterator接口在哪里实现的?
//左边是Iterator接口,说明右边一定创建了一个Iterator接口的实现类对象,否则iterator调用方法是没有方法体可以执行
Iterator iterator = list.iterator();
跟踪源代码:list.iterator()
public Iterator<E> iterator() {
return new Itr();//Itr是一个内部类
}
跟踪每一种Collection的集合发现,所有的集合在内部都一个内部类来实现java.util.Iterator接口。
为什么?
(1)每一种集合的内部实现物理结构不一样,有的是数组有的是链表等,迭代方式不一样,无法给出统一的实现
(2)迭代的作用,为某个集合迭代,遍历元素,那么它只为某类集合服务,迭代时又需要访问集合的内部(private)的元素, 所以我们设计为内部类更合适。
Set:是Collection另一个子接口
一个不包含重复元素的 collection。
更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。
正如其名称所暗示的,此接口模仿了数学上的 set 抽象。
Set的方法都是继承自Collection。
Set的实现类们:
1、HashSet:元素是完全无序的,它的顺序是由hash值来决定的。
HashSet:元素的存储和是否重复要依赖于元素的:hashCode()和equals()方法
2、TreeSet:元素是按照“大小”顺序排列的,和添加顺序无关。
TreeSet:元素的存储和是否重复要依赖于元素的比较大小的方法:int compareTo(Object o)或指定的比较器的int compare(Object o1,Object o2)
3、LinkedHashSet:是HashSet的子类,但是比HashSet要多一个“记录元素添加顺序的功能”,因此它添加和删除的效率比HashSet相对低。
当你既想要元素不重复,又想要按照添加的顺序时,选择LinkedHashSet。如果你只是想要不重复,那么选择HashSet,如果你执行想要添加顺序,选ArrayList等。
TreeSet:
(1)按照元素的自然顺序排列:要求元素必须实现java.lang.Comparable接口
例如:String实现了java.lang.Comparable接口,
它是按照字符的Unicode编码值来排序。
(2)按照指定的定制比较器的规则排序,要求指定一个java.util.Comparator接口的实现类对象
例如:专门为比较String对象,定制了一个比较器java.text.Collator实现了java.util.Comparator接口
Map:
是不同于Collection一种集合,它是用来存储“键值对——映射关系——(key,value)”
将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
Map接口的常用方法:
1、添加
put(Object key, Object value)
任意Java的引用数据类型都可以作为key和value的类型,但是最常见的是Integer和String比较多的作为key的类型。
putAll(Map map):把另一个map集合的映射关系添加到当前Map中
2、获取映射关系的对数
int size()
3、查找
boolean containsKey(Object key)
boolean containsValue(Object value)
V get(Object key) :根据key获取value
boolean isEmpty()
4、删除
V remove(Object key) :根据key移除一对映射关系,并且返回对应的value
void clear()
5、遍历
(1)Set keySet():
因为Set不允许有重复元素,因为Map的所有key也是要求不允许重复的。
(2)Collection values()
因为value是可以重复的,所以这里不能用Set
(3)Set entrySet()
因为key不重复,所以entry是不重复的
Entry类型是Map中的一个内部接口类型,映射项(键-值对)。它的实现类在Map的实现类中,以内部类的形式存在。
Map接口的实现类们:
1、HashMap:哈希表
如何确定key的不重复和顺序的:依据key的 hashCode 方法和 equals 方法
2、TreeMap
如何确定key的不重复和顺序的:依据key的自然顺序或定制比较器的规则
因此要求要么key类型实现了java.lang.Comparable接口,并重写int compareTo(Object obj)
要么在创建TreeMap时指定定制比较器对象(它是实现java.util.Comparator接口,并重写int compare(Object o1, Object o2)的对象)
3、LinkedHashMap
LinkedHashMap是HashMap的子类,比HashMap多维护了“添加”的顺序。
4、Hashtable:哈希表
如何确定key的不重复和顺序的: hashCode 方法和 equals 方法
5、Properties
Properties是Hashtable的子类,是一种特殊的集合,特殊在它的key和value的类型都是String
HashMap和Hashtable:
Hashtable:旧版的,线程安全的,不允许key和value为null值
HashMap:新版的,线程不安全的,运行key和value为null值
关系:
HashSet和HashMap:HashSet其实就是一个HashMap
添加到HashSet中的元素是作为HashMap的key,他们的value是共享了一个PRESENT(是Object类型的常量对象)
TreeSet和TreeMap:TreeSet其实就是一个TreeMap
添加到TreeSet中的元素是作为TreeMap的key,他们的value是共享了一个PRESENT(是Object类型的常量对象)
LinkedHashSet和LinkedHashMap:LinkedHashSet其实就是一个LinkedHashMap
添加到LinkedHashSet中的元素是作为LinkedHashMap的key,他们的value是共享了一个PRESENT(是Object类型的常量对象)
JDK1.8:HashMap的底层实现是:数组+链表/红黑树
1、复杂回答v1.0
(1)映射关系的类型
添加到HashMap1.8种的映射关系的对象是HashMap.Node类型,或者是HashMap.TreeNode类型。
它也是Map.Entry接口的实现类。
(2)映射关系添加到table[i]中时,如果里面是链表,新的映射关系是作为原来链表的尾部
“七上八下”:JDK1.7在上,JDK1.8在下
为什么要在下面,因为如果是红黑树,那么是在叶子上,保持一致,都在下面。
(3)扩容的时机不同
第一种扩容:元素个数size达到阈值threshod = table.length * 加载因子 并且table[i]是非空的
第二种扩容:当某个桶table[index]下面的结点的总数原来已经达到了8个,这个时候,要添加第9个时,会检查
table.length是否达到64,如果没达到就扩容。如果添加第10个时,也会检查table.length是否达到64,如果没达到就扩容。
为什么,因为一旦扩容,所有的映射关系要重新计算hash和index,一计算原来table[index]下面可能就没有8个,或新的映射关系也可能不在table[index],
这样就可能均匀分布。
(4)什么时候从链表变成红黑树
当table[index]下面结点的总数已经达到8个,并且table.length也已经达到64,那么再把映射关系添加到
table[index]下时,就会把原来的链表修改红黑树
(5)什么时候会从红黑树变为链表
当删除映射关系时table[index]下面结点的总数少于6个,会把table[index]下面的红黑树变回链表。
2、put的源代码v2.0
第一步:计算key的hash,用了一个hash()函数,目的得到一个比hashCode更合理分布的一个int值
第二步:如果table是空的,那么把table初始化为长度为16的数组,阈值初始化为= 默认的长度16 * 默认的加载因子0.75 = 12
DEFAULT_INITIAL_CAPACITY:默认的初始化容量16
DEFAULT_LOAD_FACTOR:默认加载因子 0.75
第三步:查看table[index]是否为空,如果为空,就直接new一个Node放进去
index = hash的int值 & table.length-1
第四步:先查看table[index]的根节点的key是否与新添加的映射关系的key是否“相同”,如果相同,就用新的value替换原来的value。
第五步:如果table[index]的根节点的key与新添加的映射关系的key不同,
还要看table[index]根结点的类型是Node还是TreeNode类型,
如果是Node类型,那么就查看链表下的所有节点是否有key与新添加的映射关系的key是否“相同”,如果相同,就用新的value替换原来的value。
如果是TreeNode类型,那么就查看红黑树下的所有叶子节点的key是否与新添加的映射关系的key是否“相同”,如果相同,就用新的value替换原来的value。
第六步:如果没有找到table[index]有结点与新添加的映射关系的key“相同”,那么
如果是TreeNode类型,那么新的映射关系就创建为一个TreeNode,连接到红黑树中
如果是Node类型,那么要查看链表的结点的个数,是否达到8个,如果8个,并且table.length小于64,那么先扩容。
TREEIFY_THRESHOLD:树化阈值8
MIN_TREEIFY_CAPACITY:最小树化容量64
UNTREEIFY_THRESHOLD:反树化,从数变为链表的阈值6
泛型:Generic,JDK1.5引入
1、为什么要泛型?
(1)没有泛型很麻烦,总要强制类型转换
(2)没有泛型不安全
2、泛型是什么?
生活中比喻:泛型好比是瓶子等容器上的商标,或者中药店抽屉上的标签。
集合中的泛型:设计集合之初,是不知道将来用来装什么对象,如果使用者想要表明这个集合用来装什么,就可以贴标签。
实际上泛型是什么呢?
泛型是指参数化的类型
现在设计集合等类型时,不知道其中的元素等的类型,那么把这个类型设计为参数,让使用者决定
class ArrayList<E>:E就是泛型形参
ArrayList<String>:这个String就泛型实参
为了区别:
形参:数据形参和类型形参
数据形参:方法签名中,声明方法时getMax(int a, int b)
泛型形参:在类或接口名后面 ArrayList<E>,Comparable<T>
在方法签名中,声明方法时,在返回值类型的前面<T>
public static <T> boolean addAll(Collection<? super T> c,T... elements)
实参:数据实参和类型实参
数据实参:调用方法是 getMax(5,6)
泛型实参:ArrayList<String>