摘要:本篇文章主要讲解java集合框架
右移一位相当于除2?,关于移位运算! 枚举?!
接口的意义?
重写equals可以直接影响remove方法?(看源码)
零、一些注意点
1、打印集合没必要加上 .toString 方法,println会自动给你加
2、自定义类中如果重写了equals和toString方法,在集合中是被默认调用的,但目前看源码看不懂(!???),主要影响的是 加、删、判断存在 三个方法
3、
一、集合的概念
1.1、概念:
对象的容器(放对象的地方),实现了对对象常用的操作,类似数组的功能
1.2、和数组区别:
数组长度固定,集合长度不固定
数组可以存储基本类型和引用类型,集合只能存储引用类型(可以通过装拆箱来存储基本类型)
1.3、位置:java.util.*;(需要导入)
1.4、位置:堆中
二、Collection体系集合
有序:添加顺序=遍历顺序
有下标:可以通过下标访问
Vector已经不用了
Collection父接口
特点:代表一组任意类型的对象,无序,无下标
!!方法:
1 | add();//添加元素 |
注:
1、remove方法:当数据类型是基本类型的包装类和String类时,remove根据内容来比对删除。当数据类型是自定义类型时,则比对地址。
此方法不能重写,因为方法是集合固定好的:remove是不能重写,equals可以啊!
contains方法完全一致
2、迭代:循环or遍历
3、关于迭代器的指针问题
先贴上源码,不看源码都是扯淡!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
public boolean hasNext() {
return cursor != size;
}
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}分析:
1、迭代器相当于有两个指针,一个叫做cursor,一个叫做lastRet,lastRet指针指向的初始位置都是-1,cursor指向的则是0
2、next方法本质是把cursor指针指向的东西给返回(如果cursor已经>=size,就抛异常,结束),然后把cursor+1(cursor加1后的值只会用于下一次next,不会用于任何其他用处)
3、remove方法用的是lastRet指针,在next方法中最后会把cursor原来的值赋给lastRet。移出成功后重新将lastRet设为-1
1
2
3
4
5
6 while(it.hasNext()) {
Student s=(Student)it.next();
System.out.println(s.toString());
it.remove();
//上述代码能把集合清空的原因是,每次next完将cursor指针移动到下一个位置后,remove的指针刚好指向cursor指针的前一个位置
}实际就四个步骤:
1、has判cur
(i = cur)
2、next取cur
3、cur++
4、lastRet=i
Other:
1
2
3
4
5
6
7
8
9
10
11
12
13 //两种遍历的实现方法
//增强for,因为Collection是没有下标的,不能用普通for循环
for (Object object : collection) {
Student s=(Student)object;
System.out.println(s.toString());
}
//迭代器: hasNext() next(); remove(); 迭代过程中不能使用使用collection的删除方法
Iterator it=collection.iterator();
while(it.hasNext()) {
Student s=(Student)it.next();
System.out.println(s.toString());
// it.remove();
}
1
2 //collection.remove(new Student("王二", 22));
//注意:这里是没法删除的,因为对于自定义类而言,比较的是地址
三、List接口与实现类
实现了list接口,就间接实现了Collection接口
3.0 List接口的特点:
有序、有下标、元素可以重复
因为有了下标,所以删除元素时,既可以通过对象删除,又可以通过下标来删除
1 | //相比于集合,所有涉及脚标的方法都是新方法 |
1 | public static void main(String[] args) { |
1 | //对于Integer类型的List |
3.1、List的实现类
- ArrayList:
基于数组结构(元素连续存放)实现,查询快,增删慢
运行效率快、线程不安全
- Vector:
基于数组结构实现,查询快,增删慢
运行效率慢,线程安全
- LinkedList:
链表结构实现,增删快,查询慢
java中链表的删除比起c、c++更为方便,只需要将被删元素前一个元素的指针指向被删元素的后一个元素即可,JVM自动回收垃圾(无人指向的就成了垃圾)
3.2、ArrayList类
注意一点:如果在类中重写了equals方法,则ArrayList的remove方法中可以通过新建对象的方式来删除
1 | public static void main(String[] args) { |
重写Object.equals五步走!!
1 |
|
源码分析:
1、默认容量:DEFAULT_CAPACITY=10
如果没有向集合中添加任何元素时,容量为0
添加第一个元素后,容量变成默认容量:10
每次扩容为原来的1.5倍
2、存放元素的数组:Object[] elementData
3、实际元素个数:size(小于容量!)
4、添加元素:add方法
先加容量后赋值
add第一句话是扩容,第二句话是赋值(add是用size来赋值的,size表示实际元素数量,用size做下标正好比元素个数多一个,赋值后size++);
!!
3.3 Vector类
注意遍历用的是枚举器即可
3.4 LinkedList类
1 | //遍历 |
源码分析
size:表示集合长度
first:集合头结点
last:集合尾结点
关于java中链表结点的理解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
//Node<E> 是一个引用,就相当于C中的地址,所以,Node类中保存了指向指向下一个和前一个结点的引用,注意不是下一个和前一个结点本身!
//这个类的实例存储在堆中,也有引用指向这个实例,实例中才保存着结点的引用!
手撕LinkedList!
LinkLast方法和结点的定义
3.5 ArrayList与LinkedList
A:开辟连续空间、查询快、增删慢
L:无需开辟连续空间、查询慢、增删快
四、泛型
4.1、简介:
java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递
常见形式有泛型类、泛型接口、泛型方法
意思就是:使用泛型的类、使用泛型的接口、使用泛型的方法
语法
<T,…> T称为类型占位符,表示一种引用类型(总不能是基本类型吧)
<T,E,F>多个使用逗号隔开
好处:
提高代码重用性
防止类型转换异常,提高代码安全性
注意:
你就考虑创建泛型对象!泛型对象可以被当作参数传过来,自己创建却是不可的!
4.2 泛型类:
???
1 | //所谓泛型类,即把类作为参数传递 |
1 | public class Test { |
注:
泛型类不能直接实例化
1
2
3
4public void NEW(){
T t1 = new T();
//错,不能实例化!(万一构造方法私有怎么办,万一没有无参构造怎么办)
}泛型类实例化的时候,后面尖括号中的内容可以不写
(1)泛型只能使用引用类型 (2)不同泛型对象之间不能相互赋值(总不能把Integer的实例赋给String的泛型类吧)
1
2
3GenericDemo<String> genericClassDemo1 = new GenericDemo<>();
GenericDemo<Integer> genericDemo2 = genericClassDemo1;
//想屁吃呢!不传递泛型也可,因为本质是传递了Object类(???)
4.3 泛型接口
1 | /** |
泛型接口的实现方法
一、在实现的时候确定泛型,要不同的泛型,就创建不同的实现类
1 | public class GenericImpl implements GenericInterface<String>{ |
二、实现类仍然采用泛型形式,到泛型类实例化的时候再确定泛型
1 | public class GenericImpl2<T> implements GenericInterface<T> { |
Test:
1 | //泛型接口 |
注:
即使继承了泛型接口,泛型类仍可以退化为一个正常的类。
深层原因是默认传递了Object类
4.4 泛型方法
1 | /** |
1 | GenericMethod genericMethod = new GenericMethod(); |
注:
注意使用范围,只能在当前方法中使用
泛型方法的调用,不用传递类型:方法直接根据传递的对象确定类型
4.5 泛型的好处
提高代码的重用性,一套代码,适用于不同的传入对象类型,以前只能通过方法重载
1 | //不同类型对象作参数 |
4.6 泛型集合
概念:java集合都是泛型集合,在创建集合的时候传递类型,强制集合元素的类型必须一致
特点:
- 编译时即可检查,而非运行时抛出异常。
- 访问时,不必类型转换(自动类型转换,Object → 泛型)
- 不同泛型之间的引用不能相互赋值,泛型不存在多态(相同当然可以赋值)
1 | ArrayList<String> objects = new ArrayList<>(); |
1 | ArrayList object = new ArrayList<>(); |
五、Set接口与实现类
- 无序、无下标、不重复
Set接口:模仿了数学上的Set(集合)
Set接口的方法全部继承自Collection接口,没有新的方法
1 | /** |
注:添加重复的元素不会报错,什么都不会做
Set实现类
1、HashSet
- hashSet的方法和Set完全一样,不再演示
- 简单理解,HashSet比的是hashcode和equals(需要比的操作:加、删、包含)
hashSet存储过程:
1)维护一个数组,新来一个对象,根据hashcode算在数组中的位置,如果该位置为空则直接插入,如果该位置不空,第二步
2)执行对应类下的equals方法,如果为true,则重复,拒绝添加;否则,形成链表。
- JDK1.8之后增加红黑树的实现
???
1 | public static void main(String[] args) { |
1 | //重写hashcode保证name,age相同的元素,hashcode一定相同 |
注:
自定义类默认的equals是比地址,可以重写
HashSet集合在添加自定义类型时,在自定义类型对象的类中不重写equals方法和hashCode方法时,每一个新的对象都对应新的地址
hashcode()中的31:(1)质数,减少散列冲突 (2)提高执行效率,可以用位运算解决
$$
31*i=(i<<5)-i
$$关于remove方法的比较流程考究 ???
还有一个LinkedHashSet,其内部元素有序,用法同HashSet
2、TreeSet
用红黑树(特殊的二叉树)实现,二叉查找树就是二叉搜索树
那些年,面试被虐过的红黑树 - SegmentFault 思否
???
简单理解,TreeSet比的是CompareTo方法(需要比的操作:加、删、包含)
特点:
- 基于排列顺序实现元素不重复
- 实现了SortedSet接口,对集合自动排序(插入的时候)
- 元素对象的类型必须实现Comparable接口,指定排序规则
- 通过CompareTo方法确定是否为重复元素
1 | /** |
1 |
|
Comparator接口
1 | /** |
使用TreeSet的Comparator排序真的很牛逼
1 | /** |
六、Map接口与实现类(Map没有继承Collection接口)
Map接口的特点:
- 用于存储任意键值对(Key—Value)
- 键:无序、无下标、不允许重复(唯一)
- 值:无序、无下标、允许重复
1 | //Map接口的方法 |
Map接口示范
1 | /** |
Map集合的实现类
Map重复的会覆盖,Set重复的直接加不进来
1、HashMap实现
JDK1.8之后增加红黑树的实现
- 当数组长度>=64,且数组某个位置的链表长度大于8的话,会把链表调整成红黑树!
- 当链表长度<=6之后,就取消红黑树,恢复成链表
线程不安全、运行效率快;允许用null作为key或者value
默认容量:16 默认加载因子0.75:元素超过75%后就扩容
hashMap基本实现如上,下面补充一些问题
1 | //hashMap存在的问题: 自定义类不满足“去重” 要求。究其原因是hashmap底层实现是哈希表——哈希表在存储自定义类的时候,如果自定义类中没有重写关于hashcode和equals方法的话,会调用默认的方法,而新对象地址不同,就会被认为不同。 |
源码分析:
Node<K,V>结点
Node<K,V>[] table 数组
刚创建Map的时候,table=null,size=0
类似ArrayList,刚开始为空,为了节省空间,加入一个元素以后,数组变为默认大小(HashMap为16)
当元素比例超过因子时,就扩容为原来的两倍
JDK1.8之前为头插入,JDK1.8之后为尾插入
HashSet内部就是由Hashmap实现的
2、HashTable
数组+链表:现在用的少
线程安全、运行效率慢;不允许用null作为key或者value
Properties:HashTable子类,要求key和value都是String,通常用于配置文件的读取
3、TreeMap
实现了SortedMap接口(是Map的子接口),可以实现对key的自动排序
注:要么在类中实现Comparable接口,要么实例化的时候实现Comparator接口
TreeSet内部是由TreeMap实现的
七、工具类
Collections工具类提供静态方法
集合工具类,定义了除了存取以外的集合的常用方法,可以用来操作集合
1 | void reverse(List<?> list);//反转元素顺序 |
1 | Integer[] nums={1,100,11,21,28}; |
1 | //初始化问题 |
八、注意事项(数组、集合互转)
1 | //补充:List转数组 |
asList的结果不能改变的解决办法
1 | //初始化 |
九:常用集合补充
1、Stack类:(继承Vector,基于数组实现)
1 | Stack<Integer> stack = new Stack<>(); |
补充Queue接口