Java种的集合分为单列集合和双列集合,单列集合的最高层接口是Collection,双列集合的最高层是Map,这里先介绍单列集合
单列集合
体系结构:
注:红色框都为接口,蓝色框都为实现类(实现类Vector已经被淘汰)
Collection:
Collection是一个接口,不能直接创建他的对象,所以学习他的方法时,只能创建他的实现类对象。例如ArrayList。
代码演示:
public class CollectionDemo {public static void main(String[] args) {/*public boolean add(E e) 添加public void clear() 清空public boolean remove(E e) 删除public boolean contains(Object obj) 判断是否包含public boolean isEmpty() 判断是否为空public int size 集合长度*/Collection<String> coll = new ArrayList<>();//1.添加//注:添加方法会返回一个布尔类型的值//如果我们往List系列集合中添加数据,一定会返回true,因为List系列集合是允许元素重复的//如果往Set系列集合中添加数据,当元素不存在时才会返回true,否则返回false,因为Set集合时不允许重复的coll.add("aa");coll.add("bb");coll.add("cc");System.out.println(coll);//2.清空coll.clear();System.out.println(coll);//再添加回来coll.add("aa");coll.add("bb");coll.add("cc");//3.删除coll.remove("bb");System.out.println(coll);//4.判断是否包含boolean result1 = coll.contains("bb");System.out.println(result1);//5.判断是否为空boolean result2 = coll.isEmpty();System.out.println(result2);//6.集合长度int size = coll.size();System.out.println(size);}
}
运行结果:
(1)contains方法的拓展:
如果让contains方法判断自定义的类对象,需要重写equals方法才能得到正确结果,因为contains方法底层就调用了equals方法,如果没有重写,默认使用的就是Object类中的equals方法,比较的是地址值。
没有重写equals时
代码演示:
学生类Student:
public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}public String toString() {return "Student{name = " + name + ", age = " + age + "}";}
}
测试类Test:
public class CollectionContainsDemo {public static void main(String[] args) {Collection<Student> coll = new ArrayList<>();Student s1 = new Student("xiaobai",21);Student s2 = new Student("xiaohei",22);Student s3 = new Student("xiaohei", 22);//与上面s2一样coll.add(s1);coll.add(s2);boolean result = coll.contains(s3);System.out.println(result);}
}
运行结果:
重写equals时
代码演示:
Student类:
加入下面这段代码
@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return age == student.age && Objects.equals(name, student.name);}
测试类不改变
运行结果:
(2)Collection系列集合三种通用的遍历方式:
①迭代器遍历
②增强for遍历
③lambda表达式遍历
接下来进行代码演示:
①迭代器遍历:
迭代器遍历相关的三个方法:
Iterator<E> iterator() : 获取一个迭代器对象
boolean hasNext() : 判断当前指向的位置是否有元素
E next() : 获取当前指向的元素并移动指针
代码演示:
public class ErgodicDemo {public static void main(String[] args) {//迭代器遍历相关的三个方法://Iterator<E> iterator():获取一个迭代器对象//boolean hasNext(): 判断当前指向的位置是否有元素//E next():获取当前指向的元素并移动指针//创建集合并添加元素Collection<String> coll = new ArrayList<>();coll.add("aa");coll.add("bb");coll.add("cc");//获取迭代器对象Iterator<String> it = coll.iterator();while(it.hasNext()) {String str = it.next();System.out.println(str);}}
}
运行结果:
迭代器注意点:
1.指针指向没有元素的位置时再强行调用next方法,会报错NoSuchElementException
2.迭代器遍历完毕,指针不会复位
3.循环中只能用一次next方法
4.迭代器遍历时,不能用集合的方法进行增加或者删除(可以使用迭代器的remove方法删除)
②增强for遍历:
格式:
for(数据类型 变量名 : 集合/数组){
方法体
}
快速生成方式:集合名字.for 再按回车
代码演示:
public class EnhancedFor {public static void main(String[] args) {//创建集合并存储元素Collection<String> coll = new ArrayList<>();coll.add("aa");coll.add("bb");coll.add("cc");//增强for遍历//循环遍历中,下面的第三方变量s会依次表示集合中的每一个元素for(String s : coll) {System.out.println(s);}}
}
运行结果:
增强for注意点:
修改增强for中的变量,不会改变集合中原本的数据。因为增强for中使用了参数中设置的第三方变量存储,修改的是第三方变量的值。
③Lambda表达式:
代码演示:
public class LambdaDemo {public static void main(String[] args) {//default void forEach(Consumer<? super T> action)://1.创建集合并添加元素Collection<String> coll = new ArrayList<>();coll.add("aa");coll.add("bb");coll.add("cc");//2.使用匿名内部类遍历coll.forEach(new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);}});System.out.println("----------------------");//3.使用Lambda表达式遍历coll.forEach(s -> System.out.println(s));}
}
运行结果:
④三种遍历方式适用情况:
若在遍历过程中需要删除元素,则使用迭代器
若仅仅想遍历,则使用增强for或Lambda表达式
1.List
List系列集合特点:有序,可重复,有索引
虽然Collection的方法List都继承了,但List集合因为有索引,所以多了很多索引操作的方法,下面介绍一下List系列集合独有的方法。
List系列集合独有的方法
代码演示:
public class ListDemo {public static void main(String[] args) {/*List系列集合独有的方法:void add(int index,E element) 在此集合中的指定位置插入指定的元素E remove(int index) 删除指定索引处的元素,返回被删除的元素E set(int index,E element) 修改指定索引处的元素,返回被修改的元素E get(int index) 返回指定索引处的元素*///1.创建一个集合并添加元素List<String> list = new ArrayList<>();list.add("aa");list.add("bb");list.add("cc");//2.void add(int index,E element) 在此集合中的指定位置插入指定的元素list.add(1,"ee");System.out.println(list);System.out.println("----------------");//3.E remove(int index) 删除指定索引处的元素,返回被删除的元素/*remove注意点:当集合中存储的是int类型的数据,删除时输入int类型整数,会首先认为是删除索引,但是输入一个Integer类型的变量,则会认为是删除元素*/String remove = list.remove(1);System.out.println("被删除的元素:" + remove);System.out.println(list);System.out.println("----------------");//4.E set(int index,E element) 修改指定索引处的元素,返回被修改的元素String result = list.set(1, "ee");System.out.println("被修改的元素:" + result);System.out.println(list);System.out.println("----------------");//5.E get(int index) 返回指定索引处的元素System.out.println(list.get(0));}
}
运行结果:
List集合的遍历方式
List继承了Collection,所以Collection中的遍历方式在List中都可以使用,即:
①迭代器遍历
②增强for遍历
③Lambda表达式遍历
此外List系列集合还有自己的遍历方式,即:
④普通for遍历
代码演示:
public class OrdinaryFor {public static void main(String[] args) {//创建集合并添加元素List<String> list = new ArrayList<>();list.add("aa");list.add("bb");list.add("cc");//遍历for (int i = 0; i < list.size(); i++) {String str = list.get(i);System.out.println(str);}}
}
运行结果:
⑤列表迭代器遍历
它可以在遍历时添加和删除元素
代码演示:
public class ListIteraterDemo {public static void main(String[] args) {//创建集合并添加元素List<String> list = new ArrayList<>();list.add("aa");list.add("bb");list.add("cc");//创建列表迭代器ListIterator<String> it1 = list.listIterator();//遍历while(it1.hasNext()) {String str = it1.next();System.out.println(str);}//它可以在遍历中添加或删除元素ListIterator<String> it2 = list.listIterator();while(it2.hasNext()) {String str = it2.next();if("aa".equals(str)) {it2.add("ee");}if("cc".equals(str)) {it2.remove();}}System.out.println(list);}
}
运行结果:
五种遍历方式总结:
在遍历过程中需要删除元素时,使用迭代器
在遍历过程中需要添加元素时,使用列表迭代器
如果仅仅想遍历,使用增强for或者Lambda表达式
如果遍历时想操作索引,可以使用普通for
(1)ArrayList
ArrayList底层是数组结构
底层原理:
①利用空参创建的集合,在底层创建一个默认长度为0的数组
②添加第一个元素时,底层会创建一个新的长度为10的数组
如图(elementDate是数组名,size是指针)
③存满时,会扩容1.5倍(即第一次存满后数组大小扩容到15)
④如果一次添加多个元素,扩容到1.5倍放不下,则新建数组的长度以实际为准(即一开始十 个数据存满数组的情况下,再一次性添加100个元素,那么新数组长度就为110)
迭代器底层原理图解:
(2)LinkedList
LinkedList底层数据结构是双向链表,查询慢,增删快,但是如果操作的是首尾元素,速度也是极快的
双向链表模型:
由于上述特点,LinkedList中多了很多首尾操作的特有API,例如:
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
添加元素原理图解:
2.Set
Set系列集合特点:无序,不可重复,无索引
Set中的方法:
Set是一个接口,这个接口中的方法基本上与Collection中的API一致,方法如下
public boolean add(E e) 添加
public void clear() 清空
public boolean remove(E e) 删除
public boolean contains(Object obj) 判断是否包含
public boolean isEmpty() 判断是否为空
public int size 集合长度
这部分方法在前文Collection模块中已经演示,这里不再演示。
Set的遍历方法:
也可以使用Collection中的遍历方法,如:
①迭代器遍历
②增强for遍历
③Lambda表达式遍历
这三种方法前文已经演示,注意因为Set系列集合没有索引,所以不能使用普通for遍历
(1)HashSet
特点:
无序,不可重复,无索引
HashSet底层原理概要:
HashSet集合底层采取哈希表存储数据
哈希表是一种对于增删查改数据性能都较好的结构
接下来我们先介绍一下哈希表
哈希表:
组成:
JDK8之前:数组 + 链表
JDK8开始:数组 + 链表 + 红黑树
哈希表中有一个非常重要的值:哈希值
哈希值:
可以理解为对象的整数表现形式
·根据hashCode方法算出来的int类型的整数
·该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
·一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
对象哈希值的特点:
·如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
·如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值也一样
·在小部分情况下,不同的属性值或者地址值计算出来的哈希值有可能一样(哈希碰撞)
未重写hashCode情况代码演示
编写学生类来创建对象
Student:
public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}public String toString() {return "Student{name = " + name + ", age = " + age + "}";}
}
编写测试类:
HashSetDemo1:
public class HashSetDemo1 {public static void main(String[] args) {//创建两个属性一样的学生对象Student s1 = new Student("xiaobai", 21);Student s2 = new Student("xiaobai", 21);System.out.println(s1.hashCode());System.out.println(s2.hashCode());}
}
运行结果
未重写hashCode情况代码演示
在Student类中加入了重写的hashCode方法
Student:
public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}@Overridepublic int hashCode() {return Objects.hash(name, age);}public String toString() {return "Student{name = " + name + ", age = " + age + "}";}
}
HashSetDemo1中的代码不变
运行结果
HashSet的底层原理
注意:
1.如果元素应存入位置已经有多个元素形成链表,需要调用equals方法比较每个元素,若都不一样,则挂在链表最后。
2.默认加载因子:默认加载因子为0.75表示当数组里面存了16 * 0.75 = 12个元素时,数组就会扩容成原来的两倍。
3.当链表长度大于8而且数组长度大于等于64时,这个链表将会自动转成红黑树,从而提高查找效率。
4.如果集合中存储的是自定义对象,必须要重写hashCode和equals方法。
HashSet小练习:
需求:创建一个存储学生对象的集合,存储多个学生对象 使用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
代码演示:
Student类:
public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return age == student.age && Objects.equals(name, student.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}public String toString() {return "Student{name = " + name + ", age = " + age + "}";}
}
测试类HashSetTest1:
public class HashSetTest1 {public static void main(String[] args) {/*需求:创建一个存储学生对象的集合,存储多个学生对象使用程序实现在控制台遍历该集合要求:学生对象的成员变量值相同,我们就认为是同一个对象*/HashSet<Student> hashSet = new HashSet<>();//创建学习对象 三个xiaobai 两个xiaohei 一个xiaohuangStudent s1 = new Student("xiaobai",21);Student s2 = new Student("xiaobai",21);Student s3 = new Student("xiaobai",21);Student s4 = new Student("xiaohei",20);Student s5 = new Student("xiaohei",20);Student s6 = new Student("xiaohuang",20);//添加到集合中hashSet.add(s1);hashSet.add(s2);hashSet.add(s3);hashSet.add(s4);hashSet.add(s5);hashSet.add(s6);//遍历Iterator<Student> it = hashSet.iterator();while(it.hasNext()) {Student str = it.next();System.out.println(str);}}
}
运行结果:
注意:
如果未在Student类中重写hashCode和equals方法,属性值相等的对象也是会存入集合的。
LinkedHashSet:
特点:
LinkedHashSet是有序(存储和取出的元素顺序一致),无重复,无索引的
代码演示:
public class LinkedHashSetDemo1 {public static void main(String[] args) {LinkedHashSet<Student> lhs = new LinkedHashSet<>();Student s1 = new Student("xiaobai", 20);Student s2 = new Student("xiaohei", 21);Student s3 = new Student("xiaohuang", 22);Student s4 = new Student("xiaohei", 21);lhs.add(s1);lhs.add(s2);lhs.add(s3);lhs.add(s4);System.out.println(lhs);}}
运行结果:
原理:
底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序
如下图红色双线表示双链表存储
适用情况:
如果只需要数据去重,那么默认使用HashSet
如果要求去重且存取有序,就使用LinkedHashSet
(2)TreeSet
特点:
不重复,无索引,可排序(按照元素的默认规则从小到大排序)
TreeSet集合底层是基于红黑树的数据结构(不了解红黑树可以查看作者的另一篇文章《Java基合进阶——数据结构》)实现排序的,增删改查性能都较好
代码演示:
public class TreeSetDemo1 {public static void main(String[] args) {TreeSet<Integer> ts = new TreeSet<>();ts.add(3);ts.add(5);ts.add(2);ts.add(1);ts.add(4);System.out.println(ts);}
}
运行结果:
若使用Collection中的那三种遍历方式遍历,也会得到这个顺序的结果
排序规则:
对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序
对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序
(字符串中从第一个字母挨个比较,并且若前若干个字母一样,后面没有字母的那个字符串排在后边还有字母的字符串前面,例如:aaa,ab,aba 这样排序)
TreeSet的两种比较方式
TreeSet有两种比较方式,为默认排序/自然排序和比较器排序
使用原则:
默认使用第一种,如果第一种不能满足当前需求(比如字符串中默认的规则需要修改,或者Integer默认排序规则为从小到大若不是用这个规则则使用第二种),就使用第二种
1.默认排序/自然排序:
对数据类型可直接使用,对自定义对象使用需要先在自定义的JavaBean中实现Comparable接口,并重写其中的compareTo方法,在这个方法中可以定义根据什么排序。
下面举例一个重写后的compareTo方法,它的作用是只看年龄,按照年龄的升序进行排列
@Override
public int compareTo(Student o){return this.getAge() - o.getAge();
}
this:表示当前要添加的元素 o:表示已经在红黑树存在的元素
返回值:
负数:认为要添加的元素是小的,存左边
正数:认为要添加的元素是大的,存右边
0 :认为要添加的元素已经存在,舍弃
当添加某元素后TreeSet的数据结构不满足红黑树的规则了,它就会自己通过调整节点颜色或左旋右旋调整来满足红黑树的规则(想要详细了解可以看作者的另一篇文章《Java集合进阶——数据结构》)
2.比较器排序
创建TreeSet对象时候,传递比较器Comparator指定规则
比较器排序小练习1:
需求:存入四个字符串:"c","ab","df","qwer"
按照长度排序,如果一样长则按照首字母排序
代码演示:
public class TreeSetTest1 {public static void main(String[] args) {//需求:存入四个字符串:"c","ab","df","qwer"//按照长度排序,如果一样长则按照首字母排序//创建TreeSet对象TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {//o1为要添加的元素//o2为已经在红黑树中存在的元素//返回值的规则跟之前一样@Overridepublic int compare(String o1, String o2) {//根据长度排序int i = o1.length() - o2.length();//根据字母排序//如果i == 0即长度相等,那么就使用默认的比较规则,按字母排序,//如果长度不相等,那么直接根据长度排序i = i == 0 ? o1.compareTo(o2) : i;return i;}});//添加元素ts.add("c");ts.add("ad");ts.add("df");ts.add("qwer");//输出System.out.println(ts);}}
运行结果:
比较器排序小练习2:
需求:创建5个学生对象
属性:姓名,年龄,语文成绩,数学成绩,英语成绩
按照总分从高到低输出到控制台
如果总分一样,按照语文成绩排
如果语文成绩一样,按照数学成绩排
如果数学成绩一样,按照英语成绩排
如果英语成绩一样,按照年龄排
如果年龄一样,按照姓名的字母顺序排
如果都一样,认为是同一个学生,不存
代码演示:
Student2:
public class Student2 implements Comparable<Student2>{private String name;private int age;private int chineseGrade;private int mathGrade;private int englishGrade;public Student2() {}public Student2(String name, int age, int chineseGrade, int mathGrade, int englishGrade) {this.name = name;this.age = age;this.chineseGrade = chineseGrade;this.mathGrade = mathGrade;this.englishGrade = englishGrade;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}/*** 获取* @return chineseGrade*/public int getChineseGrade() {return chineseGrade;}/*** 设置* @param chineseGrade*/public void setChineseGrade(int chineseGrade) {this.chineseGrade = chineseGrade;}/*** 获取* @return mathGrade*/public int getMathGrade() {return mathGrade;}/*** 设置* @param mathGrade*/public void setMathGrade(int mathGrade) {this.mathGrade = mathGrade;}/*** 获取* @return englishGrade*/public int getEnglishGrade() {return englishGrade;}/*** 设置* @param englishGrade*/public void setEnglishGrade(int englishGrade) {this.englishGrade = englishGrade;}public String toString() {int sum = chineseGrade + mathGrade + englishGrade;return "Student2{name = " + name + ", age = " + age + ", chineseGrade = " + chineseGrade + ", mathGrade = " + mathGrade + ", englishGrade = " + englishGrade + ", sum = " + sum + "}";}@Overridepublic int compareTo(Student2 o) {int score1 = this.getChineseGrade() + this.getMathGrade() + this.getEnglishGrade();int score2 = o.getChineseGrade() + o.getMathGrade() + o.getEnglishGrade();int i = score1 - score2;i = i == 0 ? this.getChineseGrade() - o.getChineseGrade() : i;i = i == 0 ? this.getMathGrade() - o.getMathGrade() : i;i = i == 0 ? this.getEnglishGrade() - o.getEnglishGrade() : i;i = i == 0 ? this.getAge() - o.getAge() : i;i = i == 0 ? this.getName().compareTo(o.getName()) : i;return i;}
}
测试类TreeSetTest2:
public class TreeSetTest2 {public static void main(String[] args) {//需求:创建5个学生对象//属性:姓名,年龄,语文成绩,数学成绩,英语成绩//按照总分从高到低输出到控制台//如果总分一样,按照语文成绩排//如果语文成绩一样,按照数学成绩排//如果数学成绩一样,按照英语成绩排//如果英语成绩一样,按照年龄排//如果年龄一样,按照姓名的字母顺序排//如果都一样,认为是同一个学生,不存//创建集合并定义排序规则TreeSet<Student2> ts = new TreeSet<>();//创建对象Student2 s1 = new Student2("laoda",20,65,66,67);Student2 s2 = new Student2("laoer",21,67,66,67);Student2 s3 = new Student2("laosan",22,65,66,67);Student2 s4 = new Student2("laosi",21,65,68,67);Student2 s5 = new Student2("laowu",22,65,80,69);//添加对象ts.add(s1);ts.add(s2);ts.add(s3);ts.add(s4);ts.add(s5);for (Student2 stu : ts) {System.out.println(stu);}}
}
运行结果:
集合工具类Collections
常用API:
public static <T> boolean addAll(Collection<T> c, T...elements) 批量添加元素 public static void shuffle(List<?> list) 打乱List集合元素的顺序 public static <T> void sort(List<T> list, T key) 排序 public static <T> void sort(List<T> list, Comparator<T> c) 根据指定的规则进行排序 public static <T> int binarySearch(List<T> list, T key) 以二分查找法查找元素 public static <T> void copy(List<T> dest, List<T> src) 拷贝集合中的元素 public static <T> int fill(List<T> list, T obj) 使用指定的元素填充集合 public static <T> void max/min(Collection<T> coll) 根据默认的自然排序获取最 大/最小值 public static <T> void swap(List<?> list, int i, int j) 交换集合中指定位置的元素
代码演示:
public class CollectionsDemo1 {public static void main(String[] args){/*public static <T> boolean addAll(Collection<T> c, T...elements) 批量添加元素public static void shuffle(List<?> list) 打乱List集合元素的顺序public static <T> void sort(List<T> list, T key) 排序public static <T> void sort(List<T> list, Comparator<T> c) 根据指定的规则进行排序public static <T> int binarySearch(List<T> list, T key) 以二分查找法查找元素public static <T> void copy(List<T> dest, List<T> src) 拷贝集合中的元素public static <T> int fill(List<T> list, T obj) 使用指定的元素填充集合public static <T> void max/min(Collection<T> coll) 根据默认的自然排序获取最大/最小值public static <T> void swap(List<?> list, int i, int j) 交换集合中指定位置的元素*///创建集合ArrayList<String> list = new ArrayList<>();//1.public static <T> boolean addAll(Collection<T> c, T...elements) 批量添加元素System.out.println("----------批量添加元素----------");//注意只能添加Collection下的类Collections.addAll(list,"aaa","bbb","ccc","123");System.out.println(list);//2.public static void shuffle(List<?> list) 打乱List集合元素的顺序System.out.println("----------打乱List集合元素的顺序----------");Collections.shuffle(list);System.out.println(list);//3.public static <T> void sort(List<T> list, T key) 以二分查找法查找元素System.out.println("----------排序----------");Collections.sort(list);System.out.println(list);//4.public static <T> void sort(List<T> list, Comparator<T> c) 根据指定的规则进行排序System.out.println("----------根据指定的规则进行排序----------");Collections.sort(list, new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {//按默认字符串比较规则排序return o1.compareTo(o2);}});System.out.println(list);//5.public static <T> int binarySearch(List<T> list, T key) 以二分查找法查找元素System.out.println("----------以二分查找法查找元素----------");System.out.println(list);int i = Collections.binarySearch(list, "bbb");System.out.println(i);//6.public static <T> void copy(List<T> dest, List<T> src) 拷贝集合中的元素System.out.println("----------拷贝集合中的元素----------");//新建一个集合ArrayList<String> listCopy = new ArrayList<>();//注意被拷贝的集合长度要与拷贝集合长度一致Collections.addAll(listCopy,"","","","");Collections.copy(listCopy,list);System.out.println(listCopy);//7.public static <T> int fill(List<T> list, T obj) 使用指定的元素填充集合System.out.println("----------使用指定的元素填充集合----------");Collections.fill(list,"666");System.out.println(list);//8.public static <T> void max/min(Collection<T> coll) 根据默认的自然排序获取最大/最小值System.out.println("----------根据默认的自然排序获取最大/最小值----------");String max = Collections.max(listCopy);String min = Collections.min(listCopy);System.out.println(max);System.out.println(min);//9.public static <T> void swap(List<?> list, int i, int j) 交换集合中指定位置的元素System.out.println("----------交换集合中指定位置的元素----------");System.out.println(listCopy);Collections.swap(listCopy,1,2);System.out.println(listCopy);}
}
运行结果:
单列集合使用场景
1.ArrayList
如果想要集合中的元素可重复,(用的最多)用ArrayLst集合,基于数组的。
2.LinkedList
如果想要集合中的元素可重复,而且当前的增删操作明显多于查询,用LinkedList,基于链表的
3.HashSet
如果想对集合中的元素去重,(用的最多)用HashSet,基于哈希表的
4.LinkedHashSet
如果相对集合中的元素去重,而且保证存取顺序,用LinkedHashSet,基于哈希表和双链表,效率低于HashSet
5.TreeSet
如果相对集合中的元素进行排序,用TreeSet,基于红黑树。后续也可使用List集合实现排序
双列集合
介绍:
双列集合每次添加添加两个(或者说一对)元素
如图:
左边这列称为键,不可以重复,右边这列称为值,可以重复,键跟值是一一对应的,每一个键只能找到自己的值。
一对一一对应的键和值统称为一个键值对或者键值对对象,Java中也叫Entry对象。
体系结构
由于Hashtable和Properties与IO有关,所以先不讲解
MAP
常用API
V put(K key,V value) 添加/覆盖元素 V remove(Object key) 根据键删除键值对元素 void clear() 移除所有的键值对元素 boolean containsKey(Object key) 判断集合是否包含指定的键 boolean containsValue(Object value) 判断集合是否包含指定的值 boolean isEmpty() 判断集合是否为空 int size() 集合的长度,即集合中键值对的个数
代码演示:
public class MapDemo1 {public static void main(String[] args) {/*V put(K key,V value) 添加/覆盖元素V remove(Object key) 根据键删除键值对元素void clear() 移除所有的键值对元素boolean containsKey(Object key) 判断集合是否包含指定的键boolean containsValue(Object value) 判断集合是否包含指定的值boolean isEmpty() 判断集合是否为空int size() 集合的长度,即集合中键值对的个数*///创建集合对象//因为Map为接口,所以利用HashMap创建对象Map<String,String> m1 = new HashMap<>();//添加/覆盖元素System.out.println("----------添加/覆盖元素----------");//若要添加的键不存在,则直接添加到集合中并返回null//若要添加的键存在,则将原来的键值对对象覆盖,并返回被覆盖的值String result1 = m1.put("灰太狼", "红太狼");m1.put("美羊羊","沸羊羊");m1.put("懒羊羊","小灰灰");System.out.println(m1);System.out.println(result1);//新添加一个已经存在的键String result2 = m1.put("美羊羊","喜羊羊");System.out.println(result2);System.out.println(m1);//根据键删除键值对元素System.out.println("----------根据键删除键值对元素----------");String result3 = m1.remove("灰太狼");System.out.println(result3);System.out.println(m1);//移除所有的键值对元素System.out.println("----------移除所有的键值对元素----------");m1.clear();System.out.println(m1);//重新添加元素m1.put("灰太狼", "红太狼");m1.put("懒羊羊","小灰灰");m1.put("美羊羊","喜羊羊");//判断集合是否包含指定的键System.out.println("----------判断集合是否包含指定的键----------");boolean result4 = m1.containsKey("懒羊羊");boolean result5 = m1.containsKey("烤全羊");System.out.println(result4);System.out.println(result5);//判断集合是否包含指定的值System.out.println("----------判断集合是否包含指定的值----------");boolean result6 = m1.containsValue("小灰灰");boolean result7 = m1.containsValue("烤全羊");System.out.println(result6);System.out.println(result7);//判断集合是否为空System.out.println("----------判断集合是否为空----------");boolean result8 = m1.isEmpty();System.out.println(result8);//集合的长度,即集合中键值对的个数System.out.println("----------集合的长度,即集合中键值对的个数----------");int size = m1.size();System.out.println(size);}
}
运行结果:
三种遍历方式:
1.通过键找值遍历
下面我来使用键找值遍历方式,通过迭代器、增强for、Lambda表达式遍历集合。
代码演示:
public class MapDemo2 {public static void main(String[] args) {//创建集合对象Map<String,String> m = new HashMap<>();//添加对象m.put("喜羊羊","灰太狼");m.put("沸羊羊","美羊羊");m.put("懒羊羊","小灰灰");//通过键找值遍历//将双列集合中的键提另出来放入一个单列集合Set<String> keys = m.keySet();//1.迭代器System.out.println("----------迭代器----------");//创建迭代器对象Iterator<String> it = keys.iterator();//使用迭代器遍历while(it.hasNext()) {String key = it.next();//键找值String value = m.get(key);System.out.println(key + " = " + value);}//2.增强forSystem.out.println("----------增强for----------");for (String key : keys) {String value = m.get(key);System.out.println(key + " = " + value);}//3.Lambda表达式System.out.println("----------Lambda表达式----------");keys.forEach(key -> {String value = m.get(key);System.out.println(key + " = " + value);});}
}
运行结果:
2.通过键值对遍历
下面我来使用键值对遍历方式,通过迭代器、增强for、Lambda表达式遍历集合。
代码演示
public class MapDemo3 {public static void main(String[] args) {//创建集合对象Map<String,String> m = new HashMap<>();//添加元素m.put("喜羊羊","灰太狼");m.put("沸羊羊","美羊羊");m.put("懒羊羊","小灰灰");//通过键值对遍历//将键值对对象放到一个单列集合中Set<Map.Entry<String, String>> entries = m.entrySet();//1.迭代器System.out.println("----------增强for----------");Iterator<Map.Entry<String, String>> it = entries.iterator();while(it.hasNext()) {Map.Entry<String, String> entry = it.next();String key = entry.getKey();String value = entry.getValue();System.out.println(key + " = " + value);}//2.增强forSystem.out.println("----------增强for----------");for (Map.Entry<String, String> entry : entries) {String key = entry.getKey();String value = entry.getValue();System.out.println(key + " = " + value);}//3.Lambda表达式System.out.println("----------Lambda表达式----------");entries.forEach(entry -> {String key = entry.getKey();String value = entry.getValue();System.out.println(key + " = " + value);});}
}
运行结果:
3.Lambda表达式
default void forEach(BiConsumer<? super K, ? super V> action) 结合Lambda遍历Map集合
代码演示:
public class MapDemo4 {public static void main(String[] args) {//创建集合对象Map<String,String> m = new HashMap<>();//添加元素m.put("喜羊羊","灰太狼");m.put("沸羊羊","美羊羊");m.put("懒羊羊","小灰灰");//使用Lambda表达式遍历//1.匿名内部类m.forEach(new BiConsumer<String, String>() {@Overridepublic void accept(String key, String value) {System.out.println(key + " = " + value);}});System.out.println("---------------------");//2.转换成Lambda表达式m.forEach((key, value) -> System.out.println(key + " = " + value));}
}
运行结果:
1.HashMap
特点:
1.HashMap是Map里面的一个实现类
2.没有额外需要学习的特有方法,直接使用Map里面的方法就可以了
3.特点都是由键决定的:无序、不重复、无索引(都是指键)
4.HashMap跟HashSet底层原理是一模一样的,都是哈希表结构(JDK8之前:数组+链 表,JDK8之后:数组+链表+红黑树)计算哈希值时也是通过键计算
5.存入元素时,以来hashCode方法和equals方法保证键的唯一
6.如果键存储的是自定义对象,需要重写hashCode和equals方法,如果值存储自定义对 象,不需要重写hashCode和equals方法
小练习1
需求:创建一个HashMap集合, 键是学生对象(Student),值是籍贯(String) 存储三个键值对元素,并遍历 要求:同姓名、同年龄认为是同一个学生
核心点:HashMap的键位置如果存储的是自定义对象,需要重写hashCode和equals方法。
代码演示:
public class HashMapTest1 {public static void main(String[] args) {/*需求:创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)存储三个键值对元素,并遍历要求:同姓名、同年龄认为是同一个学生*///创建学生对象Student s1 = new Student("han",21);Student s2 = new Student("ma",22);Student s3 = new Student("zhao",21);Student s4 = new Student("zhao",21);//创建HashMap集合HashMap<Student,String> hm = new HashMap<>();//存储键值对元素hm.put(s1,"石家庄");hm.put(s2,"邯郸");hm.put(s3,"保定");hm.put(s4,"天津");//遍历//1.键找值遍历System.out.println("----------键找值遍历----------");Set<Student> keys = hm.keySet();for (Student key : keys) {String value = hm.get(key);System.out.println(key + " = " + value);}//2.键值对遍历System.out.println("----------键值对遍历----------");Set<Map.Entry<Student, String>> entries = hm.entrySet();for (Map.Entry<Student, String> entry : entries) {Student key = entry.getKey();String value = entry.getValue();System.out.println(key + " = " + value);}//3.Lambda表达式遍历System.out.println("----------Lambda表达式遍历----------");hm.forEach((student, s) ->System.out.println(student + " = " + s));}
}
运行结果:
小练习2
需求:某个班级30名学生,现在需要组成秋游活动,班长提供了四个景点依次是A、B、C、D,每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多
代码演示
public class HashMapTest2 {public static void main(String[] args) {/*需求:某个班级30名学生,现在需要组成秋游活动,班长提供了四个景点依次是A、B、C、D,每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多*///创建集合HashMap<String,Integer> hm = new HashMap<>();//生成三十个随机的景点表示学生选择的景点,并存入集合ArrayList<String> list = new ArrayList<>();String[] arr = {"A","B","C","D"};Random r = new Random();for (int i = 0; i < 30; i++) {int index = r.nextInt(4);String name = arr[index];list.add(name);}//要统计的东西较多,使用Map集合进行统计for (int i = 0; i < list.size(); i++) {//判断当前的元素在HashMap中是否存在if(hm.containsKey(list.get(i))) {//如果存在,这个key的value加1int newValue = hm.get(list.get(i)) + 1;hm.put(list.get(i),newValue);} else {//如果不存在,那么添加进去value置为1hm.put(list.get(i),1);}}//遍历System.out.println(hm);//找到选择次数的最大值int max = 0;Set<Map.Entry<String, Integer>> entries = hm.entrySet();for (Map.Entry<String, Integer> entry : entries) {int times = entry.getValue();if(max < times) {max = times;}}//输出次数等于最大次数的景点System.out.print("想去的人最多的景点为:");for (Map.Entry<String, Integer> entry : entries) {int times = entry.getValue();if(max == times) {System.out.print(entry.getKey() + " ");}}}}
运行结果:
2.LinkedHashMap
特点:
由键决定:有序、无重复、无索引
这里的有序指的是保证存储和取出的元素顺序一致
原理:
底层数据结构依然是哈希表,只是每个键值对元素又额外多了一个双链表的机制记录存储的顺序(详细了解可通过前文LinkedHashSet内容)
3.TreeMap
特点:
由键决定:不重复、无索引、可排序
这里的可排序是指对键进行排序(默认按照键的大小从小到大进行排序,也可以自己规定键的排序规则)
原理:
与TreeSet一样,底层数据结构都为红黑树,增删改查性能都较好
代码书写两种排序规则(同TreeSet):
1.实现Comparable接口,指定比较规则。
2.创建集合时传递Comparator比较器对象,指定比较规则。
小练习1:
需求:
键:整数表示id
值:字符串表示商品名称
要求1:按照id的升序排列(默认情况下TreeMap对Integer类型的键值就从小到大排序)
要求2:按照id的降序排列
代码演示:
public class TreeMapDemo1 {public static void main(String[] args) {/*需求:键:整数表示id值:字符串表示商品名称要求1:按照id的升序排列要求2:按照id的降序排列*///要求1:升序//需要排序,创建TreeMap对象TreeMap<Integer,String> tm1 = new TreeMap<>((o1, o2) -> o1 - o2);//添加元素tm1.put(2,"phone");tm1.put(3,"computer");tm1.put(1,"pen");//输出结果System.out.println(tm1);//要求2:降序//创建TreeMap对象TreeMap<Integer,String> tm2 = new TreeMap<>((o1, o2) -> o2 - o1);//添加元素tm2.put(1,"pen");tm2.put(3,"computer");tm2.put(2,"phone");//输出结果System.out.println(tm2);}
}
运行结果:
小练习2:
需求2:
键:学生对象
值:籍贯
要求:按照学生年龄的升序排列,
年龄一样按照姓名的字母排列,
同姓名年龄视为同一个人
代码演示:
Student类:
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;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}public String toString() {return "Student{name = " + name + ", age = " + age + "}";}@Overridepublic int compareTo(Student o) {int i = this.getAge() - o.getAge();i = i == 0 ? this.getName().compareTo(o.getName()) : i;return i;}
}
测试类TreeMapDemo2:
public class TreeMapDemo2 {public static void main(String[] args) {/*需求2:键:学生对象值:籍贯要求:按照学生年龄的升序排列,年龄一样按照姓名的字母排列,同姓名年龄视为同一个人*///创建学生对象Student s1 = new Student("zhao",21);Student s2 = new Student("han",21);Student s3 = new Student("ma",22);Student s4 = new Student("zhao",21);//创建TreeMap对象TreeMap<Student,String> tm = new TreeMap<>();//添加学习对象tm.put(s1,"bd");tm.put(s2,"sjz");tm.put(s3,"hd");tm.put(s4,"tj");//输出结果System.out.println(tm);}
}
运行结果:
小练习3:
需求: 字符串“aababcabcdabcde”
统计字符串中每一个字符出现的次数,
并按照以下格式输出
输出结果:a(5)b(4)c(3)d(2)e(1)
代码演示:
public class TreeMapDemo3 {public static void main(String[] args) {/*需求:字符串“aababcabcdabcde”统计字符串中每一个字符出现的次数,并按照以下格式输出输出结果:a(5)b(4)c(3)d(2)e(1)*/String str = "aababcabcdabcde";//需要同时存储字符和其出现的次数,采用双列集合//需要按照次数从大到小排序,采用TreeMap//创建TreeMapTreeMap<Character,Integer> tm = new TreeMap<>();//添加元素for (int i = 0; i < str.length(); i++) {if(tm.containsKey(str.charAt(i))) {//若已存在 值+1tm.put(str.charAt(i),tm.get(str.charAt(i)) + 1);} else {//若不存在,put 值为1tm.put(str.charAt(i),1);}}//遍历StringBuilder sb = new StringBuilder();tm.forEach((key, value) -> sb.append(key).append("(").append(value).append(")"));System.out.println(sb);}
}
运行结果:
双列集合思考:
可变参数
可变参数本质上是一个数组
作用:
在形参中接收多个数据
格式:
数据类型...参数名称(例如:int...args)
代码演示:
public class Args {public static void main(String[] args) {int sum1 = getSum();int sum2 = getSum(1,2,3,4);int sum3 = getSum(1,2,3,4,5);System.out.println(sum1);System.out.println(sum2);System.out.println(sum3);}public static int getSum(int...args) {int sum = 0;for (int i = 0; i < args.length; i++) {sum = sum + args[i];}return sum;}
}
运行结果:
注意:
1.在方法的形参中最多只能写一个可变参数
2.在方法中,如果除了可变参数以外,还有其他参数,那么可变参数要写最后
综合练习:
练习一:
自动点名器1:
班级里有N个学生,学生属性:姓名,年龄,性别。
实现随机点名器
代码演示:
public class Test1 {public static void main(String[] args) {/*班级里有N个学生,学生属性:姓名,年龄,性别。实现随机点名器*///第一种方法System.out.println("第一种方法:");ArrayList<Student> list = new ArrayList<>();Collections.addAll(list,new Student("han",21,"m"),new Student("ma",22,"m"),new Student("zhao",21,"m"),new Student("wang",22,"m"),new Student("hu",22,"m"),new Student("wu",21,"f"));Random r = new Random();int index = r.nextInt(list.size());System.out.println(list.get(index).getName());//第二种方法System.out.println("第二种方法:");Collections.shuffle(list);System.out.println(list.get(0).getName());}
}
运行结果:
练习二:
自动点名器2:
班级里有N个学生,学生属性:姓名,年龄,性别。
实现随机点名器
要求:百分之70概率随机到男生,百分之30概率随机到女生
代码演示:
public class Test2 {public static void main(String[] args) {/*自动点名器2:班级里有N个学生,学生属性:姓名,年龄,性别。实现随机点名器要求:百分之70概率随机到男生,百分之30概率随机到女生*///存储学生信息ArrayList<Student> list = new ArrayList<>();Collections.addAll(list,new Student("han",21,"m"),new Student("ma",22,"m"),new Student("zhao",21,"m"),new Student("wang",22,"m"),new Student("hu",22,"m"),new Student("wu",21,"f"), new Student("xiaomei",21,"f"),new Student("xiaofang",22,"f"));//创建集合来控制概率ArrayList<Integer> probabilityList = new ArrayList<>();Collections.addAll(probabilityList,1,1,1,1,1,1,1);Collections.addAll(probabilityList,0,0,0);Collections.shuffle(probabilityList);int number = probabilityList.get(0);if(number == 1) {System.out.print("男生:");} else {System.out.print("女生:");}//创建集合分别存储男生和女生ArrayList<Student> boyList = new ArrayList<>();ArrayList<Student> girlList = new ArrayList<>();if(number == 1) {//如果要选男生//将所有男生存入一个集合中for (Student student : list) {if(student.getGender() == "m") {boyList.add(student);}}//并随机抽取Collections.shuffle(boyList);System.out.println(boyList.get(0).getName());} else {//如果要选女生//将所有女生存入一个集合中for (Student student : list) {if(student.getGender() == "f") {girlList.add(student);}}//并随机抽取Collections.shuffle(girlList);System.out.println(girlList.get(0).getName());}}
}
运行结果:
练习三:
自动点名器3:
班级里有N个学生,学生属性:姓名,年龄,性别。
实现随机点名器
要求:被点到的学生不会再被点到
如果班级中所有的学生都点完了,需要重新开启第二轮点名
代码演示:
public class Test3 {public static void main(String[] args) {/*自动点名器3:班级里有N个学生,学生属性:姓名,年龄,性别。实现随机点名器要求:被点到的学生不会再被点到如果班级中所有的学生都点完了,需要重新开启第二轮点名*///存储学生信息ArrayList<Student> list = new ArrayList<>();Collections.addAll(list,new Student("han",21,"m"),new Student("ma",22,"m"),new Student("zhao",21,"m"),new Student("wang",22,"m"),new Student("hu",22,"m"),new Student("wu",21,"f"), new Student("xiaomei",21,"f"),new Student("xiaofang",22,"f"));//创建新集合存储数据ArrayList<Student> copyList = new ArrayList<>();copyList.addAll(list);Scanner sc = new Scanner(System.in);while (true) {//点名控制界面System.out.println("点名请输入:1,退出请输入:2");int num = sc.nextInt();//判断if(num == 1) {if(copyList.size() != 0){//新集合长度不为0时} else {//新数组长度为0copyList.addAll(list);}//打乱集合Collections.shuffle(copyList);//获取随机姓名System.out.println(copyList.get(0).getName());//删除被点到的学生对象copyList.remove(0);}else {//退出break;}}}
}
运行结果:
不可变集合
不可改变的集合
特点:
不可添加、不可删除、不可修改、只能查询
代码演示:
(利用List、Set、Map接口创建不可变集合,并展示查询及遍历方法)
List:(Set与List方法一致)
public class ImmutableDemo1 {public static void main(String[] args) {//创建集合List<String> list = List.of("han", "ma", "zhao", "hu", "wang", "wu");//遍历集合//逐个遍历System.out.println(list.get(0));System.out.println(list.get(1));System.out.println(list.get(2));System.out.println(list.get(3));System.out.println(list.get(4));System.out.println(list.get(5));//普通for遍历System.out.println("----------------------");for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}//迭代器System.out.println("----------------------");Iterator<String> it = list.listIterator();while(it.hasNext()) {String str = it.next();System.out.println(str);}//增强forSystem.out.println("----------------------");for (String str : list) {System.out.println(str);}//lambda表达式System.out.println("----------------------");list.forEach(str -> System.out.println(str));}
}
运行结果:
Map:
public class ImmutableDemo2 {public static void main(String[] args) {//Map系列不可变集合//JDK10以上//先创建一个可变集合HashMap<String,String> hm = new HashMap<>();hm.put("111","aaa");hm.put("222","bbb");hm.put("333","ccc");hm.put("444","ddd");hm.put("555","eee");//转为不可变集合Map<String, String> map1 = Map.copyOf(hm);System.out.println(map1);//JDK10以下Map<Object, Object> map2 = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));System.out.println(map2);}
}
运行结果:
注意:
创建Set和Map的不可变集合时,要注意不能包含重复元素,并且按照各自集合的要求存储元素。
Map不可变集合中最多只能存储20个元素,即10个键值对,超过十个用ofEntries方法