Java基础 | 集合(四)

简介: Java集合

(四)Set集合

1.Set集合概述和特点

Set集合特点

  • 不包含重复元素的集合
  • 没有带索引的方法,所以不能使用普通for循环

Set集合练习

  • 存储字符串并遍历
import java.util.HashSet;
import java.util.Set;

public class SetDemo {
    public static void main(String[] args) {
        //创建集合对象
        Set<String> set = new HashSet<>();//HashSet对集合的迭代顺序不作任何保证

        //添加元素
        set.add("hello");
        set.add("java");
        set.add("world");
        //不包含重复元素
        set.add("world");//结果只有一个world

        //遍历集合
        for (String s : set) {
            System.out.println(s);
        }
    }
}

2.哈希值

哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值

Object类中有一个方法可以获取对象的哈希值

  • public int hashCode():返回对象的哈希码值

对象的哈希值特点

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
  • 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同

3.HashSet集合概述和特点

HashSet集合特点

  • 底层数据结构是哈希表
  • 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
  • 没有带索引的方法,所以不能使用普通for循环遍历
  • 由于是Set集合,所以是不包含重复元素的集合

HashSet集合练习

  • 存储字符串并遍历
import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo {
    public static void main(String[] args) {
        HashSet<String> sh = new HashSet<>();
        sh.add("hello");
        sh.add("world");
        sh.add("java");

        //迭代器遍历
        Iterator<String> it = sh.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }

        //增强for遍历
        for(String s : sh){
            System.out.println(s);
        }

    }
}

4.HashSet集合保证元素唯一性源码分析

HashSet集合添加一个元素过程:

image-20220101163525259

HashSet集合存储元素:

  • 要保证元素唯一性,需要重写hashCode()和equals()

5.案例(HashSet集合存储学生对象并遍历)

要求:学生对象的成员变量值相同,我们就认为是同一个对象

代码实现:

创建学生类(需要重写hashCode()和equals()方法来保证元素的唯一性)

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;
    }

    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;
    }

    //重写重写hashCode()和equals()方法来保证元素的唯一性
    @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);
    }
}

创建测试类

import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo {
    public static void main(String[] args) {
        HashSet<Student> sh = new HashSet<>();
        Student s1 = new Student("学生1", 20);
        Student s2 = new Student("学生2", 21);
        Student s3 = new Student("学生3", 22);
        Student s4 = new Student("学生1",20);//与s1相同

        sh.add(s1);
        sh.add(s2);
        sh.add(s3);
        sh.add(s4);//结果并未显示s4,因为与s1位同一个元素,在学生类定义中重写了hashCode()和equals()方法确保了元素的唯一性

        //迭代器遍历集合
        Iterator<Student> it = sh.iterator();
        while (it.hasNext()) {
            Student s = it.next();
            System.out.println(s.getName() + ", " + s.getAge());
        }

        //增强for遍历集合
        for(Student s : sh) {
            System.out.println(s.getName() + ", " + s.getAge());
        }
    }
}

6.LinkedHashSet集合概述和特点

LinkedHashSet集合特点

  • 哈希表和链表实现的Set接口,具有可预测的迭代次序
  • 由链表保证元素有序,也就是 说元素的存储和取出顺序是一致的
  • 由哈希表保证元素唯一,也就是说没有重复的元素

LinkedHashSet集合练习

  • 存储字符串并遍历
import java.util.LinkedHashSet;

public class LinkedHashSetDemo {
    public static void main(String[] args) {
        //LinkedHashSet集合保证了元素的唯一性也保证了存储和取出的顺序的一致性
        LinkedHashSet<String> lh = new LinkedHashSet<>();
        lh.add("hello");
        lh.add("java");
        lh.add("world");

        lh.add("hello");//元素唯一性,所以结果不显示多余的hello字符串
        for (String s : lh) {
            System.out.println(s);
        }
    }
}

7.TreeSet集合概述和特点

