Java中的Set集合框架

系列文章目录

Java中的List集合框架
Java中的Map集合框架


前言

本文介绍了Java中常用的集合框架HashSet和TreeSet,本文为系列文章,接下来还会介绍Map,如果喜欢的话,可以点赞,收藏,关注。


一、Set类型的特点

  1. 无序的:Set中的元素没有特定的顺序。添加到Set的元素不会保持原来的顺序。
  2. 无重复的元素:Set的一个重要特性是它不允许重复元素。如果尝试添加一个已经存在的元素,Set将不会接受它。
  3. 线程不安全:Set接口的实现类(例如HashSet和TreeSet)不是线程安全的。如果你在多线程环境中使用,需要额外的同步措施。
  4. 不允许null元素:Set不允许包含null元素。
  5. 没有父/子关系:与List不同,Set没有子列表的概念。
  6. 基于Map实现:大多数Set的实现(如HashSet和LinkedHashSet)是基于Map实现的。这意味着添加和删除操作通常具有O(1)的复杂度,而查找操作具有O(n)的复杂度(在最佳情况下,如在TreeSet中,查找操作具有O(log n)的复杂度)。
  7. 用于存储单例:由于Set不允许重复元素,所以它是存储单例的理想结构。
  8. 支持泛型:从Java 5开始,Set接口的实现类(如HashSet和TreeSet)支持泛型,这使得在编译时检查类型错误成为可能。

二、HashSet案例

HashSet

public class HashSetTest {

    public static void main(String[] args) {
        Set set = new HashSet();
        set.add("苹果");
        set.add("苹果");
        set.add("香蕉");
        set.add("椰子");
        set.add("桃子");
        //因为set是无序的,因为没有下标,所以只能使用增强型for循环
        for (Object o : set) {
            System.out.println(o);
        }

    }
}
/**
运行结果:不可重复,且无序的
桃子
苹果
香蕉
椰子
*/

三、Set的去重原理

先创建一个复杂类型Dog

public class Dog {
    private String name;
    private String type;
	//省略了get和set方法以及构造函数
    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", type='" + type + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Dog dog = (Dog) o;
        return Objects.equals(name, dog.name) && Objects.equals(type, dog.type);
    }
}

HashSet

public class HashSetTest {
    public static void main(String[] args) {
        Set set = new HashSet();
        set.add(new Dog("a","a"));
        //与第一条相同
        set.add(new Dog("a","a"));
        set.add(new Dog("b","b"));
        set.add(new Dog("c","c"));
        set.add(new Dog("d","d"));
        set.add(new Dog("e","e"));
        //因为set是无序的,因为没有下标,所以只能使用增强型for循环
        for (Object o : set) {
            System.out.println((Dog)o);
        }

    }
}
/*
运行结果:
Dog{name='b', type='b'}
Dog{name='e', type='e'}
Dog{name='a', type='a'}
Dog{name='d', type='d'}
Dog{name='a', type='a'}
Dog{name='c', type='c'}
**/

此时,复杂类型并没有被去重!!!

所以,此时需要在Dog类中重写hashCode方法

    @Override
    public int hashCode() {
        super.hashCode();
        return Objects.hash(name, type);
    }

再执行,成功去重

Dog{name='a', type='a'}
Dog{name='b', type='b'}
Dog{name='c', type='c'}
Dog{name='d', type='d'}
Dog{name='e', type='e'}

Set去重的原理主要基于对象的hashCode()和equals()方法。当添加元素到Set时,首先会计算元素的hashCode,然后在Set内部查找具有相同hashCode的元素。如果找到了具有相同hashCode的元素,那么就会调用equals()方法来比较这个元素和Set内部存储的元素是否相同。如果equals()返回true,那么就会忽略新添加的元素,实现去重,简而言之,Set去重一定要重写hashCode()和equals()方法
hashCode方法


equals方法

四、TreeSet算法依赖于一个比较接口

代码案例

public class TreeSetTest {

    public static void main(String[] args) {
        Set set = new TreeSet();
        set.add("a");
        set.add("a");
        set.add("b");
        set.add("c");
        set.add("d");
        set.add("e");
        for (Object o:set){
            System.out.println(o);
        }
    }
}

/**
添加字符串已经去重
a
b
c
d
e
*/

当添加的是对象时

public class TreeSetTest {

    public static void main(String[] args) {
        Set set = new TreeSet();
        set.add(new Dog("a","a"));
        set.add(new Dog("a","a"));
        set.add(new Dog("b","b"));
        set.add(new Dog("c","c"));
        set.add(new Dog("d","d"));
        set.add(new Dog("e","e"));
        for (Object o : set) {
            System.out.println((Dog)o);
        }
    }
}

报错

在这里插入图片描述

出现这个问题是因为,TreeSet在添加遍历复杂类型时,必须实现一个比较接口,Comparable,同时重写compareTo(T o)方法。

public class Dog implements Comparable<Dog>{
    private String name;
    private String type;
    
    //省略了get和set方法已经全参构造函数

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", type='" + type + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Dog dog = (Dog) o;
        return Objects.equals(name, dog.name) && Objects.equals(type, dog.type);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, type);
    }

    @Override
    public int compareTo(Dog o) {
        return name.compareTo(o.name)+type.compareTo(o.type);
    }
}

此时再运行,没有报错

Dog{name='a', type='a'}
Dog{name='b', type='b'}
Dog{name='c', type='c'}
Dog{name='d', type='d'}
Dog{name='e', type='e'}

为什么直接添加字符串不需要实现Comparable接口

原因:String类型中已经自动实现了Comparable了。

在这里插入图片描述

总结

  Set集合对开发产生了重要的影响。首先,它提供了一种有效的方式来存储和处理不重复的数据元素,这有助于减少数据冗余和不一致性。其次,Set集合框架提高了代码的可读性和可维护性,因为它提供了一组标准的方法来操作集合,如添加、删除和检查元素等。
  此外,Set集合也使得代码更加健壮,因为它能够将运行时期的异常转换成编译时的错误,这有助于提早发现并解决潜在的错误。例如,如果你试图将一个重复的元素添加到Set中,编译器将会报错,这避免了在运行时期出现重复元素的错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑的像个child

好人一生平安,先磕为敬

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值