Java-Collection集合(三)
一、Set集合
Set接口:java.util.Set
Set接口继承了Collection接口,因此所有的方法也都可以使用,它并没有对Collection接口的功能进行扩充,只是存储规则更加严格。
Set接口特点:
1.无序,存储元素与取出元素的顺序可能不一致
2.不容许存储重复元素
3.没有带索引的方法,不能使用普通for循环进行遍历
注意:Set接口也不能直接创建对象使用,需要创建实现类对象进行使用,Set接口最常用的两个实现类为HashSet和LinkedHashSet。
public class SetDemo01 {
public static void main(String[] args) {
//使用多态,创建一个Set接口对象
Set<Integer> set = new HashSet<>();
//存储一些元素,添加一些重复元素
set.add(3);
set.add(2);
set.add(2);
set.add(1);
//看出Set集合存储元素与输入顺序不一致,而且不容许存储重复元素
System.out.println(set);//输出结果:[1, 2, 3]
//遍历方法
//1.创建迭代器
Iterator<Integer> it = set.iterator();
while(it.hasNext()){
Integer i = it.next();
System.out.print(i+" ");//输入结果:1 2 3
}
//2.增强for
for(Integer i:set){
System.out.println(i+" ");//输入结果:1 2 3
}
//删除元素方法
boolean b = set.remove(2);
System.out.println(b);//输出结果:true
//返回集合元素个数
System.out.println(set+" ");//输出结果:[1, 3]
int size = set.size();
System.out.println(size);//输出结果:2
//判断集合是否为空
boolean c = set.isEmpty();
System.out.println(c);//输出结果:false
//判断集合是否包含指定元素
boolean d = set.contains(1);
System.out.println(d);//输出结果:true
//清空集合中的元素
set.clear();
System.out.println(set);//输出结果:[]
}
}
二、HashSet集合
HashSet集合:java.util.HashSet
HashSet集合是Set接口的实现类,它包含了所有Set接口的特性,根据对象的哈希值来确定元素的具体位置,底层是一个Hash表结构,因此查找速度很快。
HashSet存储结构:
哈希值:一个十进制整数,系统随机给出,本质上是对象的逻辑地址值,是模拟出来的,不是实际存储的物理地址。 在Object类中一个方法,可以用来获取对象的哈希值:
public int hashCode(); (可以进行重写)
哈希表结构:
在JDK1.8之前底层采用数组+链表的结构,JDK1.8及以后底层采用数组+链表+红黑树结构,当链表结构超过8,自动转成红黑树 可以减少查找时间。数组的作用是把元素进行分组,将相同哈希值的元素存在一起,链表/红黑树将相同哈希值的元素连接到一起。
public class SetDemo02 {
public static void main(String[] args) {
//Obeject类是所有类的根类,其他类可以使用HashCode方法
//使用系统定义好的类,已经重写了toString方法
Integer i1 = new Integer(1);
Integer i2 = new Integer(2);
int h1 = i1.hashCode();
int h2 = i2.hashCode();
System.out.println(h1);//1
System.out.println(h2);//2
//注意:String类中有两个字符串"重地"、"通话"的哈希值正好相同
String s1 = "abcd";
String s2 = "abcd";
System.out.println(s1.hashCode());//输出结果:2987074
System.out.println(s2.hashCode());//输出结果:2987074
String s3 = "重地";//输出结果:1179395
String s4 = "通话";//输出结果:1179395
System.out.println(s3.hashCode());
System.out.println(s4.hashCode());
//使用自定义的类
Demo d1 = new Demo();
Demo d2 = new Demo();
int h3 = d1.hashCode();
int h4 = d2.hashCode();
System.out.println(h3);//重写hashCode()之前输出结果:366712642,重写hashCode()之后输出结果:666
System.out.println(d1);//重写hashCode()之前输出结果:set集合.Demo@15db9742,重写hashCode()之后输出结果:set集合.Demo@29a
System.out.println(h4);//重写hashCode()之前输出结果:1829164700,重写hashCode()之后输出结果:666
System.out.println(d2);//重写hashCode()之前输出结果:set集合.Demo@6d06d69c,重写hashCode()之后输出结果:set集合.Demo@29a
//重写HashCode()以后,改变的是逻辑地址,不改变物理地址,逻辑地址可能相同,但物理地址不同
System.out.println(d1.equals(d2));//输出结果:false 实际物理地址不同
}
}
class Demo{
//重写hashCode()
@Override
public int hashCode() {
return 666;
}
}
三、Set集合存储原理
Set集合不容许存储重复元素原理:
调用add方法时,add方法调用HashCode方法和equals方法,判断元素是否重复。(hashCode比较逻辑地址,equals比较的是物理地址)在进行新元素传入时,首先调用HashCode方法进行新元素的哈希值计算,在集合中查找是否有 相同哈希值的元素,如果没有就会把元素存进集合中,如果集合中有相同哈希值元素(也称哈希冲突) ,会调用equals方法,对两个相同哈希值的元素进行判断,如果equals方法返回true,则将 新元素存进集合中,如果equals方法返回false,则不将新元素存进集合
HashSet集合存储自定义类型元素
因为之前系统已经定义的类型,已经重写了hashCode和equals方法,想要元素保证唯一, 自定义类型元素,必须重写hashCode和equals方法,建立自己的比较方式.
public class SetDemo03 {
public static void main(String[] args) {
HashSet<String> hset = new HashSet<>();
hset.add("abcd");
hset.add("abcd");
hset.add("重地");
hset.add("通话");
System.out.println(hset);//输出结果:[重地, 通话, abcd] 不存储重复元素
HashSet<Demo01> hset1 = new HashSet<>();
Demo01 demo01 = new Demo01(20,"张三");
Demo01 demo02 = new Demo01(20,"张三");
Demo01 demo03 = new Demo01(21,"张三");
hset1.add(demo01);
hset1.add(demo02);
hset1.add(demo03);
System.out.println(hset1);
/*
重写方法前输出结果:[Demo01 [age=21, name=张三], Demo01 [age=20, name=张三], Demo01 [age=20, name=张三]]
发现 两个包含相同的元素的对象也存储进去了,原因是自定义的类没有重写HashCode和equals方法,两个包含相同的元素
的对象的哈希值 不同,所以可以被存储,需要重写HashCode和equals方法
重写方法后输出结果:[Demo01 [age=20, name=张三], Demo01 [age=21, name=张三]],存储的都是不重复元素的对象
*/
System.out.println(demo01.hashCode());//重写方法前输出结果:366712642,重写方法后输出结果:776470
System.out.println(demo02.hashCode());//重写方法前输出结果:1829164700,重写方法后输出结果:776470
System.out.println(demo01.equals(demo02));//重写方法前输出结果:false,重写方法后输出结果:true
}
}
class Demo01{
public int age;
public String name;
public Demo01(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {//重写toString方法
return "Demo01 [age=" + age + ", name=" + name + "]";
}
//重写hashCode和equals方法,可以使用系统自动生成的即可
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Demo01 other = (Demo01) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
四、LinkedHashSet集合
LinkedHashSet集合:
java.util.LinkedHashSet implements java.util.HashSet
底层是哈希表+链表,新链表的作用记录元素的存储顺序,使得元素变的有序。
public class SetDemo04 {
public static void main(String[] args) {
HashSet<String> hset = new HashSet<>();
hset.add("abcd");
hset.add("abcd");
hset.add("重地");
hset.add("通话");
System.out.println(hset);//输出结果:[重地, 通话, abcd] 无序,不存储重复元素
LinkedHashSet<String> hset1 = new LinkedHashSet<>();
hset1.add("abcd");
hset1.add("abcd");
hset1.add("重地");
hset1.add("通话");
System.out.println(hset1);//输出结果:[abcd, 重地, 通话] 有序,不存储重复元素
}
}
五、可变参数
可变参数:当方法参数列表数据类型已经确定,但是不确定参数的个数,可以使用可变参数。 底层是一个数组,根据传递参数的个数不同,会创建不同长度得数组,参数的个数可以从0个到多个。
使用格式(定义方法时使用):
修饰符 返回值类 参数列表(数据类型…变量名){
方法体
}
注意事项:
1.一个方法的参数列表,只能有一个可变参数。
2.如果一个方法参数有多个,可变参数必须放在参数列表末尾。
参数特殊格式:
修饰符 返回值类 参数列表(Obiect…变量名){//可以传递任意数据类型数据
方法体
}
public class SetDemo06 {
public static void main(String[] args) {
/*
调用addMethod方法时,根据参数的个数进行不同长度的数组的创建
例如:
addMethod():创建一个长度为0的数组,new int[0]
addMethod(1):创建一个长度为1的数组,new int[]{1}
addMethod(1,2):创建一个长度为1的数组,new int[]{1,2}
......以此类推
*/
int i =addMethod();
int i1 = addMethod(1);
int i2 = addMethod(1,2);
int i3 = addMethod(1,1,1,1,1,1,1);
System.out.println(i);//输出结果:0
System.out.println(i1);//输出结果:1
System.out.println(i2);//输出结果:2
System.out.println(i3);//输出结果:7
method(1,1.11, "aa","bb","cc");
method4(1,1.11, "aa","bb","cc");//可以传递任意数据类型数据
}
//定义一个可以进行任意个数累加和的方法
public static int addMethod(int...array){
// System.out.println(array);//输出结果:[I@15db9742 打印的是数组地址值,[表示数组,I表示int类型
// System.out.println(array.length);//输出结果:0
//进行求和
int sum = 0;
for(int i:array){
sum=sum+1;
}
return sum;
}
//1.一个方法的参数列表,只能有一个可变参数
public static void method(int i,double d,String...s){} //正确写法
//public static void method1(int i,double...d,String...s){}//错误写法
// 2.如果一个方法参数有多个,可变参数必须放在参数列表末尾
public static void method2(int i,double d,String...s){} //正确写法
//public static void method3(String...s,int i,double d){}//错误写法
//可变参数特殊格式:
public static void method4(Object...obj){}
}