一.集合体系结构:
二.Set系列集合:(注:Set是接口,不能实例化,只能创建实现类的对象)
1.特点:
-
无序:存和取的顺序有可能不一致:
例如:
package com.itheima.a06mySet; import java.util.HashSet; import java.util.Set; public class A01_SetDemo1 { public static void main(String[] args) { //1.创建一个Set集合的对象 Set<String> s=new HashSet<>(); //2.添加元素 /* 如果当前元素是第一次添加,那么可以添加成功,返回true 如果当前元素是第二次添加,那么添加失败,返回false 添加失败的元素在遍历集合时不会出现 */ s.add("张三"); s.add("张三"); s.add("李四"); s.add("王五"); //3.打印集合 //Set集合有无序性,先存的张三,但打印时先出的李四 System.out.println(s);//[李四, 张三, 王五] } }
-
不重复:集合中的元素不能重复-->可用于数据的去重:
例如:
package com.itheima.a06mySet; import java.util.HashSet; import java.util.Set; public class A01_SetDemo1 { public static void main(String[] args) { //1.创建一个Set集合的对象 Set<String> s=new HashSet<>(); //2.添加元素 /* 如果当前元素是第一次添加,那么可以添加成功,返回true 如果当前元素是第二次添加,那么添加失败,返回false 添加失败的元素在遍历集合时不会出现 */ boolean b1=s.add("张三"); boolean b2=s.add("张三"); boolean b3=s.add("王五"); //3.打印集合 System.out.println(b1);//true System.out.println(b2);//false System.out.println(b3);//true System.out.println(s);//[张三, 王五] } }
-
无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素;
2.Set集合的实现类:
-
HashSet:无序,不重复,无索引
-
LinkedHashSet:有序,不重复,无索引
-
TreeSet:可排序,不重复,无索引
3.Set接口中的方法基本上与Collection的API一致:
1)Collection中的方法:
2)遍历字符串练习:
存储字符串并遍历:利用Set系列的集合,添加字符串,并使用多种方式遍历。
-
迭代器
例如:
package com.itheima.a06mySet; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class A01_SetDemo1 { public static void main(String[] args) { //1.创建一个Set集合的对象 Set<String> s=new HashSet<>(); //2.添加元素 s.add("张三"); s.add("张三"); s.add("李四"); s.add("王五"); //3.打印集合 Iterator<String> it=s.iterator(); while (it.hasNext()){ String str= it.next(); System.out.print(str+","); } //运行结果为李四,张三,王五, } }
-
增强for
例如:
package com.itheima.a06mySet; import java.util.HashSet; import java.util.Set; public class A01_SetDemo1 { public static void main(String[] args) { //1.创建一个Set集合的对象 Set<String> s=new HashSet<>(); //2.添加元素 s.add("张三"); s.add("张三"); s.add("李四"); s.add("王五"); //3.打印集合 for (String str : s) { System.out.print(str+","); } //运行结果为李四,张三,王五, } }
-
Lambda表达式
例如:未改写为Lambda表达式前:
package com.itheima.a06mySet; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; public class A01_SetDemo1 { public static void main(String[] args) { //1.创建一个Set集合的对象 Set<String> s=new HashSet<>(); //2.添加元素 s.add("张三"); s.add("张三"); s.add("李四"); s.add("王五"); //3.打印集合 s.forEach(new Consumer<String>() { @Override public void accept(String str) { System.out.print(str+","); } }); //运行结果为李四,张三,王五, } }
改写为Lambda表达式后:
package com.itheima.a06mySet; import java.util.HashSet; import java.util.Set; public class A01_SetDemo1 { public static void main(String[] args) { //1.创建一个Set集合的对象 Set<String> s=new HashSet<>(); //2.添加元素 s.add("张三"); s.add("张三"); s.add("李四"); s.add("王五"); //3.打印集合 s.forEach((String str) -> { System.out.print(str+","); } ); //运行结果为李四,张三,王五, } }
-
简化Lambda表达式:
package com.itheima.a06mySet;
import java.util.HashSet;
import java.util.Set;
public class A01_SetDemo1 {
public static void main(String[] args) {
//1.创建一个Set集合的对象
Set<String> s=new HashSet<>();
//2.添加元素
s.add("张三");
s.add("张三");
s.add("李四");
s.add("王五");
//3.打印集合
s.forEach( str ->
System.out.print(str+",")
);
//运行结果为李四,张三,王五,
}
}
三.HashSet底层原理:
1.HashSet集合底层采取哈希表存储数据。
2.哈希表是一种对于增删改查数据性能都较好的数据结构。
a.哈希表组成:
-
JDK8之前:数组 + 链表
-
JDK8开始: 数组 + 链表 + 红黑树
b.哈希值:
例1:如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
对象Student:
package com.itheima.a06mySet;
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 + "}";
}
}
测试类:
package com.itheima.a06mySet;
public class A02_HashSetDemo1 {
public static void main(String[] args) {
//1.创建对象
Student s1=new Student("zhangsan",23);
Student s2=new Student("zhangsan",23);
//2.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
System.out.println(s1.hashCode());//运行结果为793589513
System.out.println(s2.hashCode());//运行结果为1313922862
}
}
例2:如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
对象Student:
package com.itheima.a06mySet;
import java.util.Objects;
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;
}
@Override
public 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);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试类:
package com.itheima.a06mySet;
public class A02_HashSetDemo1 {
public static void main(String[] args) {
//1.创建对象
Student s1=new Student("zhangsan",23);
Student s2=new Student("zhangsan",23);
//2.如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
System.out.println(s1.hashCode());//运行结果为-1461067292
System.out.println(s2.hashCode());//运行结果为-1461067292
}
}
例3:在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
package com.itheima.a06mySet;
public class A02_HashSetDemo1 {
public static void main(String[] args) {
//在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样
//哈希碰撞
System.out.println("abc".hashCode());//运行结果为96354
System.out.println("acD".hashCode());//运行结果为96354
}
}
3.HashSet在JDK8的底层原理:
上述两个方法不重写的话在底层就利用的是地址值,意义不大。
四.HashSet的三个问题:
问题1:HashSet为什么存和取的顺序可能不一样?和遍历有关,具体看b站视频(黑马程序员Java上,阿伟的)。
问题2:HashSet为什么没有索引?因为HashSet不纯粹,有链表,红黑树和数组,带索引没 意义。
问题3:HashSet是利用什么机制保证数据去重的?利用HashCode方法(可得到哈希值,哈希值可确定当前元素添加在数组中的位置)和equals方法(比较对象内部的属性值是否相同)
五.HashSet集合练习:利用HashSet集合去除重复元素(不去重一般用ArrayList集合)
1.注:一定要重写HashCode和equals方法,不重写的话会操作地址值,地址值大多都不一样,很难做到去重
如:Student类:
package com.itheima.a06mySet;
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 + "}";
}
}
测试类:
package com.itheima.a06mySet;
import java.util.HashSet;
public class A03_HashSetDemo2 {
public static void main(String[] args) {
/* 需求:创建一个存储学生对象的集合,存储多个学生对象。
使用程序实现在控制台遍历该集合。
要求:学生对象的成员变量值相同,我们就认为是同一个对象。
*/
//1.创建学生对象
Student s1=new Student("zhangsan",23);
Student s2=new Student("lisi",24);
Student s3=new Student("wangwu",25);
Student s4=new Student("zhangsan",23);
//2.创建集合用来添加学生
HashSet<Student> hs=new HashSet<>();
//3.添加元素
System.out.println(hs.add(s1));//运行结果为true
System.out.println(hs.add(s2));//运行结果为true
System.out.println(hs.add(s3));//运行结果为true
System.out.println(hs.add(s4));//运行结果为true
//4.打印集合
System.out.println(hs);
/*运行结果为[Student{name = wangwu, age = 25}, Student{name = zhangsan, age = 23},
Student{name = lisi, age = 24}, Student{name = zhangsan, age = 23}]
*/
}
}
本例中没有重写HashCode方法和equals方法,此时操作地址值,导致难以做到去重。因此,对象s1和对象s4即使属性值一样,但地址值不同,因此可以添加,但没做到去重。
2.重写HashCode方法和equals方法后即可做到去重:
如:Student类:
package com.itheima.a06mySet;
import java.util.Objects;
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;
}
@Override
public 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);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试类:
package com.itheima.a06mySet;
import java.util.HashSet;
public class A03_HashSetDemo2 {
public static void main(String[] args) {
/* 需求:创建一个存储学生对象的集合,存储多个学生对象。
使用程序实现在控制台遍历该集合。
要求:学生对象的成员变量值相同,我们就认为是同一个对象。
*/
//1.创建学生对象
Student s1=new Student("zhangsan",23);
Student s2=new Student("lisi",24);
Student s3=new Student("wangwu",25);
Student s4=new Student("zhangsan",23);
//2.创建集合用来添加学生
HashSet<Student> hs=new HashSet<>();
//3.添加元素
System.out.println(hs.add(s1));//运行结果为true
System.out.println(hs.add(s2));//运行结果为true
System.out.println(hs.add(s3));//运行结果为true
System.out.println(hs.add(s4));//运行结果为false
//4.打印集合
System.out.println(hs);
/*运行结果为[Student{name = wangwu, age = 25}, Student{name = lisi, age = 24},
Student{name = zhangsan, age = 23}]
*/
}
}
3.注:String类型和Integer类型等的数据,此时不需要手动重写HashCode方法和equals方法就能做到去重,因为Java已经写好了。
六.LinkedHashSet底层原理:具体看b站视频(黑马程序员Java上,阿伟的)
代码举例:
Student类:
package com.itheima.a06mySet;
import java.util.Objects;
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;
}
@Override
public 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);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试类:
package com.itheima.a06mySet;
import java.util.LinkedHashSet;
public class A04_LinkedHashSetDemo {
public static void main(String[] args) {
//1.创建4个学生对象
Student s1=new Student("zhangsan",23);
Student s2=new Student("lisi",24);
Student s3=new Student("wangwu",25);
Student s4=new Student("zhangsan",23);
//2.创建集合的对象
LinkedHashSet<Student> lhs=new LinkedHashSet<>();
//3.添加元素
System.out.println(lhs.add(s1));//运行结果为true
System.out.println(lhs.add(s2));//运行结果为true
System.out.println(lhs.add(s3));//运行结果为true
System.out.println(lhs.add(s4));//运行结果为false
//4.打印集合
System.out.println(lhs);
/*运行结果为[Student{name = zhangsan, age = 23}, Student{name = lisi, age = 24},
Student{name = wangwu, age = 25}]
*/
//注:此时存和取的顺序是一致的,先存入的s1,取的时候也就先取s1,以此类推
}
}
七.总结:
LinkedHashSet集合比HashSet集合效率低,因为LinkedHashSet集合在哈希表的基础上又多做了一些事(操作数据顺序)。