3.集合框架
3.1 Collection集合
3.1.1存储特点
Collection接口时单列集合的顶级接口。在这种集合中存储的数据,只占一列。所有的元素,直接存储与各种数据结构中。
Collection集合中,没有下标的概念
3.1.2 Collection API
-
接口方法
由于这个接口时单列集合的顶级接口,在这里定义的所有方法,在所有的实现类中都是可以使用的。
修饰&返回值 | 方法 | 描述 |
---|---|---|
boolean | add(E element) | 将一个指定类型的元素添加到集合的末尾 |
boolean | addAll(Collection coll) | 批量添加,将一个集合中的所有的元素,添加到当前集合的末尾 |
boolean | remove(E ele) | 删除集合中指定的元素 |
boolean | removeAll(Collection coll) | 删除集合中所有的在参数集合中存在的元素(删除交集) |
boolean | retainAll(Collection coll) | 保留集合中所有的在参数集合汇总存在的元素,删除其他元素(保留交集) |
void | clear() | 清空集合中的所有的数据 |
boolean | removeIf(Predicate predicate) | 删除集合中满足指定条件的数据 |
boolean | contains(E ele) | 判断一个集合中是否包含指定的元素 |
boolean | contiansAll(Collection coll) | 判断参数集合中,是否所有的元素都在当前集合中包含。 |
int | size() | 获取集合中元素的数量,类似于数组length |
boolean | isEmpty() | 判断一个集合是否是空集合 |
Object[] | toArray() | 将集合中的元素,转成Object数组 |
T[] | toArray(T[] arr) | 将集合中的元素,转成指定类型的数组 |
3.1.3Collection集合遍历
3.1.3.1迭代器(Iterator)
工作原理
-
使用集合的iterator()方法,获取到一个迭代器对象
原因:迭代器在对集合进行遍历时,要与对应的集合产生关系,通过集合的一个方法获取迭代器对象,可以在方法内部创建迭代器对象,同时自动与当前集合产生关系,对于使用者来说简化了代码
-
这个迭代对象,内部维护了一个引用,指向集合中的某一个元素。默认指向-1位
-
使用next()方法,向后移动一位元素指向,并返回新的指向元素
-
如果使用next()方法,向后移动指向的时候,以及超出了集合的范围,指向了一个不存在的元素,会抛出异常NoSuchElenmentException
-
一般情况下,配合hasNext()方法一起使用的。
注意事项
在使用迭代器进行元素的遍历过程中,不要使用集合的方法修改集合中内容!!!否则会出现ConcureentModificationException。可以使用迭代器自带的remove()方法操作
对出现ConcurrentModificationException异常的解释(扩展内容)
Fail-Fast机制: 我们知道java.util.ArrayList不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了list,那么将抛出ConcurrentModificationException,这就是所谓的fall-fast策略。 这⼀策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数, 对ArrayList内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给 迭代器的expectedModCount。 [java] view plain copy 1.HashIterator() { 2. expectedModCount = modCount; 3. if (size > 0) { // advance to first entry 4. Entry[] t = table; 5. while (index < t.length && (next = t[index++]) == null) 6. ; 7. } 8.} 在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已 经有其他线程修改了list: 注意到modCount声明为volatile,保证线程之间修改的可⻅性。 [java] view plain copy 1.final Entry<K,V> nextEntry() { 2. if (modCount != expectedModCount) 3. throw new ConcurrentModificationException(); 在HashMap的API中指出: 由所有ArrayList类的“collection 视图⽅法”所返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进⾏修改,除⾮通过迭代器本身的 remove ⽅法,其他任何时间任何⽅式的修改,迭代器都将抛出 ConcurrentModificationException。因此,⾯对并发的修改,迭代器很快就会 完全失败,⽽不冒在将来不确定的时间发⽣任意不确定⾏为的⻛险。 注意,迭代器的快速失败⾏为不能得到保证,⼀般来说,存在⾮同步的并发修改 时,不可能作出任何坚决的保证。快速失败迭代器尽最⼤努⼒抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是 错误的,正确做法是:迭代器的快速失败⾏为应该仅⽤于检测程序错误。
3.1.3.2增强for循环
注意事项:
在使⽤增强for循环进⾏元素的遍历过程中,不要修改集合中的内容 !!! 否则, 会出现 ConcurrentModificationException 。
原因:增强for循环内部实际调⽤的是⼀个迭代器
3.1.3.3forEach
java8之后,在Collection集合中添加了一个新的方法forEach
方法原型:
default void forEach(Consumer<? super T> action)
方法逻辑:
将集合中的每一个元素,代入到参数Consumer函数式接口的方法中,作为参数,在调用这个方法的时候,可以自定义实现的方式。
3.2 List集合
3.2.1存储特点
List集合是单列集合,是Collection接⼝的⼦接⼝。Collection接⼝中所有的⽅法,这 ⾥都有。同时,这个集合⽐Collection集合,多个若⼲⽅法。
在List接⼝中,是有下标的概念的。多出来的这些⽅法,基本也都是围绕着下标操作 的。
3.2.2 List与Set对比
-
List:存储的数是有序的(元素的存储顺序与添加元素的顺序一致),可以重复的。
-
Set:存储的数据是无序的,不可以重复
3.2.3 List ApI
修饰&返回 | 方法 | 描述 |
---|---|---|
void | add(int index, E element) | 向集合中指定的下标位插入一个元素。 |
void | addAll (int index,Collection coll) | 在集合中指定的下标位插入另外一个集合中所有的数据。 |
E | remove(int index) | 删除集合中指定下标位的元素 |
E | set(int index,E element) | 修改指定下标位的值 |
E | get(int index) | 获取指定下标位的元素 |
int | indexOf (E element) | 获取集合的某一个元素第一次出现的下标 |
int | lastIndexOf (E element) | 获取集合中的摸一个元素最后一次出现的下标 |
List | subList(int formIndex,int toIndex) | 从一个集合中,截取一部分,作为子集合。[from,to)。 |
void | replaceAll (UnaryOperator operator) | 将集合中的元素带入到接口方法中,用返回值替换原来的元素 |
void | sort(Comparator comparator) | 将集合中的元素进行升序排序 |
3.2.4 List集合排序
在List接口中,提供了一个排序的方法sort
方法原型
default void sort(Comparator<? super E> c)
方法逻辑
这是系统封装好的一个用来做排序的方法,由于集合中只能存储引用数据类型的数据,因此,需要明确两个元素的大小关系。参数Comparaor是一个对象大小比较的接口。在这个接口的方法中,有两个参数,分别表示参与比较的两个对象。返回值是int类型,不看具体的值,只看正负。
3.2.5 List集合遍历
有与List接口是继承自Collection接口的,因此在Collection部分的三种遍历方式,都可以用来遍历List集合,同时在List集合中,还添加了两种用来遍历集合的其他方式。
-
下标遍历
顾名思义,类似于数组的下标遍历。遍历集合中的所有的下标,依次获取指定下标位的元素。
-
列表迭代器
这种⽅式, 类似于迭代器。 在List集合中, 有⼀个⽅法 listIterator() , 可以获 取到⼀个 ListIterator 接⼝的引用。 ⽽ ListIterator 是 Iterator 的子接⼝。 因此在保留了传统的迭代器的迭代⽅法的基础上, 还添加了若⼲个其他的⽅法。
ListIterator 中新增了hasPrevious()和previous()⽅法 当我们使⽤hasnext()和next()⽅法实现从左到右遍历后,可以继续使⽤使⽤ hasPrevious()和previous()⽅法从右到左遍历.
使⽤ListIterator, 在遍历集合中的元素的同时, 可以向集合中添加元素、删除 元素、修改元素。 但是, 这⾥对集合中的元素操作, 并不是使⽤ List 接⼝中的⽅法, ⽽是⽤ ListIterator 接⼝中的⽅法完成。
4. List详解
4.1 ArrayList与LinkedList对比
-
相同点:
-
都是List集合的常用的实用类
-
对集合中的元素操作的方法基本一致
-
都是线程不安全的
-
-
不同点:
-
ArrayList底层实现是数组,使用数组这种数据结构进行数据的存储
-
LinkedList底层实现是双链表,使用双链表这种数据结构进行数据的存储
-
数组与链表结果特点比较:
-
数组实现功能时查找快,添加删除慢
-
链表查找慢,添加删除快
ArrayList与LinkedList的使用场景
-
对集合中的元素,增删操作不怎么频繁,查询操作比较频繁。使用ArrayList。
-
对集合中的元素,增删操作比较频繁,查询操作不怎么频繁。使用LinkedList。
4.2集合对自定义对象的存储
-
类分成系统类和⾃定义类
-
系统类往往已经重写了tostring(),equals(),hashcode()等⽅法
-
⾃定义类,为了实现我们的功能,往往我们需要重写⽗类的常⽤⽅法,⽐ 如:tostring(),equals(),hashcode()
4.3LinkedList
对于LinkedList的特有方法对比:
jdk1.6以前的删除获取方法,拿不到元素报异常
jdk1.6以后的删除获取方法,拿不到元素返回null
4.4数据结构
4.4.1数据结构定义
数据结构是计算机存储、组织数据的⽅式,是相互之间存在⼀种或多种特定关系的数 据元素的集合,即带“结构”的数据元素的集合。“结构”就是指数据元素之间存在的关 系,分为逻辑结构和存储结构.通常情况下,精⼼选择的数据结构可以带来更⾼的运行或者存储效率。
数据的逻辑结构和物理结构是数据结构的两个密切相关的⽅⾯,同⼀逻辑结构可以对应不同的存储结构。算法的设计取决于数据的逻辑结构,⽽算法的实现依赖于指定的存储结构.
4.4.2数据的逻辑结构
指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前 后间关系,⽽与他们在计算机中的存储位置⽆关。逻辑结构包括:
-
集合:数据结构中的元素之间除了“同属⼀个集合” 的相互关系外,别⽆其他关 系;
-
线性结构:数据结构中的元素存在⼀对⼀的相互关系;
-
树形结构:数据结构中的元素存在⼀对多的相互关系;
-
图形结构:数据结构中的元素存在多对多的相互关系。
4.4.3数据的物理结构
指数据的逻辑结构在计算机存储空间的存放形式。
4.4.4常用的数据结构
程序设计中常⽤的数据结构包括如下⼏个。注意:这⾥的数据结构分类是从逻辑结构 上进⾏的.
-
数组(Array)
数组是⼀种聚合数据类型,它是将具有相同类型的若⼲变量有序地组织在⼀起的集 合。数组可以说是最基本的数据结构,在各种编程语⾔中都有对应。⼀个数组可以分 解为多个数组元素,按照数据元素的类型,数组可以分为整型数组、字符型数组、浮 点型数组、指针数组和结构数组等。数组还可以有⼀维、⼆维以及多维等表现形式。
-
栈(Stack)
栈是⼀种特殊的线性表,它只能在⼀个表的⼀个固定端进⾏数据结点的插⼊和删除操 作。栈按照后进先出的原则来存储数据,也就是说,先插⼊的数据将被压⼊栈底,最 后插⼊的数据在栈顶,读出数据时,从栈顶开始逐个读出。栈在汇编语⾔程序中,经 常⽤于重要数据的现场保护。栈中没有数据时,称为空栈。
-
队列(Queue)
队列和栈类似,也是⼀种特殊的线性表。和栈不同的是,队列只允许在表的⼀端进⾏ 插⼊操作,⽽在另⼀端进⾏删除操作。⼀般来说,进⾏插⼊操作的⼀端称为队尾,进 ⾏删除操作的⼀端称为队头。队列中没有元素时,称为空队列。
-
链表( Linked List)
链表是⼀种数据元素按照链式存储结构进⾏存储的数据结构,这种存储结构具有在物 理上存在⾮连续的特点。链表由⼀系列数据结点构成,每个数据结点包括数据域和指 针域两部分。其中,指针域保存了数据结构中下⼀个元素存放的地址。链表结构中数 据元素的逻辑顺序是通过链表中的指针链接次序来实现的。
-
树( Tree)
树是典型的⾮线性结构,它是包括,2个结点的有穷集合K。在树结构中,有且仅有 ⼀个根结点,该结点没有前驱结点。在树结构中的其他结点都有且仅有⼀个前驱结 点,⽽且可以有两个后继结点,m≥0。
-
图(Graph)
图是另⼀种⾮线性数据结构。在图结构中,数据结点⼀般称为顶点,⽽边是顶点的有 序偶对。如果两个顶点之间存在⼀条边,那么就表示这两个顶点具有相邻关系。
-
堆(Heap)
堆是⼀种特殊的树形数据结构,⼀般讨论的堆都是⼆叉堆。堆的特点是根结点的值是 所有结点中最⼩的或者最⼤的,并且根结点的两个⼦树也是⼀个堆结构。
-
散列表(Hash)
散列表源⾃于散列函数(Hash function),其思想是如果在结构中存在关键字和T相等 的记录,那么必定在F(T)的存储位置可以找到该记录,这样就可以不⽤进⾏⽐较操作 ⽽直接取得所查记录。
5.泛型
5.1泛型在类中的使用
5.1.1语法部分
定义:在类名的后⾯, 紧跟上⼀对尖括号
class Animal <T> {} class Dog <T, M> {}
泛型类的使用:声明引⽤、实例化对象、被继承。
// 1. 声明引⽤ Animal<String> animal; // 2. 实例化对象 Animal<Integer> animal = new Animal<>(); // 3. 被继承 class Dog extends Animal<String> {} class Dog<T> extends Animal<T> {}
5.1.2泛型类的特点
-
在类中定义的泛型, 虽然还不明确是什么类型, 但是在当前类中是可以使⽤ 的。
-
在使⽤到这个类的时候, 必须要指定泛型的类型。 如果不指定, 默认是Object。
-
泛型, 只能在当前的类中使⽤, 不能在其他的类中使⽤, 包括⼦类。
5.2泛型在接口中的使用
5.2.1 语法部分
泛型接口的定义: 在接⼝名字的后⾯, 添加上⼀对尖括号。 在尖括号⾥⾯定义泛型。
interface MyInterface<T> {} interface MyInterface<T, M> {}
泛型接口的使⽤: 实现类实现接⼝、使⽤接⼝访问接⼝中的静态成员、被继承。
// 1. 实现类实现接⼝ class MyInterface1Impl implements MyInterface1<Person> {} // 2. 被继承 interface SubMyInterface extends MyInterface1<String> {}
5.2.2泛型接口的特点
-
在接⼝中定义的泛型, 虽然还不明确是什么类型, 但是在当前接⼝中是可以使 ⽤的。
-
在使⽤到这个接⼝的时候, 必须要指定泛型的类型。 如果不指定, 默认是 Object。
-
泛型, 只能在当前的接⼝中使⽤, 不能在其他的接⼝中使⽤, 包括⼦接⼝。
5.2.3子类使用接口泛型
这⾥有两种情况: 第⼀种:⼦类与接⼝泛型⼀致,接⼝可以直接使⽤泛型类型
第⼆种:接⼝使⽤泛型,⼦类不⽤,在实现的接⼝位置必须指定⼀个具体的数据类型
5.3泛型在方法中的使用
5.3.1语法部分
定义: 泛型⽅法中, 在定义⽅法时,在返回值前⾯通过定义泛型
public static <T> void test(T t) { }
-
在定义处的<>⽤来定义泛型
-
在调⽤处的<>⽤来使⽤泛型
5.3.2泛型方法的分类
-
第⼀:⽅法上的泛型与类上的⼀致
-
第⼆:⽅法上独⽴使⽤泛型
-
在⽅法中定义的泛型,虽然还不明确是什么类型,但是在当前⽅法中是可以使⽤的。
-
泛型⽅法,在使⽤的时候,不能跟类、接⼝似的,⼿动的设置类型。 泛型⽅法中,泛型的设置,在参数中体现。
-
泛型⽅法,⼀定需要是有参的。参数列表中,必须有泛型类型。
-
泛型⽅法中的泛型的设置,是通过在调⽤⽅法的时候,实参的类型推导出 来的。
-
泛型,只能在当前的⽅法中使⽤, 不能在其他的⽅法中使⽤
-
-
第三:静态⽅法使⽤泛型
5.4关于java中?的使用总结
-
⽤于?: 这⾥是三⽬运算符⼀部分,前⾯是判断条件,后⾯是两个分⽀结果
-
⽤于数据库的sql语句 select * from emp where name=? :表示占位符
-
⽤于泛型,是通配符,表示任意⼀种数据类型.