Collection集合概述
- Collection集合是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
- JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现
1.1数组和集合的区别
- 相同点
都是容器,可以存储多个数据
- 不同点
- 数组的长度是不可变的,集合的长度是可变的
- 数组可以存基本数据类型和引用数据类型
- 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类
1.2 单列集合体系结构
List系列集合:添加的元素是有序、可重复、有索引
Set系列集合:添加的元素是无序、不重复、无索引
1.3 常见的方法
注意点: Collection是一个接口,我们不能直接创建他的对象。 所以,我们学习他的方法时,只能创建他的实现类对象 实现类:ArraysList 目的:为了学习Collection接口里面的方法 格式:Collection coll = new ArrayList<>();
1.4 Collection集合的遍历
1.4.1 迭代器遍历
- 迭代器介绍
- 迭代器,集合的专用遍历方式
- Iterator<E> iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到
- Iterator中的常用方法
boolean hasNext(): 判断当前位置是否有元素可以被取出
E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置
- 代码演示
public class IteratorDemo1 {
public static void main(String[] args) {
//创建集合对象
Collection<String> c = new ArrayList<>();
//添加元素
c.add("hello");
c.add("world");
c.add("java");
c.add("javaee");
//Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
Iterator<String> it = c.iterator();
//用while循环改进元素的判断和获取
while (it.hasNext()) {//判断当前位置上的元素是否为空
String s = it.next();//获取当前元素并将指针移动到下一个元素的位置
System.out.println(s);
}
}
}
1.4.2 增强for
- 介绍
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
- 实现Iterable接口的类才可以使用迭代器和增强for
- 简化数组和Collection集合的遍历
- 格式
for(集合/数组中元素的数据类型 变量名 : 集合/数组名) {
// 已经将当前遍历到的元素封装到变量中了,直接使用变量即可
}
- 代码演示
public class MyCollectonDemo1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
//1,数据类型一定是集合或者数组中元素的类型
//2,str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素
//3,list就是要遍历的集合或者数组
for(String str : list){
System.out.println(str);
}
}
}
1.4.3 lambda表达式
利用forEach方法,再结合lambda表达式的方式进行遍历、
- 代码演示
public class A07_CollectionDemo7 {
public static void main(String[] args) {
/*
lambda表达式遍历:
default void forEach(Consumer<? super T> action):
*/
//1.创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("zhangsan");
coll.add("lisi");
coll.add("wangwu");
//2.利用匿名内部类的形式
//底层原理:
//其实也会自己遍历集合,依次得到每一个元素
//把得到的每一个元素,传递给下面的accept方法
//s依次表示集合中的每一个数据
/* coll.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});*/
//lambda表达式
coll.forEach(s -> System.out.println(s));
}
}
一、List集合
2.1 List集合的概述和特点【记忆】
- List集合的概述
- 有序集合,这里的有序指的是存取顺序
- 用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
- 与Set集合不同,列表通常允许重复的元素
- List集合的特点
- 存取有序:存和取的元素顺序一致
- 可以重复:可以通过索引操作元素
- 有索引:存储的元素可以重复
2.2 List集合的特有方法
- List继承了Collection的所有方法
- List集合因为有索引,所以多了很多索引操作的方法
- 示例代码
public class MyListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//method1(list);
//method2(list);
//method3(list);
//method4(list);
}
private static void method4(List<String> list) {
// E get(int index) 返回指定索引处的元素
String s = list.get(0);
System.out.println(s);
}
private static void method3(List<String> list) {
// E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
//被替换的那个元素,在集合中就不存在了.
String result = list.set(0, "qqq");
System.out.println(result);
System.out.println(list);
}
private static void method2(List<String> list) {
// E remove(int index) 删除指定索引处的元素,返回被删除的元素
//在List集合中有两个删除的方法
//第一个 删除指定的元素,返回值表示当前元素是否删除成功
//第二个 删除指定索引的元素,返回值表示实际删除的元素
String s = list.remove(0);
System.out.println(s);
System.out.println(list);
}
private static void method1(List<String> list) {
// void add(int index,E element) 在此集合中的指定位置插入指定的元素
//原来位置上的元素往后挪一个索引.
list.add(0,"qqq");
System.out.println(list);
}
}
2.3 List集合的五种遍历方式【应用】
1. 迭代器
2. 列表迭代器
3. 增强for
4. Lambda表达式
5. 普通for循环
其中迭代器、增强for、lambda表达式与Collection集合中所用得遍历方式一样,因为List集合是有序的且有索引的,因此List集合的遍历方式多了列表迭代器、普通for循环两种
- 代码示例
//创建集合并添加元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//1.迭代器
/*Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}*/
//2.增强for
//下面的变量s,其实就是一个第三方的变量而已。
//在循环的过程中,依次表示集合中的每一个元素
/* for (String s : list) {
System.out.println(s);
}*/
//3.Lambda表达式
//forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
//并把每一个元素传递给下面的accept方法
//accept方法的形参s,依次表示集合中的每一个元素
//list.forEach(s->System.out.println(s) );
//4.普通for循环
//size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素
/*for (int i = 0; i < list.size(); i++) {
//i:依次表示集合中的每一个索引
String s = list.get(i);
System.out.println(s);
}*/
// 5.列表迭代器
//获取一个列表迭代器的对象,里面的指针默认也是指向0索引的
//额外添加了一个方法:在遍历的过程中,可以添加元素
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String str = it.next();
if("bbb".equals(str)){
//qqq
it.add("qqq");
}
}
System.out.println(list);
1、List的实现类ArrayList源码分析:
核心步骤:
1. 创建ArrayList对象的时候,他在底层先创建了一个长度为0的数组。
数组名字:elementDate,定义变量size。
size这个变量有两层含义:
①:元素的个数,也就是集合的长度
②:下一个元素的存入位置
2. 添加元素,添加完毕后,size++
扩容时机一:
3. 当存满时候,会创建一个新的数组,新数组的长度,是原来的1.5倍,也就是长度为15.再把所有的元素,全拷贝到新数组中。如果继续添加数据,这个长度为15的数组也满了,那么下次还会继续扩容,还是1.5倍。
扩容时机二:
4. 一次性添加多个数据,扩容1.5倍不够,怎么办呀?
如果一次添加多个元素,1.5倍放不下,那么新创建数组的长度以实际为准。
举个例子:
在一开始,如果默认的长度为10的数组已经装满了,在装满的情况下,我一次性要添加100个数据很显然,10扩容1.5倍,变成15,还是不够,
怎么办?
此时新数组的长度,就以实际情况为准,就是110
添加一个元素时的扩容:
添加多个元素时的扩容:
2、LinkedList源码分析:
底层是双向链表结构
核心步骤如下:
1. 刚开始创建的时候,底层创建了两个变量:一个记录头结点first,一个记录尾结点last,默认为null
2. 添加第一个元素时,底层创建一个结点对象,first和last都记录这个结点的地址值
3. 添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值
二、Set集合
Set 集合概述和特点【应用】
- 不可以存储重复元素
- 没有索引,不能使用普通for循环遍历
2.2 Set集合的使用【应用】
存储字符串并遍历
代码示例:
public class MySet1 {
public static void main(String[] args) {
//创建集合对象
Set<String> set = new TreeSet<>();
//添加元素
set.add("ccc");
set.add("aaa");
set.add("aaa");
set.add("bbb");
// for (int i = 0; i < set.size(); i++) {
// //Set集合是没有索引的,所以不能使用通过索引获取元素的方法
// }
//遍历集合
Iterator<String> it = set.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("-----------------------------------");
for (String s : set) {
System.out.println(s);
}
}
}
1、TreeSet集合
1.1 TreeSet集合概述和特点【应用】
- 不可以存储重复元素
- 没有索引
- 可以将元素按照规则进行排序
- TreeSet():根据其元素的自然排序进行排序
- TreeSet(Comparator comparator) :根据指定的比较器进行排序
1.2 TreeSet集合基本使用【应用】
存储Integer类型的整数并遍历
代码示例:
public class TreeSetDemo01 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();
//添加元素
ts.add(10);
ts.add(40);
ts.add(30);
ts.add(50);
ts.add(20);
ts.add(30);
//遍历集合
for(Integer i : ts) {
System.out.println(i);
}
}
}
1.3 自然排序Comparable的使用【应用】
- 案例需求
- 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
- 实现步骤
1. 使用空参构造创建TreeSet集合
+ 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
2. 自定义的Student类实现Comparable接口
+ 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
3. 重写接口中的compareTo方法
+ 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
- 代码实现
学生类
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
//按照对象的年龄进行排序
//主要判断条件: 按照年龄从小到大排序
int result = this.age - o.age;
//次要判断条件: 年龄相同时,按照姓名的字母顺序排序
result = result == 0 ? this.name.compareTo(o.getName()) : result;
return result;
}
}
测试类
public class MyTreeSet2 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<>();
//创建学生对象
Student s1 = new Student("zhangsan",28);
Student s2 = new Student("lisi",27);
Student s3 = new Student("wangwu",29);
Student s4 = new Student("zhaoliu",28);
Student s5 = new Student("qianqi",30);
//把学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
//遍历集合
for (Student student : ts) {
System.out.println(student);
}
}
}
1.4 比较器排序Comparator的使用【应用】
- 案例需求
- 存储老师对象并遍历,创建TreeSet集合使用带参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
- 实现步骤
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
- 代码实现
老师类
public class Teacher {
private String name;
private int age;
public Teacher() {
}
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类
public class MyTreeSet4 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
//o1表示现在要存入的那个元素
//o2表示已经存入到集合中的元素
//主要条件
int result = o1.getAge() - o2.getAge();
//次要条件
result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
return result;
}
});
//创建老师对象
Teacher t1 = new Teacher("zhangsan",23);
Teacher t2 = new Teacher("lisi",22);
Teacher t3 = new Teacher("wangwu",24);
Teacher t4 = new Teacher("zhaoliu",24);
//把老师添加到集合
ts.add(t1);
ts.add(t2);
ts.add(t3);
ts.add(t4);
//遍历集合
for (Teacher teacher : ts) {
System.out.println(teacher);
}
}
}
1.5 总结TreeSet的原理和使用(Comparable和comparator)
- TreeSet中的元素不允许重复,但是有序
- TreeSet采用树结构存储数据,存入元素时需要和树中元素进行对比,需要指定比较策略。可以通过Comparable和Comparator来指定比较策略。
- 实现了Comparable的系统类可以顺利存入TreeSet。自定义类可以实现Comparable接口来指定比较策略。
- 可创建Comparator接口实现类来指定比较策略,并通过TreeSet构造方法参数传入。这种方式尤其对系统类非常适用。
2、HashSet集合
2.1HashSet集合概述和特点【应用】
- 底层数据结构是哈希表
- 存取无序
- 不可以存储重复元素
- 没有索引,不能使用普通for循环遍历
2.2HashSet集合的基本应用【应用】
存储字符串并遍历
代码示例:
public class HashSetDemo {
public static void main(String[] args) {
//创建集合对象
HashSet<String> set = new HashSet<String>();
//添加元素
set.add("hello");
set.add("world");
set.add("java");
//不包含重复元素的集合
set.add("world");
//遍历
for(String s : set) {
System.out.println(s);
}
}
}
2.3 哈希值【理解】
- 哈希值简介
是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
- 如何获取哈希值
Object类中的public int hashCode():返回对象的哈希码值
- 哈希值的特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
3、LinkedHashSet集合
LinkedHashSet集合的特点和原理 特点:有序、不重复、无索引 原理:底层基于哈希表,比起HashSet多了一个双链表记录添加顺序,所以它是有序的 如果数据去重,默认使用HashSet,如果要求去重且存取有序,才使用LinkedHashSet
代码示例
public class a04_LinkedHashSetDemo {
public static void main(String[] args) {
//1、创建学生对象
Student s1 = new Student("zhangsan",21);
Student s2 = new Student("lisi",22);
Student s3 = new Student("wangwu",21);
Student s4 = new Student("zhangsan",21);
//2、创建集合并添加学生
LinkedHashSet<Student> lhs = new LinkedHashSet<>();
//添加元素
System.out.println(lhs.add(s1));
System.out.println(lhs.add(s2));
System.out.println(lhs.add(s3));
System.out.println(lhs.add(s4));
//打印集合
System.out.println(lhs);
}
}
三、单例集合总结
1、如果想要集合中的元素可重复
- 用ArrayList集合,基于数组的
2、如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
- 用LinkedList集合,基于链表的
3、如果想对集合中的元素去重
- 用HashSet集合,基于哈希表的
4、如果对集合中的元素去重,而且保证存取顺序
- 用LinkedHashSet集合,基于哈希表和双向链表的,效率低于HashSet
5、如果想对集合中的元素进行排序
- 用TreeSet集合,基于红黑树,后续也可以用List集合实现排序