Java集合框架——Set接口

第三阶段 JAVA常见对象的学习

集合框架——Set接口

List集合的特点是有序的,可重复的,是不是存在这一种无序,且能保证元素唯一的集合呢?(HashSet )这就涉及到我们今天所要讲的Set集合

Set可以理解为行为不同的Collection

(一) 概述及功能

(1) 概述

Collection

  • List —— 有序(存储顺序和取出顺序一致),可重复
  • Set —— 无序(存储顺序和取出顺序不一致),唯一

    我们首先要清楚有序无序,到底是什么意思?

    集合所说的序,是指元素存入集合的顺序,当元素存储顺序和取出顺序一致时就是有序,否则就是无序。

    我们一般说的无序是指HashSet,它既不能保证存储和取出顺序一致,更不能保证自然顺序(a-z),而TreeSet 是可以实现自然顺序的。(HashSet的有无序问题可是个大问题,下一篇专篇讲解)

(2) 功能

A:基本功能:(继承而来)
//添加功能
boolean add(E e):如果指定的元素不存在,则将其指定的元素添加(可选操作)
boolean addAll(Collection<? extends E> c):将指定集合中的所有元素添加到此集合

//删除功能
void clear():移除集合中的所有元素
boolean remove(Object o):从集合中移除指定的元素
boolean removeAll(Collection<?> c):从集合中移除一个指定的集合元素(有一个就返回true)

//长度功能
int size();

//判断功能
boolean isEmpty():判断集合是否为空
boolean contains(Object o):判断集合中是否包含指定元素
boolean containsAll(Collection<?> c):判断集合中是否包含指定的一个集合中的元素
boolean retainAll(Collection<?> c):仅保留该集合中包含在指定集合中的元素

//获取Set集合的迭代器:
Iterator<E> iterator();

//把集合转换成数组
Object[] toArray():返回一个包含此集合中所有元素的数组
<T> T[] toArray(T[] a):同上,返回的数组的运行时类型是指定数组的运行时类型
    
B:特有功能:
//判断元素是否重复,为子类提高重写方法
boolean equals(Object o):将指定的对象与此集合进行比较以实现相等
int hashCode();:返回此集合的哈希码值

Set集合中的方法用法并不难,可以参照前面Collection、List集合的讲解,对照学习,我们重点讲解Set中一些重要的特点。

(二) HashSet

一句话记住它:一种没有重复元素的无序集合

我们先说说无序是怎么回事,HashSet 它不保证 set 的迭代顺序,特别是它不保证该顺序恒久不变,也就是说它的存储顺序和取出顺序不一致,虽然说它无序,但是,作为集合来说,它肯定有它自己的存储顺序,而你的顺序恰好和它的存储顺序一致,这代表不了有序。下一篇专篇讲解这一问题!

无序问题由于篇幅较长,我们先放到另一边

我们先来思考一下,HashSet是如何保证不重复的呢?

通过查看HashSet中add方法的源码

// HashSet 源码节选-JKD8
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

我们可以看到HashSet中add方法所调用的是HashMap中的put方法,我们定位过去

由于解释篇幅较长,直接给出结论,具体源码解释在HashMap源码分析中具体讲解

这个方法底层主要依赖 两个方法:hashCode()和equals()。

  • 步骤:
  • HashSet方法调用add方法时,调用hashCode(),得到一个哈希值,判断哈希值是否相同。
  • 相同:执行equals()方法

? 返回true:说明元素重复,就不添加

? 返回false:说明元素不重复,就添加到集合

  • 不同:就直接把元素添加到集合

现在大家可能想问一句,只使用hashCode()来判断是否重复可以吗?答案是否定的

我们给出这样一句话:

对象相等则hashCode一定相等,hashCode相等对象未必相等,只有equals返回true,hashCode才相等

  • 如果类没有重写这两个方法,默认使用的Object()。一般来说不会相同。
  • 而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。
  • 对于String 类型来说,不用重写 hashCode()方法和equals()方法都可以保证元素的唯一性,但是如果不是Stirng,而是其它自定义的对象就要重写这两个方法才能保证元素的唯一性。

(三) TreeSet

概述:

TreeSet:底层是二叉树结构(红黑树是一种自平衡的二叉树)

如何存储

那么这一种结构又是如何存储元素的呢?(我们将上图中圆圈称为节点)

  • 第一个元素存储的时候,直接作为根节点存储
  • 第二个元素开始,每个元素从根节点开始比较
    • 若大 则作为右孩子
    • 若小 则作为左孩子
    • 相等 则不作处理

我们来举一个例子看看:

import java.util.Set;
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        Set<Integer> ts = new TreeSet<Integer>();
        ts.add(20);
        ts.add(18);
        ts.add(23);
        ts.add(22);
        ts.add(17);
        ts.add(24);
        ts.add(19);
        ts.add(18);
        ts.add(24);

        for (Integer i : ts) {
            System.out.print(i + " ");
        }
    }
}

//运行结果
17 18 19 20 22 23 24 

我们使用图片来解释一下上面的代码

我们将第一个数字20 作为根节点存放,第二个数字18比20小所以放在左边 23大放在右边

例如22这个数字是如何放到如图的位置呢?

首先22先和20比较是大的所以放到右边,接着继续和23进行比较是小的,所以放到23的左边,接下来同理

我们看到运行结果,很神奇的是按照顺序输出的,这也正符合了我们一开始给出的结论:TreeSet 是可以实现自然顺序的

如何取出

那么TreeSet中元素是如何取出来的呢?

从根节点开始,按照左,中,右的原则依次取出元素即可

分析:我们的根节点是20,所以先看左边也就是18,但是下面还有子节点,我们继续看左边所以第一个数字就是17,然后再看中和右也就是18和19,这时候根节点的左边也就全部看完了,所以接着就是中间的根节点20,右边同理。

如何存储自定义对象

我们设定一种场景,存储学生类中的学生对象,并且按照年龄从小到大排序(自然排序)

当满足所有成员变量的值都相同的时候即为同一个元素

注意:如果一个类的元素要想能够进行自然排序,就必须实现自然排序接口(关键)

public 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 "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student s) {
        //按照年龄排序
        //年龄相同的时候,去看姓名是否也相同
        //String 默认实现了Comparavle接口,所以可以直接使用字符串的compareTo方法
        int num = this.age - s.age;
        int num2 = num == 0 ? this.name.compareTo(s.name) : num;
        return num2;
    }
}
import java.util.Set;
import java.util.TreeSet;

public class StudentDemo {
    public static void main(String[] args) {
        Set<Student> ts = new TreeSet<Student>();
        Student s1 = new Student("张三",27);
        Student s2 = new Student("李四",16);
        Student s3 = new Student("王五",40);
        Student s4 = new Student("马六",40);
        Student s5 = new Student("马六",40);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);

        for (Student s : ts){
            System.out.println(s);
        }

    }
}

//运行结果
Student{name='李四', age=16}
Student{name='张三', age=27}
Student{name='王五', age=40}
Student{name='马六', age=40}

我们可以专门定义了一个类MyComparator,其实也可以省略这一个类,直接在TreeSetDemo测试类中定义一个匿名内部类

package cn.bwh_04_TreeSet;

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

public class StudentDemo2 {
    public static void main(String[] args) {
        Set<Student2> ts = new TreeSet<Student2>(new Comparator<Student2>() {
            @Override
            public int compare(Student2 s1, Student2 s2) {
                //姓名长度
                int num = s1.getName().length() - s2.getName().length();
                //姓名内容
                int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
                //年龄
                int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
                return num3;
            }
        });
        Student2 s1 = new Student2("张三", 27);
        Student2 s2 = new Student2("李四", 16);
        Student2 s3 = new Student2("王五", 40);
        Student2 s4 = new Student2("马六", 40);
        Student2 s5 = new Student2("马六", 40);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);

        for (Student2 s : ts) {
            System.out.println(s);
        }
    }
}

Collection 集合总结

Collection集合应用时的选择

是否唯一

  • 唯一:Set

    • 需要排序:TreeSet
    • 不需要排序:HashSet
    • 如果你知道是Set,但是不知道是哪个Set,就用HashSet。
  • 不唯一:List
    • 需要安全:Vector
    • 不需要安全:ArrayList或者LinkedList
    • 查询多:ArrayList
    • 增删多:LinkedList

如果三原则

如果你知道是List,但是不知道是哪个List,就用ArrayList。

如果你知道是Collection集合,但是不知道使用谁,就用ArrayList。

如果你知道用集合,就用ArrayList。

在集合中常见的数据结构

ArrayXxx:底层数据结构是数组,查询快,增删慢

LinkedXxx:底层数据结构是链表,查询慢,增删快

HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()和equals()

TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序

结尾:

如果内容中有什么不足,或者错误的地方,欢迎大家给我留言提出意见, 蟹蟹大家 !^_^

如果能帮到你的话,那就来关注我吧!(系列文章均会在公众号第一时间更新)

在这里的我们素不相识,却都在为了自己的梦而努力 ?

一个坚持推送原创Java技术的公众号:理想二旬不止

原文地址:https://www.cnblogs.com/ideal-20/p/11143014.html