TreeSet集合特点

  • 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法

    ​ TreeSet():根据其元素的自然排序进行排序

    ​ TreeSet(Comparetor comparator):根据指定的比较器进行排序

  • 没有带索引的方法,所以不能使用普通for循环遍历
  • 由于是Set集合,所以不包含重复元素

TreeSet集合练习

  • 存储整数并遍历
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Integer> ts = new TreeSet<>();
        //添加元素
        ts.add(2);
        ts.add(3);
        ts.add(1);

        //因为它是Set集合,所以不包含重复元素
        ts.add(2);

        for(Integer it: ts) {
            System.out.print(it);//结果为 1 2 3
        }
    }
}

8.自然排序Comparable的使用

  • 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
  • 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
  • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写

练习:

  • 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
  • 要求:按照年龄从小到大牌,年龄相同时,按照姓名的字母顺序排序

代码实现:

定义学生类:

public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student() {
    }

    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;
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Student s) {
//        return -1;//降序
//        return 0;//元素重复
//        return 1;//升序

        int flag = this.age - s.age;    //this在前为升序,在后为降序
        int flag2 = flag == 0 ? this.name.compareTo(s.name) : flag;        //String类自带compareTo方法
        return flag2;
    }
}

创建测试类:

import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<>();
        Student s1 = new Student("学生1",22);
        Student s2 = new Student("学生2",23);
        Student s3 = new Student("学生3",33);

        Student s4 = new Student("学生4",33);

        Student s5 = new Student("学生4",33);
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);//s5与s4元素重复所以结果不显示

        for(Student s:ts) {
            System.out.println(s.getName()+", "+s.getAge());
        }
    }
}

9.比较器排序Comparator的使用

  • 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
  • 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
  • 重写方法时,一定要注意排序规则必须按照要求的主要和次要条件来写

练习:

  • 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
  • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

代码实现

定义一个标准的学生类:(此处省略)

定义测试类:

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                int flag = s1.getAge() - s2.getAge();
                int flag2 = flag == 0 ? s1.getName().compareTo(s2.getName()) : flag;
                return flag2;
            }
        });

        Student s1 = new Student("学生1",22);
        Student s2 = new Student("学生2",23);
        Student s3 = new Student("学生3",33);

        Student s4 = new Student("学生4",33);

        Student s5 = new Student("学生4",33);
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);//s5与s4元素重复所以结果不显示

        for(Student s:ts) {
            System.out.println(s.getName()+", "+s.getAge());
        }
    }
}

分析:

这里o1表示位于前面的对象,o2表示后面的对象

  • 返回-1(或负数),表示不需要交换01和02的位置,o1排在o2前面,asc
  • 返回1(或正数),表示需要交换01和02的位置,o1排在o2后面,desc

下面来用我们之前的结论解释为什么 return o2.a - o1.a 就是降序了:

首先o2是第二个元素,o1是第一个元素。无非就以下这些情况:
①: o2.a > o1.a: 那么此时返回正数,表示需要调整o1,o2的顺序,也就是需要把o2放到o1前面,这不就是降序了么。

②:o2.a < o1.a : 那么此时返回负数,表示不需要调整,也就是此时o1 比 o2大, 不还是降序么。


10.案例(不重复的随机数)

编写一个程序,获取10个1~20之间的随机数,要求随机数不能重复,并在控制台输出

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

public class SetDemo {
    public static void main(String[] args) {
        //创建集合对象
        //Set<Integer> set = new HashSet<>();//HashSet集合是无序且元素唯一
        Set<Integer> set = new TreeSet<>();//TreeSet集合是有序且元素唯一

        //创建随机数对象
        Random r = new Random();

        //判断集合的长度是否小于10
        while (set.size() < 10) {
            int sum = r.nextInt(20) + 1;
            set.add(sum);
        }

        for(Integer i :set) {
            System.out.println(i);
        }
    }
}
目录
相关文章
|
2月前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
53 6
|
2月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
48 3
|
2月前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
38 2
|
16天前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
36 5
|
29天前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
40 4
|
1月前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
35 2
|
1月前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
1月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
1月前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
1月前
|
Java 开发者