集合
集合的概述
体系结构
-
集合分类:
- 单列集合:每个元素都是一个单独的个体。
- 双列集合:每个操作都是针对一对数据来进行的,一对数据作为一个整体。键值对。
-
单列集合的体系:
Collection 单列集合的顶层接口 List 有序的子接口 ArrayList 顺序存储的实现类,查询快,增删慢 LinkedList 链式存储,查询慢,增删块 Set 无序的子接口 HashSet 哈希表存储 LinkedHashSet HashSet的子类
-
双列集合的体系:
Map 双列集合的顶层接口 HashMap 哈希表存储Map的实现类 LinkedHashMap HashMap的子类
Collection
概述和常用方法
- 单词:收集,集合
- 单列集合的顶层接口:定义的所有单列集合中共有的功能。
- Collection是一个接口,不能实例化,不能创建对象,找一个该接口的实现类对象。
- 使用接口类型的引用,指向实现类的对象
- Collection类型的引用,指向ArrayList类型的对象(只能调用接口中的方法)
- 常用的方法
-
add(Object obj):将obj这个元素添加到集合中
-
remove(Object obj):将obj元素从集合中移除
-
contains(Object obj)判断集合中是否包含obj这个元素
-
isEmpty()就是集合为空就返回true
-
size()返回就是集合中元素的个数
-
clear();清空集合中的元素
-
Collection集合的第一种遍历
- 转成数组,通过遍历数组,来间接的访问集合
- 方法:Object[] toArray():将调用者集合转成Object类型的数组
Collection中带All的方法
-
addAll(Collection c):将参数c中的所有元素,都添加到调用者集合中
-
removeAll(Collection c):将参数集合c中的元素都从调用者集合中移除
-
containsAll(Collection c):判断调用者集合中是否全部包含参数集合c中的元素
-
retainAll(Collection c) 参数c中有哪些元素,就在调用者集合中保留哪些元素
(参数c集合和调用者集合中共有的元素)
集合遍历的第二种方式:迭代器
- 迭代:更新迭代,从某一个到下一个的过程的含义
- 迭代器:专门用于将集合中的元素,一个到另一个逐个进行迭代的过程,就是提供一个方法对集合容器对象都进行访问。而又不去暴露对象容器的内部细节。集合容器内部结构不同,很多时候不知道怎样去遍历一个容器中所有的元素是。所以为了容器中的元素操作起来更为简单,java引入了迭代器模式,把访问不同逻辑从不同类型集合类中抽取出来,避免了向外部暴露集合内部的内部结构。
- 获取:集合自己内部有一个可以迭代自己的对象,从集合中获取即可
Iterator iterator() - 迭代器的使用:
方法iterator()返回是一个Iterator接口的实现类对象可以使用Iterator接口中提供的方法。
hastNext():判断集合中是否还有下一个元素
next():获取集合中的下一个元素,让迭代器指针发生一次移动
remove();删除迭代器正在遍历的那个元素 - 迭代器在使用时候的注意事项
- 迭代器对象虽然多次调用next方法,都是同样的名称,但是每次调用方法返回的内容是不一样的。
next方法既可以获取下一个元素,也会让迭代器对象,向前移动一步。 - 如果没有下一个元素,仍然调用next方法,出现NoSuchElementException(没有当前元素异常)可以使用hasNext方法进行判断,如果为true就调用next方法,为false就不调用next方法
- hastNext方法不会移动迭代器指针的位置
- next方法不仅可以获取下一个元素,也会移动迭代器指针的位置
- 不要只判断一次hasNext方法,就调用多次next方法
- 迭代器对象虽然多次调用next方法,都是同样的名称,但是每次调用方法返回的内容是不一样的。
List
概述
- 是Collection有序的子接口
- 特点:
- 有序:每个元素都有自己位置,不同的位置有区别的
- 有索引:每个元素都有自己的编号
- 可以重复:即使是值相同的元素,位置和索引是不同的,可以区分相同的值
- 特有方法:
-
add(int index,Object obj): 在指定索引上,添加指定的元素
-
remove(int index):删除指定索引上的元素
-
set(int index,Object obj):将指定索引上的值,修改为指定的值
-
get(int index):获取指定索引上的值
-
第三种遍历方式
- 针对就是List集合特有的遍历方式
- 可以通过集合中的size方法获取集合中元素的个数,List集合有索引,结合get方法就能够获取List集合中所有的元素。
并发修改异常
- ConcurrentModificationException
并发修改异常 - 出现的原因:在使用【迭代器对象】遍历集合的同时,使用【集合对象】增加或删除集合的元素
- 避免的方式:两种方式都是针对list
- 方式1:迭代器遍历,迭代器增加
- 方法2:集合遍历,集合增加
- 方式1:迭代器遍历,迭代器增加:问题普通的迭代器中没有增加的方法,需要使用List中特有的迭代器
- 迭代器增加:问题普通的迭代器中没有增加的方法,需要使用List中特有的迭代器
- 列表迭代器:ListIterator是Iterator的子接口,拥有Iterator中所有的方法,还要特有的方法
- 列表迭代器的获取:listIterator();
- 迭代器增加:问题普通的迭代器中没有增加的方法,需要使用List中特有的迭代器
- 方式2:集合遍历,集合增加
- list特有的遍历方式,size和get方法相结合
- 不是所有的集合在进行迭代器遍历,迭代器增删的时候都会出现并发修改异常
- 集合中的倒数第二个元素是不会发生的,其他都是会发生了。
List的实现类
- 概述
List是一个接口,根据底层的实现方式不同,具有不同的实现类 - ArrayList: 数组实现,顺序存储
- LinkedList: 节点实现,链式存储
ArrayList
- 也是List一个实现类
- 没有特有方法
- 存储的方式:
- 数组实现,顺序存储
- 通过物理内存位置的关系,来表述逻辑顺序的相邻
LinkedList
- List的一个实现类
- 存储方式:
- 节点实现,链式存储
- 不通过物理位置的相邻,来表示逻辑位置的相邻
- 每个元素都在一个节点中,节点除了元素数据本身以外,还需要存储是下一个节点内存地址
- 特点:
- 查询速度慢,需要根据前面的节点来获取后一个节点的地址,前面所有的节点都要访问一遍,节点越多,查询速度就越慢
- 增删速度快:增删一个元素,只需要修改前后两个节点的指针域即可,与集合的规模没有关系
- LinkedList中特有的方法:
- addFirst(Object obj): 在头部添加元素
- addLast(Object obj): 在尾部添加元素
- removeFirst(): 移除头部元素
- removeLast(): 移除尾部元素
- getFirst(): 获取头部元素
- getLast(): 获取尾部元素
泛型
泛型的概述和使用
-
泛型:广泛的类型,在定义一个类的时候,类型中有些方法参数,返回值类型不确定,就使用一个符号,来表示那些尚未确定的类型,这个符号就称为泛型。
-
使用:对于有泛型类,在这些类型后面跟上了一个尖括号,尖括号中写上泛型的确定类型的(在使用该类型创建对象的时候,就可以写出具体类型)
-
泛型的好处:
- 提高了数据的安全性,将运行时的问题,提前暴露在编译时期
- 避免向下转型的问题
-
注意事项:
-
前后一致:在创建对象的时候,赋值符号前后中尖括号中的类型要一致
-
泛型推断:如果创建对象的时候,前面已经写好了泛型,后面创建对象的类型就可以只写一个尖括号。“菱形泛型”
jdk1.7特性
-
不能定义泛型数组,发生泛型擦除问题,失去了泛型存在的意义
-
泛型类的定义
- 泛型类:带着泛型的类
- 格式:
class 类名<泛型类型1,泛型类型2,泛型类型3,....>{}
- 说明
- 类名后面跟着的泛型类型,是泛型的声明,一旦泛型声明出来,就相当于这个类型成为了已知类型,这个类型就可以在整个类中使用
- 泛型声明的名称:只需要是一个合法的标识符即可,通常我们使用单个大写字母来表示。T,W,Q,K,V,E
- 泛型确定的时机:将来在使用这个类,创建对象的时候
泛型方法
- 在方法声明中,带着泛型声明的方法,就是泛型方法
- 格式
修饰符 <泛型的声明1,泛型声明2 ...> 返回值类型 方法名称(参数列表){}
- 说明
- 在方法上声明的泛型,可以在整个方法中使用,当做已知类型去使用
- 如果是非静态方法,在方法上没有任何泛型的声明,可以使用类上声明的泛型
- 如果是静态方法,在方法上没有任何泛型的声明,不可以使用类上声明的泛型,只能在静态方法上,声明泛型
泛型的通配符《了解》
使用泛型的时候,没有使用具体的泛型声明T,而是使用了和声明的某个泛型T有关的一类类型,就称为泛型的通配符
三种形式
- 第一种形式,使用?来表示可以是任意的类型
removeAll(Collection<?> c) 表示可以接受任意泛型类型的集合c
作为该方法的参数,参数集合的泛型可以和调用者集合泛型E没有任何关系 - 第二种形式,使用? extends E来表示某个泛型类型或是该泛型类型的子类
addAll(Collection<? extends E> c) 表示的是参数集合c中的泛型,必须是调用者集合泛型E的子类类型或者是本类类型,作为该方法的参数 - 第三种形式:使用?super E来表示必须是某个泛型类型或者是该泛型类型的父类类型
Arrays工具类中排序方法sort(T[] t,Comparator<? super T> c)
T就是该方法的泛型,T表示的就是数组中元素的类型<? super T>,表示的是可以接受泛型的类型必须是T类型或者是T类型的父类类型
Set集合
- set集合是Collection下的一个子接口
- 特点:
无序:没有任何前后的区别,存入的顺序和取出的顺序是不一样的,所有的元素在集合中没有位置上的概念
没有索引
不能重复,没有位置上的区别,相同的元素没有任何区分,所有不能重复 - Set的实现类:
HashSet:使用哈希表的存储方式来存储元素 - 存储特点:
相同的元素不能存入到set集合中
集合本身不保证顺序的,存入的顺序和取出的顺序是不一样的
Set集合的遍历
-
没有特有的方法,只能使用Collection接口中定义的方法,只能使用Collection的遍历方式
-
第一种:转成数组toArray(),不带泛型的数组,得到的是Object类型的数组
-
第二种: 转成数组 T[] toArray(T[] t),带泛型的数组,得到的是T类型的数组
-
自己创建的数组大小,小于集合元素的个数
在方法中,就会创建一个新的数组,用来存储集合中的元素,将数组返回 -
自己创建的数组大小,等于集合元素的个数
在方法中,就不会创建一个新的数组,使用集合中的元素将传入的数组进行填充,将原数组进行返回 -
自己创建的数组大小,大于集合元素的个数
在方法中,不会创建一个新的数组,直接将集合中的元素填充到数组中的前面的位置,后面的位置用默认值填充 -
第三种:迭代器
-
第四种:增强for循环(foreach)
格式:for(元素的数据类型 元素名称 :要遍历的集合或者数组){
使用元素名称代表当前访问的元素;
}
说明:
元素的数据类型:指的是要遍历集合中元素的数据类型
元素名称:元素名称虽然是固定的,但是随着循环的执行,每次代表的元素是不同的
要遍历的集合:可以是数组也可以是集合
本质:
底层迭代器,只不过使用这种格式更为简单
注意事项: 使用增强for,没有拿到元素的索引,无法修改集合或者数组中的元素值
底层迭代器,所在在遍历集合的时候,使用集合对象添加元素,也会发生并发修改异常
-
HashSet保证元素唯一性的原理
HashSet存储jdk提供的元素的类型
HashSet存储jdk提供的元素的类型,发现直接保证了元素的唯一性,值相同的元素都去掉了
HashSet存储自定义类型元素
-
HashSet存储自定义类型元素发现并没有保证唯一性
-
实验:猜测没有重写equals方法,没有重写之前比较的地址值,如果地址值一样了就不存储,不一样就存储,但是
- 重写后equals方法比较的是属性值,发现并没有调用,没有去重成功
-
猜测Hashset集合是哈希实现,有没有可能跟HashCode方法有关,重写HashCode方法
-
在重写hashcode方法之前,调用的Object中的hashcode方法,不同的对象(对象的地址值不同)生成的hashcode值就是不同的。
-
重写hashcode方法之后,让Person中hashcode方法生成的值都为0(实现的效果,不同的对象对象的地址值不同的时候)生成的hashCode值一样的,发现确实hashCode方法执行了,equals方法也执行了。
-
-
发现了,HashSet存储自定义类型元素的时候,确实要调用自定义元素类型中的hashCode方法,根据hashCode方法的值判断是否将自定义元素存入到集合中。发现不同的对象(地址值不同)调用Object类中的hashCode方法生成的是不同的的hashcode值,就直接存储,不能实现去重,equals方法也不调用
- 重写hashcode方法之后,让Person中hashcode方法生成的值都为0,发现调用equals方法,根据equasl方法比较的结果判断是否添加重复的元素
HashCode方法
- Object类型中的方法
- 根据对象,生成一个整数,就是哈希吗值,生成数字的方法的就是hashCode()
- 生成数字的原则:
- 同一个对象,多次调用hashCode()方法【必须】返回的是相同的数字(程序多次运行不要求hashCode码值一致)
- 使用equals(Object) 方法判断相同的两个对象【必须】返回相同的整数(equals是Object类型中的,比较的是地址值)
- 使用equals(Object) 方法判断不相同的两个对象【尽量】生成不相同的整数(不做强制要求)
- Object类型中的HashCode方法,【确实】 会根据不同的对象生成不同的整数。
HashSet保证唯一性原理的总结
- HashSet 集合存储一个obj对象的时候,首先计算obj对象的hashcode值
- 在集合中的所有的元素的哈希值,都和obj的hashcode值,说明集合中不存在Obj,可以直接将obj存储到集合中
- 在集合中有若干个元素的哈希值,和obj的哈希值相同,并不能说明obj已经存在于集合中,需要使用equals方法判断obj时候和那些与自己相同的哈希值的元素是否相等
- 如果在equals方法和obj元素比较之后,发现都不相等,那么就说明obj不存在与这个集合,可以将obj存储到hashset中
- 如果在equals方法和obj元素比较之后,发现相等,obj已经存在于集合中,obj就不存储了
保证唯一性的操作
- 重写HashCode()方法
- 根据属性值,生成哈希值
- 不同的属性值,尽量生成不同的整数
- 相同的属性值,一定会生成相同的整数
- 重写equals方法
- 比较的就是属性值
- 操作
- alt+shift + s
- 生成hashcode方法和equals方法
LinkedHashSet
- 是HashSet的一个子类,和HashSet保证元素唯一性的原理相同
- 将来每个元素在存储的时候,都记录了前后元素的地址
- 效果:
可以根据存储元素的顺序,将元素取出 - 应用:
既需要保证元素的唯一,又需要保证原来的顺序,就可以考虑LinedHashSet
Map
-
是双列集合的顶层接口
-
Map:地图
-
数据结构: 描述的就是将一个数据(key)到另一个数据(value)的映射关系(对应关系)
- 一个数据(key):容易记忆的,有规律的,简单的数据
- 另一个数据(value):没有规律,不容易记忆的,复杂的数据
- 大多数都是通过key操作value
-
映射:对应关系
函数:y = x * x 特点:x是不会重复的 key 平方 y会重复的 value x -----> y 1 1 2 4 3 9 4 16
-
java中map就是使用穷举罗列的方式描述映射关系
-
map的特点
- key(键) 是唯一的,不能重复的
- value(值)不是唯一的,可以重复
-
Map和Collection的区别
- Map是双列集合,Collection单列集合
- Map的键是唯一的,Collection中set子接口中的元素是唯一的
- Map中大多数方法都是操作键的,Collection中set子接口中的操作对元素有效
Map集合中的方法
- 增加键值对(向map集合中添加数据):
- put(K key,V value)
- 删除方法
- 根据给定的值,删除对应的键值对:
- remove(k key)
- 清空集合:
- clear()
- 根据给定的值,删除对应的键值对:
- 获取方法
- get(k key)获取键所对应的的value值
- size()返回map集合中所有的键值对的个数
- 判断方法
- containsKey(Object key)
- 判断集合中是否包含key这个键
- containsValue(Object value)
- 判断集合中是否包含value这个值
- containsKey(Object key)
- 修改方法
- 根据给定的键,修改对应的值:put(k key,v value)
- 如果在集合中已经存在key这个键,那么使用put方法,就是修改这个键对应的value值
- 如果在集合中不存在key这个键,那么使用put方法,就是向map集合重添加数据
Map集合的第一种遍历思路(熟练掌握)
- 获取Map集合中所有的键,放到一个set集合中,遍历set集合,获取每一个键,根据键再来获取对应的值【根据键获取对应的值】
- 获取Map集合中所有的键
- keySet()
- 遍历Set集合拿到map集合中所有的键了
- 遍历Set集合:
- 增强for
- 迭代器
- 拿到每个键之后,通过get(K key)获取键对应的值
- 特点:
- 获取了所有键的set集合后,要依赖于原来的map集合
Map集合的第二种遍历思路
- 获取的是Map集合中所有的键值对对象(Entry), 到set集合中,遍历set集合,拿到的是每一个键值对对象(Entry),从这个对象中获取键和值
- 根据Map集合方法可以获取所有键值对对象到一个Set集合中
Set<Map.Entry<K,V>> entrySet()
- 遍历Set集合
- 迭代器
- 增强for
- 获取到的就是一个键值对对象
- Entry是Map接口中的内部接口,访问方式:Map.Entry
- Entry常用的方法
- getKey()获取的是键值对中的键
- getValue()获取的是键值对中的值
HashMap
- 就是Map集合中使用哈希表存储方式的一种实现类
- HashMap存储jdk中提供的元素类型的键,直接保证元素的唯一性
- HashMap存储的键值对中的键是自定义类型元素的时候,无法保证元素的唯一性,要保证自定义元素的唯一性也需要重写hashCode和equals方法保证键的唯一性和HashSet保证元素唯一性的原理是一样的
- HashMap和HashSet的关系
- HashSet是HashMap实现出来的,HashSet就是HashMap键的那一列将HashMap值的那一列隐藏掉就成HashSet
- HashMap和Hashtable的区别
- HashMap是线程不安全的,效率高,出现的版本是1.2
- 能存储null键与null值
- Hashtable是线程安全的,
- 不能存储null键与null值
- HashMap是线程不安全的,效率高,出现的版本是1.2
LinkedHashMap
- 是HashMap的一个子类
- 和HashMap的不同之处,具有可预知的迭代顺序,存储键值对的顺序和遍历集合时取出的顺序时的键值对的顺序一致。
Collections工具类
binarySearch(List<? extends Comparable<? super T>> list, T key)
- 在一个升序的List集合中,通过二分查找寻找Key对应的索引
frequency(Collection<?> c, Object o)
- 返回指定 collection 中等于指定对象的元素数。
replaceAll(List<T> list, T oldVal, T newVal)
- 使用另一个值替换列表中出现的所有某一指定值。
reverse(List<?> list)
- 反转指定列表中元素的顺序
shuffle(List<?> list)
- 将list集合中的元素进行随机置换,打乱
swap(List<T> list, int a, int b)
- 将list集合中索引为a和b的两个元素进行交换
sort(List<T> list)
- 将list集合中的元素进行自然排序
- synchronizedxxx方法系列:
- 将一个线程不安全的集合传入方法,返回一个线程安全的集合
- unmodifiablexxx方法系列:
- 将一个可修改的集合传入方法,返回一个只可读的集合