一、集合
1.集合的概述
①集合与数组的区别:
-
数组的长度是不可变的,集合的长度是可变的。
-
数组可以存基本数据类型和引用数据类型。
-
集合只能存储引用数据类型,如果要存储基本数据类型要存对应的包装类。
② 集合的体系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(
2. Collection接口
- Collection接口是单列集合的根接口
- Collection接口有两个主要的子类接口(List、Set)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XulQlEDu-1628487296758)
- Collection接口的常用方法
boolean add(E e) 往集合中添加元素
boolean remove(Object o) 删除指定元素
void clear() 清空集合所有元素
boolean contains(Object o) 判断集合中是否包含某个元素
boolean isEmpty() 判断集合是否为空(元素个数为0)
int size()获取集合的元素个数
public Object[] toArray() : 把集合中的元素,存储到数组中
3. List接口
List接口下的集合是有索引的,所以List接口在继承Collection接口的基础上,还提供一些特有的方法,可以通过索引操作集合元素。
List接口下特有的方法:
void add(int index, E element) 在指定索引出添加元素
E remove(int index) 根据索引删除元素,返回被删元素
E set(int index, E element) 替换指定索引处的元素,返回被替换的元素
E get(int index) 获取指定索引处的元素
①ArrayList类常用方法
ArrayList集合中常用的成员方法:
1.boolean add(E e) 往集合中添加元素,末尾追加
2.void add(int index, E element) 在集合的指定索引处添加元素
3.boolean contains(Object o) 判断集合中是否包含指定的元素 包含返回true,不包含返回false
4.E get(int index) 获取集合中指定索引处的元素
5.int size() 获取集合中元素的个数,获取集合的长度
6.E remove(int index) 移除并返回指定索引处的元素
7.boolean remove(Object o) 移除此集合中首次出现的指定元素,元素存在删除成功返回true,元素不存在删除失败返回false
8.E set(int index, E element) 把指定索引处的元素,替换为新的元素,返回被替换的元素
0 1 2 3 4
[柳岩, 迪丽热巴, 赵丽颖, 杨幂, 杨幂]
注意:
1.集合的参数是(E,Object),传递集合中的元素
集合的参数是(int index),传递元素对应的索引
2.操作集合的索引,一定要避免索引越界异常,不要超出了集合索引的使用范围
IndexOutOfBoundsException:集合索引越界
StringIndexOutOfBoundsException:字符串索引越界
ArrayIndexOutOfBoundsException:数组索引越界
②LinkedList集合(双向链表)
java.util.LinkedList<E> implements List<E>接口
List 接口的链接列表实现。
LinkedList集合底层是一个双向链表:查询慢,增删快
双向:是一个有序的集合,存储元素和取出元素的顺序是一致的
LinkedList集合有一些操作首尾元素的方法:
public void addFirst(E e) :将指定元素插入此列表的开头。
public void push(E e) :将元素推入此列表所表示的堆栈。
public void addLast(E e) :将指定元素添加到此列表的结尾。
public E getFirst() :返回此列表的第一个元素。
public E getLast() :返回此列表的最后一个元素。
public E removeFirst() :移除并返回此列表的第一个元素。
public E pop() :从此列表所表示的堆栈处弹出一个元素。
public E removeLast() :移除并返回此列表的最后一个元素。
public boolean isEmpty() :如果列表不包含元素,则返回true。
注意:
使用LinkedList集合特有的方法,不能使用多态创建对象
List<String> list = new LinkedList<>(); 弊端:不能使用实现类特有的操作首尾元素的方法
LinkedList<String> linked = (LinkedList<String>)list;//向下转型
4. Set接口
java.util.Set 接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比Collection 接口更加严格了。与 List 接口不同的是, Set 接口都会以某种规则保证存入的元素不出现重复
Set集合取出元素的方式可以采用:迭代器、增强for。
Set集合的特点:
- 无序:元素存入和取出的顺序无法保证一致性
- 不重复:重复的元素不会被存入。
- 无索引:集合中没有索引,无法通过索引操作元素
①HashSet
- HashSet是Set接口的实现类,存储的元素是无序不重复的。
- 根据元素的哈希值确定存储位置,具有良好的存储和查找性能。
- 元素没有索引,只能通过迭代器或增强for循环遍历。
HashSet的底层原理:
(1)保证元素唯一性
根据 hashCode() 和 equals() 方法保证元素的唯一性
hashCode()
Object类提供的本地方法,可以获取对象的哈希值。
public native int hashCode();
哈希值:
根据对象的地址和数据算出来的int类型的整数。
同一个对象多次调用 hashCode()方法得到的哈希值是相同的。
不同对象的哈希值通常是不一样的,但是也无法保证唯一,好的哈希算法能尽量保证不同对象的哈希值不同,减少哈希冲突。
可以通过重写 hashCode()方法自定义哈希值的计算规则。
equals()方法
Object类提供的成员方法,默认比较两个对象的内存地址。
public boolean equals(Object obj) {
return (this == obj);
}
所有类都可以重写该方法,自定义比较规则
元素添加流程
1.调用元素的hashCode()方法获取哈希值,确定存储位置。
2.如果位置为空,直接存入元素。
3.如果不为空,调用equals()方法和该位置的所有元素逐一比较,有相同则不存入,都不相同则添加成功。
结论:
当两个元素的哈希值相同,equals方法返回true,则表示两个元素相同,不会重复添加。
使用HashSet存储自定义对象时,需要重写 hashCode()方法和 equals() 方法。
(2) 底层数据结构
HashSet的底层是借助了HashMap实现数据存储的。HashMap底层使用哈希表存储元素,不同版本的JDK对哈希表的实现结构也不一样。
-
JDK7
在JDK7及之前,哈希表采用数组+链表实现。每个链表被称为桶(bucket)。插入对象时,根据哈希值确定其在桶(数组)中的位置,如果该桶为空,则可将该对象直接插入相应的桶中。如果该桶已经存在元素,则需要将该对象与该桶中的所有对象进行比较,看看是否已经存在该对象,如果全部比较后均不存在,则将其添加到该桶中。
-
JDK8
从JDK8开始,对于哈希表的实现做了一些改进,通过数组+链表+红黑树实现。当某个桶经常发生哈希冲突时,该链表长度将会变的非常长,下一次新对象将必须依次比较该桶中的所有对象,耗费大量时间降低性能。为此当桶中对象数量超过8个时,在JDK8中会将该链表转换为红黑树进行存储。此举将大大减少新对象在该桶的比较次数,提高性能。
②TreeSet集合
TreeSet集合是Set接口的一个实现类,底层依赖于TreeMap,是一种基于红黑树的实现,其特点为:
- 元素唯一 ,不允许存储重复元素
- 元素没有索引 ,元素没有带索引的方法
- 底层是一个红黑树结构(存储的元素都是有序的)
- 使用元素的自然顺序对元素进行排序,或者根据创建 TreeSet 时提供的 Comparator 比较器 进行
排序,具体取决于使用的构造方法:
public TreeSet(): 根据其元素的自然排序进行排序
public TreeSet(Comparator<E> comparator): 根据指定的比较器进行排序
Comparator比较器排序
- 如果集合元素没有实现Comparable接口,也可以使用Comparator比较器接口实现自定义排序。
- 实现Comparator接口,需要重写 int compare(T o1, T o2) 方法。
- TreeSet集合既支持自然排序,也支持比较器排序(使用带比较器参数的构造方法)
static <T> void sort(List<T> list, Comparator<? super T> c) 根据指定比较器产生的顺序对指定集合进行排序。
参数:
List<T> list:要排序的List集合
Comparator<? super T> c:对集合进行排序的比较器
java.utl.Comparator<T>接口:强行对某个对象 collection 进行整体排序 的比较函数。
Comparator接口中的抽象方法:
int compare(T o1, T o2) 比较用来排序的两个参数。
参数:
T o1, T o2:内部自动获取的集合中的元素[1,2,3,4]
比较的规则(重点):
升序:o1-o2
降序:o2-o1
两个元素相等:o1==o2
Comparable自然排序
- 使用无参构造方法创建TreeSet集合时,元素排序默认使用自然排序
- 自然排序要求元素类实现Comparable接口,并重写 compareTo 方法
注意:TreeSet集合元素实现实现Comparable接口,并重写 compareTo 方法 后,使用带比较器参数的构造方法创建TreeSet集合对象,其元素排序优先使用Comparator比较器排序
③LinkedHashSet集合
- LinkedHashSet继承了HashSet,底层在哈希表的基础上,又维护了一个双向链表。
- LinkedHashSet通过哈希表保证元素唯一性,又通过双向链表实现了元素存取的有序性。
5.Collections工具类
Collections 类在java.util包中,是一个操作 集合的工具类。Collections 类提供了许多操作集合的静
态方法,可以实现集合元素的排序、批量添加,查找,复制等操作。
Collections类常用方法 :
addAll(Collection<T> c, T... elements) 将所有指定的元素添加到指定的集合c中。
sort(List<T> list) 根据自然顺序对list集合的元素进行升序排序。
sort(List<T> list, Comparator<T> c) 根据指定的比较器,对list集合元素进行自定义排序。
shuffle(List<?> list) 随机打乱list集合中元素的顺序。
二、迭代器
1. Iterator迭代器
- JDK 中提供了一个Iterator接口,称为迭代器,可以实现集合元素的遍历。
- Collection接口中提供了iterator()方法,可以获取迭代器对象。
②Iterator操作元素的方法
boolean hasNext() 判断是否存在下一个元素。
E next() 获取下一个元素。
void remove() 删除元素
③Iterator遍历集合的步骤:
1.获取迭代器对象: 集合对象.iterator();
2.循环判断是否有下一个元素: 迭代器对象.hasNext()
3.如果有元素,调用 next() 方法,取出元素。
④并发修改异常
产生原因:
当使用迭代器或者增强for循环遍历集合时,在迭代过程中调用集合类自身的remove或者add等方法改变集合的元素个数时,就会产生发修改异常,即ConcurrentModificationException。
解决办法:
1.在遍历集合的同时,不修改集合长度
2.Iterator接口中有一个方法叫remove,作用也是删除集合中的元素
void remove() 删除使用next方法取出的集合中的元素
3.Iterator接口有一个子接口叫ListIterator接口,在ListIterator接口中定义了往集合中添加元素的方法
public interface ListIterator<E> extends Iterator<E>
void add(E e) 将指定的元素插入列表(可选操作)。 ListIterator接口特有的方法
void remove() 删除使用next方法取出的集合中的元素
注意:
1.如果使用迭代器中add|remove方法,往集合中添加|删除元素就相当于迭代器和集合商量好了,可以往集合中添加|删除元素,迭代器就不会抛出并发修改异常了
2.ListIterator迭代器只能比那里List接口下边的集合(ArrayList,LinkedList),不能遍历Set
接口下的集合(HashSet,LinkedHashSet)
⑤迭代器的实现类是每个集合的内部类(扩展面试)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
2.增强for循环
-
它是J D K 5之后出现的,其内部原理是一个Iterator迭代器,对迭代器进行了封装。
-
数组和Collection集合都可以使用增强for循环。
Collection接口有一个父接口叫Iterable public interface Collection<E> extends Iterable<E> java.lang.Iterable<T>接口 实现这个接口允许对象成为 "foreach" 语句的目标。 Collection接口继承了Iterable,所以可以使用增强for循环 Collection接口所有的实现类,都可以使用增强for循环
-
作用:简化迭代器遍历的语法。
-
增强for使用注意:
- 需要遍历的集合或数组不能为null。
- 遍历过程中不能增删集合元素,否则会出现并发修改异常
-
增强for的格式:
for(元素数据类型 变量名 : 数组或者Collection集合) {
//在此处使用变量即可,该变量就是元素
}
三、泛型:可以在类或方法中预知地使用未知的类型
是JDK5中引入的特性,它提供了编译时类型安全检测机制
泛型的好处:
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
1.泛型的定义和使用
①泛型的接口
定义格式:
修饰符 interface接口名<代表泛型的变量> { }
泛型的接口使用:
- 定义类时确定泛型的类型
- 始终不确定泛型的类型,直到创建对象时,确定泛型的类型
泛型类:
定义格式:
修饰符 class 类名<泛型标记> { }
泛型类的使用:
- 泛型类在创建对象时确定泛型的真实类型。
- 类中引用到泛型的地方都会替换成具体类型。
②泛型方法:
定义格式:
修饰符 <泛型标记> 返回值类型 方法名(类型 变量名) { }
泛型方法使用:
- 调用方法,传参时确定泛型类型。
- 方法中引用泛型的地方会替换成具体类型。
2.通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用
泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
①通配符基本使用 :
不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符
此时只能接受数据,不能往该集合中存储数据
② 通配符高级使用----受限泛型
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定
一个泛型的上限和下限。
(1)泛型的上限:
- 格式: 类型名称 <? extends 类 > 对象名称
- 意义: 只能接收该类型及其子类
(2)泛型的下限:
- 格式: 类型名称 <? super 类 > 对象名称
- 意义: 只能接收该类型及其父类型