第五部分 集合
(1)长度可变
(2)只能存储引用类型
(3)不使用泛型时,存储对象可以不是同种数组类型
Collection接口
1.添加,删除
2.判断
3.集合大小
4.获取集合元素
(collection中没有获取角标的方法,所以无法获取指定元素)
(1)增强for循环 for(Object o:collection)
(2)Iterator iterator() 迭代器
迭代器一开始的位置是第一个元素的前面
常用方法:
next() 下一个元素 hasNext() 是否还有元素 remove()删除元素
注意next()方法必须放在remove()方法之前,不然由于不断remove(),调用next()方法不确定是哪一个元素remove依赖next,如果remove之前没有调用next是不合法的。
常见问题
在迭代的过程中,想要删除当前元素的时候不可以调用集合的remove()方法,只能调用迭代器的remove()方法
原因:这个过程就像是抓娃娃的过程,集合相当与娃娃机的拥有者,迭代器相当娃娃机的使用者,当使用者使用娃娃机的时候就临时获得了娃娃机的绝对控制权,绝不可能允许拥有者,从娃娃机里拿出元素,而只能通过使用者拿出元素从而减少娃娃机内中娃娃的数量。
例题:获取集合中数字,并求其和
5.集合和数组之间的转换
数组----》集合 Arrays.asList(数组)
注:1数组转换集合的时候数组里面只能是引用数据类型,如果基本数据类型转换的集合就是一个对象整体,会把数组当成一个整体来看待。
2. 当数组转换成集合的时候,不能做一些增删的操作,因为转换成的集合其本质还是一个数组。(其实返回的不是一个ArrayList而是一个视图)
集合---》数组 集合对象.toArray()
不建议使用Collection作为父类引用,因为有些方法不具备
List接口:
有序可重复的,有序指的是存入顺序
ArrayList:底层数据结构是数组,查询元素快,增删改慢 ,线程不同步
LinkedList: 底层数据结构是链表,增删改块,查询慢,线程不同步
Vector:特别慢不推荐使用 线程同步
具有Collection所有方法
特有方法(注意set方法,返回值是被替换的值)
特有的迭代器 ListIterator
特有方法 (可以实现倒叙遍历)
LinkedList特有方法
栈:push pop 后进先出 队列 :先进先出
Collections:
一个工具类,提供了一些操作List集合的功能,都是静态方法
sort的方法实现的时候,集合的里的类中一定要有自己的比较方法,也就是说类实现comparable接口重写比较方法,因为sort方法在定义时使用泛型,规定传入list的集合中的类必须实现了Comparable接口。
Set接口:
因为无序所以没有get和set方法,因此不能室友普通for循环
无序,不可重复,添加类对象时,类内部重写了equals才能更准确实现不可重复
面试题
去除List集合中的重复数据 List--》Set(set转换为List也可以这么转换)
1.HashSet:底层数据结构是哈希表,保证元素唯一性,线程不同步
2.TreeSet:进行指定顺序排列 ,线程不同步
Hashset存储原理
当向set集合中存储数据的时候,首先会比较两个对象hashcode(重写方法,假设同对象hashCode就相同),如果hashcode值不一样的话,就认为两个对象不相同可以存储,如果两个对象hashcode值相同的话则去调用equals方法分别比较两个对象中每个属性是否相同,如果不同则可以存储,如果相同则不可以存储
Hashcode:返回对象的hash值,hash值是通过某种计算规则获取到的一个int类型的数值
HashSet和ArrayList集合都有判断元素是否相同的方法,
boolean contains(Object o)
HashSet使用hashCode和equals方法,ArrayList使用了equals方法
LinkedHashSet
带有链表的式的集合该集合为了完善set集合无序的特点
TreeSet(防重复与HashSet相同)
默认采用了了排序
1.自然排序(sort()方法,其中集合所存类必须实现comparable接口,重写compareTo()方法)
对于compareTo方法来说返回1代表换位,0代表对象相等,-1不换位,(true相当1,false相当-1)
自然排序只能实现一次(也就是不能排序了一种信息后,想要用另一种信息进行排序是不可能的,但是如果按照一种排序,如果这种排序相等再进行另一种排序是可能),也就是不能对第二种信息进行排序(),由此引出定制排序(比较器)可以在创建TreeSet时,在其构造器能创建比较的匿名内部类
2.定制排序:建议使用匿名内部类,因为定制排序可能只应用一次,在构造方法添加Comparator实现类。
调用单一参数的sort(List l)方法代表由list元素(元素指集合内的类)上的compare方法决定顺序,
调用sort(List o,Comparator c)方法代表不会调用compareTo()方法,(建立内部类实现Comparator接口,重写compare()方法)
构造方法TreeSet(ComparatorE> comparator) ,因为参数是一个接口,所以比较创建一个接口类子类的对象
Map
(同样重写了equals和hashcode)
存键值对
Map元素实际是两个对象: 关键字key和值value ,key不能重复,value可以重复,如果存储相同的key后面存入的值会把之前的key所对应的值替换掉
注:可存null值
1.添加删除
2.判断
3.获取
HashSet LinkedHashMap 与LinkedHashSet目的相同
存储原理:HashMap的key的值的存储原理与HashSet的存储原理一样
Hashtable(线程安全),不可以存null的key常被HashMap代替
Properties(了解):类似map(是map的一个子类)但是键值都是String型,该集合通常用于对键值对形式的配置文件进行操作.
配置文件:将软件中可变的部分数据可以定义到一个文件中,方便以后更改,该文件称之为配置文件。
在以后开发的过程当中经常会使用到properties配置文件,该配置文件里面会保存一些数据,该配置文件所对应的类就是Properties
TreeMap:底层key与TreeSet相同,也就是说要实现接口重写方法,所以当你使用TreeMap的时候,里面所存类必须实现接口重写方法,不然会报错。
TreeMap默认是按照key进行排序的,如果想要针对value进行遍历需要把他转换为list集合,但是其实将key与value交换位置会更好
红黑树(二叉树)
TreeSet和TreeMap可以实现排序,但是底层数据结构又是怎么存的呢?
存储原理:第一个数存进来,放在最顶端,当第二数12来的时候,因为比23小,就放在了23树分叉的左侧,假如第三个数是13,那么它先和23比较,小与侧左侧分支
获取所有项
由于Map没有迭代器,而增强for循环其实就是迭代器的一个简化版本,所以也不能使用增强for循环,因此只能通过将他map转换为set来实现
1.keySet
keySet原理是先将Key转换为Set几个,然后通过mao.get(Key)得到value的值,从而实现遍历
2.entrySet
此时返回的Set中的Entry存的是一组组映射关系,这时通过getKey()和getValue()方法拆解映射关系
Entry是Set接口里的内部接口,这个接口中有getKey()和getValue()方法
3.values
直接遍历map集合中的value值
List集合与一般数组区别
1.一般数组在创建就必须知道大小,ArrayList会在调用方法时变大小
2.存放对象给一般数组必须指定位置,ArrayList可以指定位置,也可以不指定
3.ArrayList使用泛型,已确保添加进去的都是同已类型,而数组在定义的时候直接就确定存储的类型
数组就像身上编了号站成一排的人,要找第10个人很容易,根据人身上的编号很快就能找到。但插入、删除慢,要往某个位置插入或删除一个人时,后面的人身上的编号都要变。当然,加入或删除的人始终末尾的也快。
链表就像手牵着手站成一圈的人,要找第10个人不容易,必须从第一个人一个个数过去。但插入、删除快。插入时只要解开两个人的手,并重新牵上新加进来的人的手就可以。删除一样的道理。
Java中,ArrayList、LinkedList就是分别用数组和链表做内部实现的。
集合的选择
Array 查询快,增减慢
Link 增减快,查询慢,因此链表不推荐使用get()方法,所以迭代最好不用for循环
hash 唯一性,类中重写hashCode,equals方法
tree 排序,调用类实现接口重写方法
map和set都不能出入重复元素,set用add存入重复元素,会返回false,而map存入重复元素,value会覆盖原来value值
存储是一个元素用collection
存储一对元素,元素间有映射关系,用Map
保证唯一性用Set(注重独一无二的性质)
不保证用List(快,因为Set要不断比较)(是对付顺序的好帮手,知道索引位置)
泛型
更好的类型安全性,大多数应用于泛型,确保集合放入同类对象,它可以防止把Dog加到一群Cat三种,如果< Object> 表示只能放入Object对象,不能放入其它的对象,它的子类也不可以,因为不能判断出是它的哪个子类,但是可以放入它的父类。类似于只取到了Object的遥控器
没有泛型的时候,集合默认传入的是Object类型,应用泛型,让问题在编译器就能被抓到,而不会等到运行的时候才冒出来,如果没有泛型,编译器会很愉快地接收你把绵羊对象送到老虎集合中
没泛型传入一个fish类型,出来的是一个Object类型,有泛型传入什么类型出来什么类型
常用泛型的地方:
1.创建被泛型化类的实例,声明与指定泛型类型的变量
List <Song>songList=new ArrayList<Song>()
2.声明取用泛型类型的方法
void foo(List<Song> list) 这时候方法接收的实参集合必须是Song类型
以下为了解(在定义类接口,方法的时候使用泛型,此时相当是形参,当创建对象时明确泛型相当实参)
泛型类
class 类名<Q> 创建对象时要指定具体类型
泛型接口
interface 接口名<Q>
当父类和子类加上泛型后,两个类就不是继承关系了
泛型方法
public T getData(T data) {
returndata;
不确定传入的是什么类型,就要把传入的值返回 ,静态方法不可行,因为泛型的确定与对象有关
当数组的时候,animals型数组可以传入所有它的子类对象,那么集合也可以实现这个功能么?而上述说的泛型比如
ArrayList<Animals> 只能传入animals型,传入它的子类是绝对不可能的,那么到底要如何实现呢?,但是为什么数组可以传呢?这是因为数组是运行期检查,而集合是编译期检查
为了解决这个问题出现了通配符
通配符 ? 可以作为任何类型
泛型限定
上限: ? extends E:往集合填充元素的时候,往集合添加元素是E或者其子类
只有类在声明泛型时是 ? extends E 才可以ArrayList <Dog>al=new ArrayList<Animals>();
否则泛型必须统一,子类也不可以
下线 : ? supper E 从集合获取元素,获取的是元素E和其父类