时间: 2024-11-18 05:13:55

Java集合框架——Set接口的相关文章

Java集合框架——List接口

第三阶段 JAVA常见对象的学习 集合框架--List接口 按照集合框架的继承体系,我们先从Collection中的List接口开始学习 (一) 概述及功能(ArrayList演示) (1) 概述 List在Collection中充当着一个什么样的身份呢?--有序的 collection(也称为序列) 实现这个接口的用户以对列表中每个元素的插入位置进行精确地控制.用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素.与 set 不同,列表通常允许重复的元素. (2)List集

Java集合框架顶层接口collectiion接口

如何使用迭代器 通常情况下,你会希望遍历一个集合中的元素.例如,显示集合中的每个元素. 一般遍历数组都是采用for循环或者增强for,这两个方法也可以用在集合框架,但是还有一种方法是采用迭代器遍历集合框架,它是一个对象,实现了Iterator 接口或ListIterator接口. 迭代器,使你能够通过循环来得到或删除集合的元素.ListIterator 继承了Iterator,以允许双向遍历列表和修改元素. 序号 迭代器方法描述 1 使用 Java Iterator这里通过实例列出Iterato

java集合框架--List接口

1.List接口概述 有序的集合序列.此接口的用户可以对列表中的每个元素的插入位置进行精确的控制.用户可以根据元素的索引(在列表中的位置)访问元素,并搜索列表中的元素. 与Set接口不同,列表通常允许重复的元素. 2.List案例 存储字符串并遍历 package com; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /**  * 存储字符串并遍历  *  */ public clas

Java集合框架的接口和类层次关系结构图

%E7%94%A8groovy%E8%84%9A%E6%9C%AC%E8%BF%9B%E8%A1%8C%E6%AF%8F%E6%97%A5%E5%B7%A5%E4%BD%9C%E7%9A%84%E8%87%AA%E5%8A%A8%E5%8C%96groovy http://auto.315che.com/zhiyue/qa23760388.htm?437o http://auto.315che.com/jingyi/qa23872297.htm http://auto.315che.com/xi

Java集合框架总结(4)——List接口的使用

Java集合框架总结(4)--List接口的使用 List集合代表一个有序集合,集合中每个元素都有其对应的顺序索引.List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素. 1.List接口和ListIterator接口 List作为Collection接口的子接口,可以使用Collection接口里的全部方法.List是有序集合,所以List集合里增加了一些根据索引来操作集合元素的方法: void add(int index, Object element):将元素element插

Java集合框架总结(5)——Map接口的使用

Java集合框架总结(5)--Map接口的使用 Map用于保存具有映射关系的数据(key-vlaue).Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false Map中包含了一个keySet()方法,用于返回Map所以key组成的Set集合. Map集合与Set集合元素的存储形式很像,如Set接口下有HashSet.LinkedHashSet.SortedSet(接口).TreeSet.EnumSet等实现类和子接口,而Map接口下则有HashMa

Java集合框架中List接口的简单使用

Java集合框架可以简单的理解为一种放置对象的容器,和数学中的集合概念类似,Java中的集合可以存放一系列对象的引用,也可以看做是数组的提升,Java集合类是一种工具类,只有相同类型的对象引用才可以放到同一个集合中,否则是不能放进去的: 集合可以对元素进行简单快速的查找.插入.删除操作 某些集合可以有<key value>映射的关系 数组的长度是固定的,而集合的长度是跟随元素的个数动态变化的,灵活性和扩展性都比数组更加优越 数组只能存放基本类型的数据,而集合存放的是对象引用类型的 数组只能通过

Java集合框架之List接口

在上一篇Java集合框架之Collection接口中我们知道List接口是Collection接口的子接口,List接口对Collection进行了简单的扩充,List接口中的元素的特点为有序,可重复,允许null值,因为List继承了Collection接口,所以继承自Collection接口中的方法不再赘述,从List接口中的方法来看,List接口主要是增加了面向位置的操作,允许在指定位置上对集合中的元素进行操作,同时增加了一个能够双向遍历线性表的新列表迭代器ListIterator.下面介

Java集合框架中Map接口的使用

在我们常用的Java集合框架接口中,除了前面说过的Collection接口以及他的根接口List接口和Set接口的使用,Map接口也是一个经常使用的接口,和Collection接口不同,Map接口并不是线性的存放对象的引用,Map接口提供了一种映射关系,所有的元素都是以键值对(Entry类型对象实例)的方式存储的,所以能够根据key快速查找value,key是映射关系的索引,value是key所指向的对象,注意,这里的value不是一个数值,而是一个对象的引用,Java集合框架的元素均是指对象!