前面的话
博客主页:hyhWTX的博客主页
欢迎关注🖱点赞🎀收藏⭐留言✒
本文由hyhWTX原创,csdn首发!
二十四.Set接口
一.接口
java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的补充,只是比Cooection接口更加严格了,与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
Set集合有多个子类。
HashSet集合介绍:
java.util.HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致—)。Java.util.HashSet底层其实是一个java.util,HashMap支持。
HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此就有良好的存取和查找性能,保证元素唯一性的方式依赖于:hashCode与equals方法。
源码示例:
Person.java
package Day12.Hashcode;
/**
* @author hyhWTX
* @version 1.0
* @ClassName Person
* @date 2022年07月09日 16:12
* @Description: TODO (一句话描述以下该类的功能)
*/
public class Person extends Object{
//重写HashCode方法
// @Override
// public int hashCode() {
// return 1;
// }
}
Demo01HashCode.java
package Day12.Hashcode;
/**
* @author hyhWTX
* @version 1.0
* @ClassName Demo01HashCode
* @date 2022年07月09日 16:06
* @Description: hashCode案例示范
*/
/**
* hashCode()方法的源码:
* public native int hashCode();
* native:代表该方法调用的是本地操作系统的方法。
*/
public class Demo01HashCode {
public static void main(String[] args) {
//Person类继承Object类,所以可以使用Object里面的hashcode方法
Person p1 = new Person();
int h1 = p1.hashCode();
System.out.println(h1);//21685669
Person p2 = new Person();
int h2 = p2.hashCode();
System.out.println(h2);//2133927002
/**
* toString方法的源码
* return getClass().getName() + "@" + Integer.toHexString(hashCode());
*
*/
System.out.println(p1);//Day12.Hashcode.Person@14ae5a5
System.out.println(p2);//Day12.Hashcode.Person@7f31245a
System.out.println(p1==p2);
/**
*String类的哈希值
* String类重写Object类的hashCode方法
*
*/
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println("重地".hashCode());
System.out.println("通话".hashCode() );
}
}
二.结构——hashmap
哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到地址,不是数据实际存储的物理地址),在Object类中有一个方法,可以获取对象的哈希值。int hashCode()返回该对象的哈希码值。
源码实例:
Person.java
package Day12.Hashcode;
/**
* @author hyhWTX
* @version 1.0
* @ClassName Person
* @date 2022年07月09日 16:12
* @Description: TODO (一句话描述以下该类的功能)
*/
public class Person extends Object{
//重写HashCode方法
// @Override
// public int hashCode() {
// return 1;
// }
}
Demo01HashCode.java
package Day12.Hashcode;
/**
* @author hyhWTX
* @version 1.0
* @ClassName Demo01HashCode
* @date 2022年07月09日 16:06
* @Description: hashCode案例示范
*/
/**
* hashCode()方法的源码:
* public native int hashCode();
* native:代表该方法调用的是本地操作系统的方法。
*/
public class Demo01HashCode {
public static void main(String[] args) {
//Person类继承Object类,所以可以使用Object里面的hashcode方法
Person p1 = new Person();
int h1 = p1.hashCode();
System.out.println(h1);//21685669
Person p2 = new Person();
int h2 = p2.hashCode();
System.out.println(h2);//2133927002
/**
* toString方法的源码
* return getClass().getName() + "@" + Integer.toHexString(hashCode());
*
*/
System.out.println(p1);//Day12.Hashcode.Person@14ae5a5
System.out.println(p2);//Day12.Hashcode.Person@7f31245a
System.out.println(p1==p2);
/**
*String类的哈希值
* String类重写Object类的hashCode方法
*
*/
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println("重地".hashCode());
System.out.println("通话".hashCode() );
}
}
什么是哈希表呢?
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储
在一个链表里,但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低,而JDK1.8中,哈希表存储采用数组+链表+红黑数实现,当链表长度超过阙值(8)时,将链表转换为红黑树,这样就大大减少了查找时间。
简单而言,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PAKMLKLx-1657717102995)(2022-07-09%201png.png)]
三.HashSet存储数据的结构
源码示例:
Demo02Set.java
package Day12.Set;
import java.util.HashSet;
/**
* @author hyhWTX
* @version 1.0
* @ClassName Demo02Set
* @date 2022年07月09日 16:47
* @Description: HashSet存储自定义类型元素
*/
/**
* set集合不允许存储重复元素的原理
*/
public class Demo02Set {
public static void main(String[] args) {
//创建HashSet集合对象
HashSet<Object> set = new HashSet<>();
String s1 = new String("abc");
String s2 = new String("abc");
set.add(s1);
set.add(s2);
set.add("重地");
set.add("通话");
set.add("abc");
System.out.println(set);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yVFp7hjx-1657717102997)(2022-07-09%202.png)]
给HashSet中存放自定义类型元素时,需要重写对象中的HashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。
源码示例:
Person.java
package Day12.Set;
import java.util.Objects;
/**
* @author hyhWTX
* @version 1.0
* @ClassName Person
* @date 2022年07月09日 18:59
* @Description: Demo03Set使用的Person类
*/
public class Person {
private String name;
private int age;
public Person() {
}
public Person(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 "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//重写之后两者的equal值相同,存储一次
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Demo03Set.java
package Day12.Set;
/**
* @author hyhWTX
* @version 1.0
* @ClassName Demo03Set
* @date 2022年07月09日 18:51
* @Description: HashCode存储自定义类型元素
*/
import java.util.HashSet;
/**
* set集合报错元素唯一:
* 存储的元素(String,Integer,...Student,Person..)必须重写hashCode方法和equals方法
*
* 要求:
* 同名同年龄的人,视为同一个人,只能存储一次
*/
public class Demo03Set {
public static void main(String[] args) {
//创建HashSet集合存储Person
HashSet<Person> set = new HashSet<>();
Person p1 = new Person("小美女", 12);
Person p2 = new Person("小美女", 12);
Person p3 = new Person("小美女", 12);
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
System.out.println(p1==p2);//两者的hashcode值不同
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println(set);
}
}
四.LinkedHashSet
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,该如何实现呢?在Hashset下面有一个子类java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。
具有可预知迭代顺序的 Set
接口的哈希表和链接列表实现。此实现与 HashSet
的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。注意,插入顺序不 受在 set 中重新插入的 元素的影响。(如果在 s.contains(e)
返回 true
后立即调用 s.add(e)
,则元素 e
会被重新插入到 set s
中。)
**注意,此实现不是同步的。**如果多个线程同时访问链接的哈希 set,而其中至少一个线程修改了该 set,则它必须 保持外部同步。这一般通过对自然封装该 set 的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedSet
方法来“包装”该 set。最好在创建时完成这一操作,以防止意外的非同步访问:
Set s = Collections.synchronizedSet(new LinkedHashSet(...));
此类的 iterator
方法返回的迭代器是快速失败 的:在迭代器创建之后,如果对 set 进行修改,除非通过迭代器自身的 remove
方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException
。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何强有力的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException
。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
此类是 Java Collections Framework 的成员。
源码示例:
package Day12.Set;
/**
* @author hyhWTX
* @version 1.0
* @ClassName Demo04LinkedHashSet
* @date 2022年07月09日 19:37
* @Description: LinkedHashSet示例示范
*/
import java.util.HashSet;
import java.util.LinkedHashSet;
/**
* LinkedHashSet集合特点:
* 底层是一个哈希表(数组+链表/红黑树)+链表,多了一条链表(记录元素的存储顺序),保证元素有序
*/
public class Demo04LinkedHashSet {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("www");
set.add("abc");
set.add("abc");
;
set.add("itcast");
System.out.println(set);//无序的,不允许重复
LinkedHashSet<String> linked = new LinkedHashSet<>();
linked.add("www");
linked.add("abc");
linked.add("abc");
linked.add("itcast");
System.out.println(linked);//有序,且不允许重复
}
}
五.可变参数
在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成如下格式:
修饰符 返回值类型 方法名(参数类型...形参名){}
书写等价于:
修饰符 返回值类型 方法名(参数类型...形参名){}
只是后面的这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。
JDK1.5以后,出现了简化操作。…用于参数上,称之为可变参数。
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译撑的class文件,将这些元素先封装到一个数组中,在进行传递,这些动作都在编译.class文件时,自动完成了。
注意事项:
- 一个方法的参数列表,只能有一个可变参数
- 如果方法的参数有多个,那么可变参数必须写在参数列表的末尾。
可变参数特殊写法
public static void method(Object ...object)//可接受任意类型的参数
源码示例:
package Day12.VarArgs;
/**
* @author hyhWTX
* @version 1.0
* @ClassName Demo01Args
* @date 2022年07月10日 13:18
* @Description: 可变参数示例
*/
public class Demo01Args {
public static void main(String[] args) {
int i = add(10,20,30,40,50,60,70,80,90,100);
System.out.println(i);
}
/**
*定义计算(0~n)整数和的方法
* 已知:计算整数的和,数据类型已经确定为int ,参数数目不确定,使用可变参数
* 调用add方法,就会创建一个长度为0的数组 new int[0]
* 在add中添加几个数字,,里面就会创建长度为几的数组
*
*/
public static int add(int...arr){
System.out.println(arr);//底层是一个数组
//定义一个初始化的变量,记录累加求和
int sum = 0;
//遍历数组,获取数组中的每一个元素
for (int i : arr) {
//累加求和
sum +=i;
}
//将求和结果返回
return sum;
}
// //定义一个方法,计算三个int类型整数的和
// public static int add(int a,int b,int c){
// return a+b+c;
// }
// //定义一个方法,计算两个int类型的整数的和
// public static int add(int a,int b){
// return a+b;
// }
}