Set接口

Set接口明显区别于List接口的是,它内部存储的数据是无序不可重复的。如果元素重复,则不允许添加。
由于Set接口也是Conllections的子接口,所以Set接口同样继承了Collections的方法,具体请看我的贴子
Java学习之集合Collection接口
对于Set接口继承而来的方法,就不再细讲。下边看一个案例:

import java.util.HashSet;
import java.util.Set;

public class Demo1 {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>() ;
        System.out.println(set.add("大黄"));
        System.out.println(set.add("大黄"));
        for (String s : set) {
            System.out.println(s);
        }
    }
}

执行结果:

java set方法链式调用 java set接口方法_java

可以看到,当我们已经Set集合中新增了一个元素,再去添加相同的元素时,返回值是一个false。并且遍历完之后,确实发现元素没有被插入,这证明了我们的Set集合无法在同一个集合里,插入相同的元素。

Set接口下边有两个实现类
1、HashSet类

HashSet的方法实现自Set接口,不再过多解释。需要注意的是,当我们需要在HashSet集合中存储对象,应该如何存储。

测试存储实体类

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

class Car{
    private String carId;
    private String color;
    private String brand;

    public Car(String carId, String color, String brand) {
        this.carId = carId;
        this.color = color;
        this.brand = brand;
    }

    public Car() {
    }

    public String getCarId() {
        return carId;
    }

    public void setCarId(String carId) {
        this.carId = carId;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    @Override
    public String toString() {
        return "Car{" +
                "车牌号:'" + carId + '\'' +
                ", 颜色:'" + color + '\'' +
                ", 品牌:'" + brand + '\'' +
                '}';
    }

}
public class Demo2 {
    public static void main(String[] args) {
        Set<Car> carSet = new HashSet<>();
        carSet.add(new Car("豫A.88888","银色","保时捷"));
        carSet.add(new Car("豫A.66666","火烈鸟","法拉利"));
        carSet.add(new Car("豫A.QQ123","军绿色","拖拉机"));
        Car car1  = new Car("豫A.12345","黑色","五菱之光");
        Car car2  = new Car("豫A.12345","黑色","五菱之光");
        System.out.println(car1.hashCode());//哈希值356573597
        System.out.println(car2.hashCode());//哈希值1735600054
        System.out.println(carSet.add(car1));//返回true
        System.out.println(carSet.add(car2));//返回true
        for (Car car : carSet) {
            System.out.println(car);
        }
    }
}

执行结果:

java set方法链式调用 java set接口方法_System_02

可以看到,这里的执行结果和我们预期的执行结果并不相同。HashSet集合是不允许存储重复元素的,但是为什么这里有两个内容相同的对象,却被添加到集合中了呢?
【注意】:HashSet集合通过存入集合元素的哈希值来判断当前的元素是否在集合中存在,所以,即使我们的对象内容完全一样。但是在内存中却是两个不同的对象,这在开发中显然是不允许的。那么我们该如何解决这个BUG。

案例修改:

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

class Car{
    private String carId;
    private String color;
    private String brand;

    public Car(String carId, String color, String brand) {
        this.carId = carId;
        this.color = color;
        this.brand = brand;
    }

    public Car() {
    }

    public String getCarId() {
        return carId;
    }

