集合知识
集合的体系结构
Java中集合有很多,总体可以分为两类Collection和Map.
Collection:是单列集合
单列集合特点:
一次往集合只能添加一个元素:添加商品的名字,如脉动,康师傅…
Map:是双列集合
一次添加一对数据:一次添加商品名称和对应价格,如:脉动+5元,康师傅+3元…
单列集合(Collection)
Collection是所有单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的.
体系图
Collection的方法
它的功能是全部单列集合都可以继承使用的.
根据条件删除集合元素
用Collection实现类对象调用removeIf方法(Predicate<? super E> filter)
参数是:Predicate函数式接口(lambda表达式介绍中有)的实现类.
以下是lambda表达式简化匿名内部类的代码.
//c1:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
//删除所有偶数
c1.removeIf(t -> t % 2 == 0);
System.out.println(c1);//[1, 3, 5, 7, 9]
各方法细节:
添加方法(add)细节:
细节1:如果我们往List集合中添加数据,那么方法永远返回true,因为List系列是允许重复的.
细节2:如果我们往Set集合中添加数据:
如果当前元素不存在,方法返回true,表示添加成功.
如果当前元素存在,方法返回false,表示添加失败.
因为Set系列集合不允许元素重复.
删除方法(remove)细节:
细节1:
因为Collection里面是共性方法,所以此时不能通过索引删除(Set系列集合没有索引),只能通过元素的对象进行删除.
细节2:方法有一个布尔类型返回值,删除成功返回true,删除失败(删除元素不存在)返回false.
判断元素是否包含方法(contacts)细节
细节:
底层依赖equals方法进行判断是否存在.
所以,如果集合中存储的是自定义对象,也想通过contacts方法来判断是否包含,那么javabean类中,一定要重写equals方法.
如果没有重写equals方法,默认使用使用它父类(Object)类中的equals方法,进行判断,而Object中的equals方法依靠地址值进行判断.所以需要在javabean类中重写equals方法.
IDEA快速重写equals方法:
在javabean类中使用alt+insert快捷键选择带equals的选项.
判断集合是否为空方法(isEmpty)细节
细节:这里的空指判断集合长度是否为0.
Collection系列集合的三种通用遍历
1.迭代器遍历
特点:不依靠索引获取集合元素.
迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式.
Collection集合获取迭代器:通过iterator()方法获取迭代器对象默认指向当前集合的0索引.
迭代器中的常用方法:
hasNext方法:判断当前位置是否有元素,有元素返回true,没有元素返回false.
next方法:获取当前位置的元素,并且将迭代器对象移向下一位置.
Collection<String> c2 = new ArrayList<>();
c2.add("aaa");
c2.add("bbb");
c2.add("ccc");
c2.add("ddd");
System.out.println(c2);
Iterator<String> iterator = c2.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
/*aaa
bbb
ccc
ddd*/
迭代器的细节注意点:
1.报错NOSuchElementException
当迭代器已经指向集合尾部(集合外)再去调用next方法时发生.
2.迭代器遍历完成指针不会复位.
当一个集合遍历完成还想再遍历一遍,需要再获取一个新的Iterator对象(指针不会复位).
3.循环中只能使用一次next方法
调用一次next方法指针会移动一次,所以每次调用next方法获取的元素不是同一个,如果想复用需要变量接收.
4.迭代器在遍历的时候不能使用集合的方法增加或删除.
如果要删除要使用迭代器的remove(无参数)方法进行删除
如果添加暂时没有办法.
Iterator<String> iterator = c2.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
if(next.equals("bbb")){
iterator.remove();
}
}
System.out.println(c2);//[aaa, ccc, ddd]
2.增强for遍历
增强for的底层就是一个迭代器,就是为了简化迭代器的代码书写.
所有单列集合(Collection)和数组才可以用增强for进行遍历.
格式:
for(元素数据类型 变量名:数组或单列集合){
循环体
}
Collection<String> c3 = new ArrayList<>();
c3.add("aaa");
c3.add("bbb");
c3.add("ccc");
c3.add("ddd");
for (String s : c3) {
System.out.println(s);
}
System.out.println(c3);
增强for的细节
修改增强for中的变量,不会改变集合中原本的数据.
因为s是一个第三方变量,在循环过程中依次表示集合中每一个元素.
s只是接收了原理集合变量中的值.
3.lambda表达式遍历(含lambda介绍)
通过集合的forEach方法进行遍历
通过匿名内部类实现接口写法:
Collection<String> c3 = new ArrayList<>();
Collection<String> c4 = new ArrayList<>();
c3.add("aaa");
c3.add("bbb");
c3.add("ccc");
c3.add("ddd");
c3.forEach(new Consumer<String>() {
@Override
//s依次表示集合中每一个数据
public void accept(String s) {
System.out.println(s);
}
});
lambda表达式简化代码
因为lambda可以简化匿名内部类的书写.
但不是所有匿名内部类都可以用lambda表达式简化
前提:
lambda表达式只能简化函数式接口的匿名内部类,抽象类不可以用lambda表达式简化.
函数式接口:
首先是一个接口,并且有且只有一个抽象方法的接口叫函数式接口,接口上方可以加@Functionalinterface注解.
如果接口中有多个抽象方法,lambda表达式的方法体不知道要传递给谁.
lambda表达式的省略规则:
1.参数类型可以省略.
2.如何只有一个参数参数类型可以省略,同时()可以省略.
3.如果lambda表达式的方法体只有一行,大括号,分号,return可以省略不写,需要同时省略.
Collection<String> c3 = new ArrayList<>();
Collection<String> c4 = new ArrayList<>();
c3.add("aaa");
c3.add("bbb");
c3.add("ccc");
c3.add("ddd");
c3.forEach(s->System.out.println(s));
List系列集合:
特点:添加的元素是有序,可重复,有索引.
有序:存和取的顺序一致.
可重复:集合中存储的元素可以重复.
有索引:可以通过索引获取集合中的元素.
List是单列集合的一种,所以Collection的方法和遍历方式,List系列集合都适用.
List集合的特有方法:
List集合的特有方法细节:
添加(add)方法的细节:
在指定索引插入元素,原来元素依次后移.
删除(remove)方法的细节:
此时有两个remove方法.一个是单列集合共有的删除方法,一个是List系列集合特有删除方法.
单列集合共有的删除方法:根据对象删除,返回布尔类型.
List系列集合特有删除方法:根据索引删除,返回删除元素.
注意点(重构方法调用优先级):
如果集合存储的是数字时,调用remove方法时是根据索引删除还是数字元素删除?
在调用方法时出现了,方法的重载现象
优先会调用.实参和形参类型一致的方法
List<Integer> list2=new ArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
list2.add(4);
System.out.println(list2);//[1, 2, 3, 4]
int i1=1;
list2.remove(i1);//[1, 3, 4]
Integer i2=1;
list2.remove(i2);//[3, 4]
List系列集合的实现类
ArrayList
Array:数组
List:属于List系列.
LinkedList
Linked:链表
List:属于List系列.
Set系列集合:
特点:添加的元素是无序,不重复,无索引.
无序:存和取的顺序不一定一致.
不重复:集合中存储的元素不可以重复.(可以利用此特性去重)
无索引:不可以通过索引获取集合中的元素.
Set是一个接口继承于Collection接口,Set的方法与基本Collection方法基本一致.
Set系列集合的实现类
如果数据去重默认使用HashSet.
如果要去有序去重使用LinkedHashSet
HashSet
**特点:**无序,不重复,无索引.
Hash:哈希表
哈希表:是对于增删改查都较好的数据结构.
哈希表组成:
JDK8之前:数组+链表
JDK8之后:数组+链表+红黑树
哈希表的存储:
1.希表在存储时不是从第一个开始存储,是根据元素的哈希值和数组的长度计算出应存入的位置.
公式:int index=(数组长度-1)&哈希值.
2.判断此位置是否为null,如果是null直接存入.
3.如果不是null,表示有元素,则调用equals方法比较属性值是否相同.
4.equals方法比较后相同不存.不同,存入数组形成链表.
JDK8之前:新元素存入数组,老元素挂在新元素下面.
JDK8之后:新元素挂在老元素下面…
哈希值:
对象的整数表现形式.
如果没有重写hashCode方法不同对象计算的哈希值是不同的
如果已经重写hashCode方法,不同的对象只要属性相同,计算出的哈希值就相同
但在小部分情况下,不同的属性值或不同的地址值计算出来的哈希值可能一样(哈希碰撞).
总结:
如果在HashSet集合存储的是自定义对象,必须重写hashCode和equals方法.从而,满足去重功能.
重写hashCode的目的是用对象的属性值计算哈希值.
重写equals方法是通过对象的属性值进行比较.
Set:属于Set系列.
TreeSet
特点: 可排序,不重复,无索引.
可排序:默认从小到大排序.
如果存储的是字符类型:按ASCII码表中的数字进行顺序排序.
字符串比较是从第一个字符开始比较相同比较第二位以此类推.
TreeSet<Integer> ts = new TreeSet<>();
ts.add(4);
ts.add(5);
ts.add(1);
ts.add(2);
ts.add(3);
System.out.println(ts);//[1, 2, 3, 4, 5]
TreeSet底层是基于红黑树的数据结构实现排序的,增删改查性能都比较好.
TreeSet的两种比较方式:
方式1:
默认排序/自然排序:javabean类实现Comparable接口指定比较规则.
@Override
public int compareTo(Student3 o) {
return this.getAge() - o.getAge();
}
this:表示当前要添加的元素
o:表示已经在红黑树存在的元素.
返回值:
负数:认为要添加的元素是小的,存左边.
正数:认为要添加的元素是大的,存右边.
0:要添加的元素已存在,舍弃不存.
方式2:
比较器排序:创建TreeSet对象时,传递比较器Comparator指定比较规则.
TreeSet<String> ts3 = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return 0;
}
});
o1:表示当前要添加的元素.
o2:表示已经在红黑树存在的元素.
返回值:
负数:认为要添加的元素是小的,存左边.
正数:认为要添加的元素是大的,存右边.
0:要添加的元素已存在,舍弃不存.
两种方法是使用原则:
默认使用第一种,如果第一种不满足当前需求,再用第二种.
这里不需要重写hashCode和equals方法,因为hashCode和equals方法与哈希表有关.
TreeSet底层是红黑树.但只要指定排序规则.不然无法实现排序功能.
Tree:树
Set:属于Set系列.
LinkedHashSet
特点: 有序,不重复,无索引.
LinkedHashSet可以保证存储和取出的顺序是一致的.(底层也是哈希表,不过多了一个双两表机制记录存储顺序)
Linked:链表
HashSet:父类是HashSet