内容学习于:edu.aliyun.com


1. Set接口简介

  Set接口与List接口一样都属于Collection子接口,但是Set接口里面最大特点在于不能够进行重复元素的数据保存,首先来观察Set接口定义:

  • public interface Set extends Collection

  在JDK 1.9以前,Set 接口并没有对Collection接口的方法进行任何的扩充,即:两个接口的方法完全相同的(Set接口没有List接口中的get)方法),只不过从JDK 1.9 开始,为其追加了一些of()方法。

使用of方法观察Set代码:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Set<String> data = Set.of("HELLO", "HELLO");
    }
}

结果:

Exception in thread “main” java.lang.IllegalArgumentException: duplicate element: HELLO

  此时的程序里面可以发现直接抛出了一个“IllegalArgumentException "异常,因为所设置的内容包含有重复数据,而在没有重复数据的情况下才可以实现保存。

代码:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Set<String> data = Set.of("hello", "MLDN");
        System.out.println(data);
    }
}

结果:

[MLDN, hello]

  与之前List最大的差别在于,List 中的数据增加顺序就属于保存顺序,而发现Set集合里面会改变数据的存储顺序。
  此时创建的Set集合无法实现内容的修改,在实际中.一般都会使用Set接口的子类进行实例化处理,常用的子类:HashSet(散列存储)、TreeSet ( 有序存储)、LinkedHashSet (链表存储)。
  如下图所示:

2. HashSet子类(90%)

  在Set集合使用的过程里面,HashSet是一个最为常见的Set接口子类,也是在以后开发中主要使用的一个类型,来观察HashSet类定义的结构

  • public class HashSet extends AbstractSetimplements Set
      如下图所示:

  通过内部的源代码可以发现,实际上HashSet 里面包含有一- 个HashMap的结构(key = value的偶对象)。

  所以每当使用HashSet无参构造的时候,HashSet之中的默认大小为16, 而且每当增长到75%的时候会自动进行扩容。

3. TreeSet子类

  TreeSet是属于一种排序的操作结构,所以里面所保存的内容全部都要求有序存储,此类的继承结构如下:

  • public class TreeSet extends AbstractSetimplements NavigableSet
  • public interface NavigableSet extends SortedSet
  • public interface SortedSet extends Set
      如下图所示:
      这个时候将按照自然顺序(由低到高的升序)进行所有存储数据的排序,同时也不保存有重复的内容。

4. 集合排序说明

  使用TreeSet子类存储的所有的数据内容都是可以进行有序存储的,但是所有数据的排序都需要设置一个排序的规则,而这个排序的规则可以使用两个比较器完成,观察TreeSet的构造方法

  • 使用的是Comparable排序接口: public TreeSet()
  • 设置格外使用的Comparator接口: public TreeSet(Comparator<? super E> comparator)

使用TreeSet保存对象代码:

class Ball implements Comparable<Ball> {
    private String name;
    private double price;

    public Ball(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public int compareTo(Ball o) {
        if (this.price != o.price) {
            return (int) (this.price - o.price);
        } else {
            return this.name.compareTo(o.name);
        }
    }

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

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Set<Ball> data = new TreeSet<>();
        data.add(new Ball("aaa", 1.2));
        data.add(new Ball("bbb", 1.2));
        data.add(new Ball("ccc", 1.2));
        System.out.println(data);
    }
}

结果:

[Ball{name=‘aaa’, price=1.2}, Ball{name=‘bbb’, price=1.2}, Ball{name=‘ccc’, price=1.2}]

  在以后的项目开发过程里面基本上都是针对于数据库中的数据进行排序,所以如果不是必须的情况下,不要轻易使用TreeSet子类,尤其是使用其保存自定义类对象的场景。

5. 重复元素判断

  Set集合最大的特点在于不会进行重复元素的存储,通过之前的代码可以发现TreeSet子类依据的是Comparable接口实现了重复元素的判断,这个重复元素并不是针对于所有的集合类。
  真正的重复元素的判断实际上需要两个操作的支持:

  • 进行对象编码的获取: public int hashCode();
  • 进行对象内容的比较: public boolean equals(Object obj)。

  对于hashCode(需要有一个计算的公式,通过属性计算出来一个不会重复的编码,利用开发工具可以自动生成,在IDEA里面使用“ALT + INSERT" (在类中输入),则可以出现一个生成框。

实现代码:

class Ball {
    private String name;
    private double price;

    public Ball(String name, double price) {
        this.name = name;
        this.price = price;
    }



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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Ball ball = (Ball) o;
        return Double.compare(ball.price, price) == 0 &&
                name.equals(ball.name);
    }

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

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Set<Ball> data = new HashSet<>();
        data.add(new Ball("aaa", 1.2));
        data.add(new Ball("bbb", 1.2));
        data.add(new Ball("ccc", 1.2));
        data.add(new Ball("ccc", 1.2));//重复元素
        System.out.println(data);
    }
}

结果:

[Ball{name=‘ccc’, price=1.2}, Ball{name=‘bbb’, price=1.2}, Ball{name=‘aaa’, price=1.2}]

  带有比较功能的集合类是依据比较器区分的重复,而没有比较功能的集合利用的就是hashCode()和equals()方法实现。