    public void setCarId(String carId) {
        this.carId = carId;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    @Override
    public String toString() {
        return "Car{" +
                "车牌号:'" + carId + '\'' +
                ", 颜色:'" + color + '\'' +
                ", 品牌:'" + brand + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o;
        return Objects.equals(carId, car.carId) && Objects.equals(color, car.color) && Objects.equals(brand, car.brand);
    }

    @Override
    public int hashCode() {
        return Objects.hash(carId, color, brand);
    }
}
public class Demo2 {
    public static void main(String[] args) {
        Set<Car> carSet = new HashSet<>();
        carSet.add(new Car("豫A.88888","银色","保时捷"));
        carSet.add(new Car("豫A.66666","火烈鸟","法拉利"));
        carSet.add(new Car("豫A.QQ123","军绿色","拖拉机"));
        Car car1  = new Car("豫A.12345","黑色","五菱之光");
        Car car2  = new Car("豫A.12345","黑色","五菱之光");
        System.out.println(car1.hashCode());//哈希值一样,且内容也一样。则不能添加进集合。
        System.out.println(car2.hashCode());
        System.out.println(carSet.add(car1));//返回true
        System.out.println(carSet.add(car2));//返回false
        for (Car car : carSet) {
            System.out.println(car);
        }
    }
}

可以看到,我在Car实体类中重写了equals()和HashCode方法。而这两个方法就是判断HashSet方法存储元素的依据。在官方的API中,HashSet是有这两个方法的。而它的方法是继承自我们的顶级父类Object。所以我们可以在Car实体类中重写这两个方法,让这两个方法可以识别对于不同内存地址的对象,进行内容是否一致的判断。并且API规定,当我们一旦重写了HashSet方法,我们就必须重写HashCode方法。

接下来看执行结果:

java set方法链式调用 java set接口方法_java_03

可以看到,当我们重写两个方法后,再次执行add操作时,会自动判断两个对象是否一样(包括他们的每一个属性),这样就实现了当我们往HashSet集合中添加元素时,不允许添加重复数据的功能。

1、TreeSet类

TreeSet类的方法和HashSet类的方法一致,但是不同的是,TreeSet类底层是通过二叉树来实现的。所以,对于存入TreeSet集合的元素,它会自动的给所有存入的元素进行排序。那么同样的问题来了,我们该怎么在TreeSet集合中存储对象

测试存储实体类

import java.util.Set;
import java.util.TreeSet;
class Student{
    private String name;
    private Integer age;

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

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

}
public class Demo3 {
    public static void main(String[] args) {
        Set<Student> studentSet = new TreeSet<>();
        studentSet.add(new Student("大飞", 25));
        studentSet.add(new Student("卢本伟", 26));
        studentSet.add(new Student("UZI", 23));
        studentSet.add(new Student("厂长", 30));
        for (Student s : studentSet) {
            System.out.println(s);
        }
    }
}

执行结果:

java set方法链式调用 java set接口方法_java set方法链式调用_04

遗憾的是,Idea给我报了一个类解析异常。分析原因,原来是当我把元素添加进TreeSet集合的时候,它会自动进行排序,但是实体对象是无法比较大小的。明白了原因,下一步是怎么把它解决。

官方API给了我们答案

java set方法链式调用 java set接口方法_学习_05


可以看到,我们必须去实现一个Comparable接口。

案例修改:

import java.util.Set;
import java.util.TreeSet;
class Student implements Comparable<Student>{
    private String name;
    private Integer age;

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

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

    @Override
    public int compareTo(Student o) {
        int num = this.age - o.age;
        return num;
    }
}
public class Demo3 {
    public static void main(String[] args) {
        Set<Student> studentSet = new TreeSet<>();
        studentSet.add(new Student("大飞", 25));
        studentSet.add(new Student("卢本伟", 26));
        studentSet.add(new Student("UZI", 23));
        studentSet.add(new Student("厂长", 30));
        for (Student s : studentSet) {
            System.out.println(s);
        }
    }
}

我们在Student类中实现了Comparable接口,并且重写了compareTo()方法,让其返回值为实体类之间的年龄差。

执行结果:

java set方法链式调用 java set接口方法_java set方法链式调用_06

可以看到我们的添加操作已经完成。
但是这样写的话,就又出现了一个BUG。假设我要添加一个新的元素进入集合,同样年龄,那么肯定不能添加进去,因为当排序的属性值相同的时候是进不去集合的。

java set方法链式调用 java set接口方法_System_07


执行结果:

java set方法链式调用 java set接口方法_java set方法链式调用_08

于是我们不得不对方法进行二次升级:

java set方法链式调用 java set接口方法_java_09


如上图:当年龄相同的时候,我们就让其比较名字。注意,这里的compareTo是String类自带的方法。会自动根据字典排序。

执行结果如下:

java set方法链式调用 java set接口方法_java set方法链式调用_10


至此,TreeSet集合存储对象类型的元素已经实现。


TreeSet集合存储对象实现比较的第二种方法

java set方法链式调用 java set接口方法_学习_11

如上图,官方其实给了我们另一种实现存入元素的实现自动排序的思路。那就是 比较器
话不多说上代码:

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
class Student{
    private String name;
    private Integer age;

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

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

}
class MyComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        int num = o1.getAge() - o2.getAge();
        if (o1.getAge() == o2.getAge()){
            return o1.getName().compareTo(o2.getName());
        }
        return num;
    }
}
public class Demo3 {
    public static void main(String[] args) {
        Set<Student> studentSet = new TreeSet<>(new MyComparator());
        studentSet.add(new Student("大飞", 25));
        studentSet.add(new Student("卢本伟", 26));
        studentSet.add(new Student("UZI", 23));
        studentSet.add(new Student("厂长", 30));
        studentSet.add(new Student("厂公", 30));
        for (Student s : studentSet) {
            System.out.println(s);
        }
    }
}

上面的代码,通过实现了Comparator接口的MyComparator 类,传入TreeSet的构造方法。实现了对存入TreeSet集合的自动排序。
至此Set接口讲解完毕。