java阶段七容器集合之Set
java阶段七容器集合之Set1.1Set集合的特点①不包重复元素的集合②没有带索引的方法,所以不能使用普通for循环遍历③对元素的顺序不作任何保证(新添加一个元素都是随机插入在原来的set集合中)//创建Set集合对象Set<String> set = new HashSet<String>();//添加元素set.add("hello");set.add("world"
1.1 Set集合的特点
①不包重复元素的集合
②没有带索引的方法,所以不能使用普通for循环遍历
③对元素的顺序不作任何保证(新添加一个元素都是随机插入在原来的set集合中)
//创建Set集合对象
Set<String> set = new HashSet<String>();
//添加元素
set.add("hello");
set.add("world");
set.add("java");
//不包含重复元素 所以这里添加不进去
set.add("java");
//遍历
for ( String s : set){
System.out.println(s);
}
输出结果:
world
java
hello
1.2 哈希值
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。
获取哈希值:Object中的一个方法可以返回对象的哈希值
public int hashCode():返回对象的哈希值。
对象哈希值的特点:
①同一个对象多次调用hashCode()方法返回的哈希值是相同的
②默认情况下,不同对象调用hashCode()方法返回的哈希值是不一样的,但是可以通过重写hashCode()方法实现返回相同的哈希值
1.3 HashSet
HashSet集合的特点:
①该类实现Set接口,由哈希表(实际为HashMap实例)支持。
② 对集合的迭代顺序不作任何保证;
③没有带索引的方法,所以不能使用普通for循环遍历
④由于是Set集合,所以不包含重复元素
//创建HashSet集合
Set<String> stringSet = new HashSet<String>();
//存储字符串
stringSet.add("Rose");
stringSet.add("Jake");
//只能使用增强型for循环 结果不保证顺序 ,不允许重复值
for (String s : stringSet){
System.out.println(s);
}
输出结果:
Jake
Rose
1.4 HashSet集合保证元素唯一性源码分析
//存储字符串
stringSet.add("Rose");
stringSet.add("Jake");
//保证元素唯一性 不添加
stringSet.add("Jake");
------------------------------------------
public class HashSet<E>{
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//hash值是根据元素的hashCoude()方法得到的
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果hash表未初始化就对hash表进行初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//根据对象的hash值判断元素的存储位置,如果该位置没有元素则存储元素
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
/*
若该位置有元素则将
存入的元素和原来的元素比较哈希值
如果hash值不同则逻辑短路,向下执行,把元素添加到集合
如果哈希值相同,equals方法再比较
如果返回false,会继续向下执行,把元素添加到集合
如果返回true,说明元素重复,不存储
*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
}
练习:HashSet集合存储学生对象并遍历
需求:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象 (需要重写equals() 和 hashCode())
Student 学生类
package com.itheima08;
public class 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 "name=" + name + " "+
"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;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
HashCode测试类
public class HashSetTest {
public static void main(String[] args) {
//创建HashSet集合
Set<Student> studentSet = new HashSet<>();
//创建学生对象
Student student1 = new Student("Rose",20);
Student student2 = new Student("Jake",22);
Student student3 = new Student("Lisa",21);
Student student4 = new Student("Rose",20);
//把学生添加到集合
studentSet.add(student1);
studentSet.add(student2);
studentSet.add(student3);
studentSet.add(student4);
//遍历集合(增强for)
for (Student student : studentSet){
System.out.println(student);
}
}
}
输出结果:
name=Rose age=20
name=Jake age=22
name=Lisa age=21
1.5 LinkedHashSet集合概述和特点
**LinkedHashSet的概述:**哈希表和链表实现的Set接口,具有可预测的迭代次序。该实现与HashSet不同之处在于它保持双向连接表的所有条目。该链表定义了迭代排序,他是将元素插入集合的顺序。
LinkedHashSet集合的特点:
①哈希表和链表实现的Set接口,具有可预测的迭代次序
②由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
③由哈希表保证元素唯一,也就是说没有重复的元素
LinkedHashSet集合练习 存储字符串并遍历
public class LinkedHashSetTest {
public static void main(String[] args) {
//创建LinkedHasSet集合
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<String>();
//存储字符串
linkedHashSet.add("Rose");
linkedHashSet.add("Lisa");
linkedHashSet.add("Lee");
linkedHashSet.add("Pamela");
linkedHashSet.add("lara");
//不会储存此条重复数据
linkedHashSet.add("Rose");
//for 遍历
for (String s : linkedHashSet){
System.out.println(s);
}
}
}
1.6 TreeSet集合概述和特点
TreeSet集合的特点:
①元素有序,这里了的有序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
TreeSet() :根据其元素的自然排序进行排序
TreeSet(Comparator comparator):根据指定的比较器进行排序
②没有索引的方法,所以不能使用普通for循环
③由于是Set集合,所以不包含重复的元素
TreeSet集合练习 存储整数并遍历
public class TreeSetTest {
public static void main(String[] args) {
//创建TreeSet集合对象 基本数据引用类型要使用相对应的包装类
Set<Integer> treeSet = new TreeSet<>();//使用的无参构造方法会自然排序
//存储整数
treeSet.add(10);
treeSet.add(20);
treeSet.add(22);
treeSet.add(5);
//重复元素 不会添加
treeSet.add(10);
//遍历
for (Integer o : treeSet){
System.out.println(o);
}
}
}
1.7 自然排序Comparable的使用
存储学生对象并遍历,创建集合使用无参构造方法
要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
注意:考察定制排序
学生类:
blic 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 "name=" + name + " "+
"age=" + age;
}
/**
* @param o 相当于student1对象的年龄
* @return 正数:升序 负数:降序
*/
@Override
public int compareTo(Student o) {
//return 0; //重复元素不添加
//return 1; //升序
//return -1; //降序
//按照年龄从小到大 否则按姓名字母 此this是student2的年龄
int num = this.age - o.age;
//年龄降序
//int num = o.age - this.age;
//int compareTo(To)将此对象与指定的对象进行比较以进行排序。 返回一个负整数,零或正整数,因为该对象小于,等于或大于指定对象。
int num2 = num == 0 ? this.name.compareTo(o.name) : num;
return num2;
}
}
测试类:
public class ComparableTest {
public static void main(String[] args) {
//创建TreeSet集合对象
Set<Student> studentSet = new TreeSet<>();
//创建学生对象
Student student1 = new Student("Rose",15);
Student student2 = new Student("Jake",18);
Student student3 = new Student("Pamela",22);
Student student4 = new Student("Lara",15);
Student student5 = new Student("Lara",15);
//存储学生对象
studentSet.add(student1);
studentSet.add(student2);
studentSet.add(student3);
studentSet.add(student4);
studentSet.add(student5);
//遍历集合
for (Student student : studentSet){
System.out.println(student);
}
}
}
输出结果:
name=Lara age=15
name=Rose age=15
name=Jake age=18
name=Pamela age=22
结论:
①用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的。
②自然排序,要让所属的类实现Comparable接口,并且重写comparable(To)方法
③重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写。不能只按主要条件来排序。
1.8 比较器排序Comparator的使用
存储学生对象并遍历,创建TreeSet集合使用带参构造方法;
要求:按照年龄从小到大,年龄相同时。按照姓名的字母顺序排序;
学生类:
public class 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 "name=" + name + " "+
"age=" + age;
}
}
测试类:
public class ComparatorTest {
public static void main(String[] args) {
//创建TreeSet集合对象 使用带参构造(接口的实现类对象)
Set<Student> studentSet = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//年龄升序
int num = o1.getAge() - o2.getAge();
//当年龄相相同时 比较姓名 按照姓名的字母排序
//int compareTo(T o)将此对象与指定的对象进行比较以进行排序。 返回一个负整数,零或正整数,因为该对象小于,等于或大于指定对象。
/*if (num == 0 ){
num = o1.getName().compareTo(o2.getName());
}*/
//三元表达式法
int num2 = num == 0 ? o1.getName().compareTo(o2.getName()) : num;
return num2;
}
});
//创建学生对象
Student student1 = new Student("Rose",15);
Student student2 = new Student("Jake",18);
Student student3 = new Student("Pamela",22);
Student student4 = new Student("Lara",15);
Student student5 = new Student("Lara",15);
//将学生对象添加到集合
studentSet.add(student1);
studentSet.add(student2);
studentSet.add(student3);
studentSet.add(student4);
studentSet.add(student5);
//遍历集合
for (Student student : studentSet){
System.out.println(student);
}
}
}
输出结果:
name=Lara age=15
name=Rose age=15
name=Jake age=18
name=Pamela age=22
结论:
①TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
②比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写Compare(T o1,T o2)方法
③重写方法时,一定要注意排序规则必须是按照要求的主要条件和次要条件来写
练习:使用比较器排序实现
用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩),并遍历该集合
要求:按照总分从高到低出现(降序);
学生类:
public class Student {
/**
* 学生姓名
*/
private String name;
/**
* 学生语文成绩
*/
private int languageScore;
/**
* 学生数学成绩
*/
private int mathScore;
public Student() {
}
public Student(String name, int languageScore, int mathScore) {
this.name = name;
this.languageScore = languageScore;
this.mathScore = mathScore;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLanguageScore() {
return languageScore;
}
public void setLanguageScore(int languageScore) {
this.languageScore = languageScore;
}
public int getMathScore() {
return mathScore;
}
public void setMathScore(int mathScore) {
this.mathScore = mathScore;
}
public int getSum() {
return mathScore + languageScore;
}
@Override
public String toString() {
return "name=" + name + " " +
"languageScore=" + languageScore + " " +
"mathScore=" + mathScore + " " +
"sum=" + getSum();
}
}
测试类:
public class ComparatorTest {
public static void main(String[] args) {
//创建TreeSet集合对象并且 使用TreeSet的带参构造方法(comparator)比较器排序
Set<Student> studentSet = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//主要条件
int num = o2.getSum() - o1.getSum();
//次要条件
int num2 = num == 0 ? o2.getName().compareTo(o1.getName()) : num;
return num2;
}
});
//创建学生对象
Student student1 = new Student("Rose",80,75);
Student student2 = new Student("Jake",88,90);
Student student3 = new Student("Pamela",87,60);
Student student4 = new Student("Lee",99,99);
Student student5 = new Student("Stark",85,70);
//添加集合对象
studentSet.add(student1);
studentSet.add(student2);
studentSet.add(student3);
studentSet.add(student4);
studentSet.add(student5);
//遍历集合
for (Student student : studentSet){
System.out.println(student);
}
}
}
输出结果:
name=Lee languageScore=99 mathScore=99 sum=198
name=Jake languageScore=88 mathScore=90 sum=178
name=Stark languageScore=85 mathScore=70 sum=155
name=Rose languageScore=80 mathScore=75 sum=155
name=Pamela languageScore=87 mathScore=60 sum=147
练习:使用自然排序实现
用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩),并遍历该集合
要求:按照总分从低到高出现(升序);
学生类:
//使用comparable(自然排序)需要类实现comparable接口 再重写compareTo(T o)方法
public class Student implements Comparable<Student>{
/**
* 学生姓名
*/
private String name;
/**
* 学生语文成绩
*/
private int languageScore;
/**
* 学生数学成绩
*/
private int mathScore;
public Student() {
}
public Student(String name, int languageScore, int mathScore) {
this.name = name;
this.languageScore = languageScore;
this.mathScore = mathScore;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLanguageScore() {
return languageScore;
}
public void setLanguageScore(int languageScore) {
this.languageScore = languageScore;
}
public int getMathScore() {
return mathScore;
}
public void setMathScore(int mathScore) {
this.mathScore = mathScore;
}
@Override
public String toString() {
return "name=" + name + " " +
"languageScore=" + languageScore + " " +
"mathScore=" + mathScore + " " +
"sum=" + getSum();
}
@Override
public int compareTo(Student o) {
return this.getSum() - o.getSum();
}
public int getSum() {
return mathScore + languageScore;
}
}
测试类:
public class ComparableTest {
public static void main(String[] args) {
//创建TreeSet集合对象 使用无参构造 自然排序(comparable)
Set<Student> studentSet = new TreeSet<>();
//创建学生对象
Student student1 = new Student("Rose",80,75);
Student student2 = new Student("Jake",88,90);
Student student3 = new Student("Pamela",87,60);
Student student4 = new Student("Lee",99,99);
Student student5 = new Student("Stark",85,70);
//将对象添加到集合
studentSet.add(student1);
studentSet.add(student2);
studentSet.add(student3);
studentSet.add(student4);
studentSet.add(student5);
//遍历集合
for (Student student : studentSet){
System.out.println(student);
}
}
}
输出结果:
name=Pamela languageScore=87 mathScore=60 sum=147
name=Rose languageScore=80 mathScore=75 sum=155
name=Stark languageScore=85 mathScore=70 sum=155
name=Jake languageScore=88 mathScore=90 sum=178
name=Lee languageScore=99 mathScore=99 sum=198
总结:注意不能只写主要条件,需要再写次要条件,不然只写主要条件的话,当他的主要条件返回0时,就会判断此元素相同,不会添加到集合中去。所以要写次要条件当主要条件相同时,再判断他的次要条件是否相同,再确定是否为同一元素。
未写次要条件比较:
public class ComparableTest {
public static void main(String[] args) {
//创建TreeSet集合对象 使用无参构造 自然排序(comparable)
Set<Student> studentSet = new TreeSet<>();
//创建学生对象
Student student1 = new Student("Rose",80,75);
Student student2 = new Student("Jake",88,90);
Student student3 = new Student("Pamela",87,60);
Student student4 = new Student("Lee",99,99);
//这个学生信息的成绩和student1相同 集合中不会有student5
Student student5 = new Student("Stark",85,70);
//将对象添加到集合
studentSet.add(student1);
studentSet.add(student2);
studentSet.add(student3);
studentSet.add(student4);
//添加但结果并没有 因为只写了主要条件,当主要条件返回0时没有次要条件就会被认为是相同元素
studentSet.add(student5);
//遍历集合
for (Student student : studentSet){
System.out.println(student);
}
}
}
@Override
public int compareTo(Student o) {
//主要条件
int num = this.getSum() - o.getSum();
return num;
}
输出结果:
name=Pamela languageScore=87 mathScore=60 sum=147
name=Rose languageScore=80 mathScore=75 sum=155
name=Jake languageScore=88 mathScore=90 sum=178
name=Lee languageScore=99 mathScore=99 sum=198
案例:不重复的随机数
需求:编写已一个程序,获取10个20之间的随机数,要求随机数不能重复,并且在控制台输出
HashSet集合版本: 输出结果未排序
public class NoRodeRandomTest {
public static void main(String[] args) {
//创建HashSet集合对象
Set<Integer> set = new HashSet<Integer>();
//创建随机数对象
Random random = new Random();
//判断集合小于10 并且产生不重复随机数
while (true){
int number = random.nextInt(20)+1;
set.add(number);
if (set.size() == 10)
break;
}
//遍历集合
for (Integer integer : set){
System.out.print(integer + " ");
}
}
}
输出结果1:
1 17 3 19 7 8 9 10 11 13
输出结果2:
17 18 2 3 19 4 5 6 9 12
TreeSet集合版本:输出结果排序,这里使用的是无参构造方法(自然排序)
public class NoRodeRandomTest {
public static void main(String[] args) {
//创建TreeSet集合对象
Set<Integer> set = new TreeSet<Integer>();
//创建随机数对象
Random random = new Random();
//判断集合小于10 并且产生不重复随机数
while (true){
int number = random.nextInt(20)+1;
set.add(number);
if (set.size() == 10)
break;
}
//遍历集合
for (Integer integer : set){
System.out.print(integer + " ");
}
}
}
输出结果1:
1 3 5 6 9 10 12 13 15 17
输出结果2:
1 2 4 6 12 13 16 17 19 20
1.9 泛型概述和好处
什么是泛型?
泛型指的是广泛的数据类型,就是将原来具体的类型参数化,在使用的时候再传入具体的类型!
泛型的好处:
①将运行时期的错误提前到了编译期间
②省去了类型转换的麻烦
1.9.1 泛型类
泛型的定义格式:
格式:修饰符 class 类名<类型>{ }
范例:public class Generic { }
此处的T可以随便写任意标识,常见的入T、E、K、V等形式的参数常用于表示泛型
1.9.2 泛型方法
泛型方法的定义格式:
格式:修饰符 <类型> 返回值类型 方法名(类型 变量名){ }
范例:public void show(T t) { }
1.9.3 泛型接口
泛型接口的定义格式:
格式:修饰符 interface 接口名<类型>{ }
范例:public interface Generic { }
1.9.4 泛型通配符
为了表示各种类型List的父类,可以使用类型通配符
类型通配符:<?>
List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中。
如果说我们不希望List<?>是任何泛型List的父类,只希望她代表某一类泛型List的父类,可以使用类型通配符的上限
类型通配符上限:<?extends类型>
List<?extends Number>:它表示的类型是Number或者其子类类型
除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限
类型通配符下限:<?super 类型>
List<? super Number>:它表示的类型是Number或者其父类型
通配符的测试:
public class GenericTest {
public static void main(String[] args) {
//类型通配符:<?>
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
System.out.println("---------------");
//类型通配符的上限
List<?extends Number> list4 = new ArrayList<Integer>();
List<?extends Number> list5 = new ArrayList<Number>();
//会报错因为他的上限Number
//List<?extends Number> list6 = new ArrayList<Object>();
//类型通配符的下限
List<?super Number> list7 = new ArrayList<Object>();
List<?super Number> list8 = new ArrayList<Number>();
//会报错因为他的下限是Number
//List<?super Number> list9 = new ArrayList<Integer>();
}
}
1.9.5 可变参数
可变参数又称参数可变,用作方法的形参出现,name方法参数个数就是可变的了
格式:修饰符 返回值类型 方法名(数据类型…变量名){ }
范例:public static int sum(int…a){ }
public class ArgsTest01 {
public static void main(String[] args) {
System.out.println(sum(10,20));
System.out.println(sum(20,15,4,33));
System.out.println(sum(11,12,13));
System.out.println(sum(20,30,40,50,60));
}
/*
当有多个参数时,可变参数要放在不可变参数后面
public static int sum(int b,int...a){}
*/
public static int sum(int...a){
int sum = 0;
//这里的a是一个数组
for (int i : a){
sum += i;
}
return sum;
}
}
可变参数注意事项
①这里的变量其实是一个数组
②如果一个方法有多个参数,包含可变参数,可变参数要放在最后
1.9.6 可变参数的使用
Arrays工具类中有一个静态方法:
public static List asList(T…a):返回由指定数组支持的固定大小的列表
返回的集合并不能做增删操作,可以做修改操作;
List接口中有一个静态方法:
public static List < T>List of (E…element):返回包含任意数量元素的不可变列表
返回的集合不能做增删改操作
Set接口中有一个静态方法:
public static Set of(E…elements):返回一个包含任意数量元素的不可变集合
//public static <T> List<T> asList(T...a):返回由指定数组支持的固定大小的列表
List<String> list = Arrays.asList("Lisa","Jennie","Rose","Jisoo");
//会改变集合的大小 不可操作
//list.add("me"); //UnsupportedOperationException
//会改变集合的大小 不可操作
//list.remove("Lisa");
//不会改变集合长度 可操作
list.set(1,"me");
System.out.println(list);
输出结果:
[Lisa, me, Rose, Jisoo]
//public static <E> List < T>List<E> of (E...element):返回包含任意数量元素的不可变列表
List<Object> list = List.of("1","2",3,"2");
//会报错
// list.add("sd"); //UnsupportedOperationException
//list.remove("1");
//list.set(0,"2");
System.out.println(list);
输出结果:
[1, 2, 3, 2]
// public static <E> Set <E> of(E...elements):返回一个包含任意数量元素的不可变集合
Set<String> set = Set.of("Jake", "Rose", "Jennie");
//set.add("Stark");
//set.remove("Rose");
System.out.println(set);
输出结果:
[Jake, Rose, Jennie]
更多推荐
所有评论(0)