Java拾贝第十四天——集合之Set

发布时间 2023-11-02 15:56:33作者: ocraft

Set

Set是Collection的子接口,其定义如下:

public interface Set<E> extends Collection<E>

与List相同,此接口也使用了泛型,使用时必须指定具体的类型。

Set常见的实现子类:HashSet、TreeSet

HashSet

HashSet是Set的子类,其类定义如下:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

HashSe是无序的,其内部结构是散列表。

HashSet类的常用方法如下:

方法 类型 描述
boolean add(E e) 普通方法 添加元素
boolean remove(Object o) 普通方法 删除元素
boolean contains(Object o) 普通方法 是否包含某元素

boolean add(E e)

添加元素

栗子:

    public static void main(String[] args) {
        Set<String> set = new HashSet<>();

        set.add("张三");
        set.add(new String("张三"));
        String str = new String("张三");
        set.add(str);
        //测试存放相同的元素


        set.add("李四");
        set.add("王五");

        for (String s : set) {
            System.out.println(s);
        }
    }

程序运行结果:

李四
张三
王五

从结果可以看出HashSet中不能存放相同的元素,且HashSet内部的元素是无序的。

问:HashSet是如何判断重复的元素呢?
	答:散列码(哈希编码)。

众所周知Object是所有类的父类,在Object中有一个hashCode()方法来得到散列码。

每一个对象都有一个默认的散列码(哈希编码)。

对于自定义的类,想要达到去重的效果存放于HashSet集合中,需要重写其hashCode()方法。

boolean remove(Object o) 和 boolean contains(Object o)

删除元素 和 判断元素是否存在

栗子:

    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("张三");
        set.add("李四");
        set.add("王五");
        set.add("赵五");

        System.out.println("删除元素前");
        for (String s : set) {
            System.out.println(s);
        }

        System.out.println("删除元素后");
        set.remove("张三");
        System.out.println(set.contains("张三"));
        for (String s : set) {
            System.out.println(s);
        }
    }

程序运行结果:

删除元素前
李四
张三
王五
赵五
删除元素后
false
李四
王五
赵五

TreeSet

TreeSet是Set的子类,其类定义如下:

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable

TreeSet是有序的。

TreeSet常用方法同HashSet类的常用方法

栗子:

    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("王五");
        set.add("张三");
        set.add("赵六");
        set.add("李四");

        for (String s : set) {
            System.out.println(s);
        }
    }

程序运行结果:

张三
李四
王五
赵六

从栗子中可以清楚地看到,就算添加数据时没有顺序,其输出结果还是有序的。

问:TreeSet是如何判断重复的元素呢?
	答:对象的equals()方法返回值,或CompareTo()方法返回值。

问:TreeSet是如何实现的排序呢?
	答:TreeSet存储对象的时候, 可以排序, 但是需要指定排序的算法。
	对于上述栗子,我们存储的对象是String,String内部有默认排序。
	对于自定义的类,必须实现Comparable接口并重写其compareTo()方法。

自定义的类在TreeSet中使用

因为TreeSet中的元素都是有序排放的。

所以对于自定义的类想要在TreeSet中使用

  1. 实现Comparable接口
  2. 重写其compareTo()方法
  3. 在方法中指定排序规则

栗子:

public class Test14 {
    public static void main(String[] args) {
        Set<Ball> set = new TreeSet<>();
        set.add(new Ball("C",42.3));
        set.add(new Ball("B",32.3));
        set.add(new Ball("A",22.3));
        set.add(new Ball("A",22.3));
        set.add(new Ball("D",22.3));

        for (Ball ball : set) {
            System.out.println(ball);
        }
    }
}

class Ball implements Comparable {
    private String name;
    private double sale;

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

    @Override
    public int compareTo(Object o) {//compareTo方法也叫比较器
        Ball b = (Ball) o;//向下转型
        //this指向调用compareTo()方法的对象
        if (this.sale > b.sale) {//大返回-1
            return 1;
        } else if (this.sale < b.sale) {
            return -1;//小返回-1
        } else {
            return 0;//相等返回0
        }
    }

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

程序运行结果:

Ball{name='A', sale=22.3}
Ball{name='B', sale=32.3}
Ball{name='C', sale=42.3}

对于上述栗子,添加元素时是无序的,但输出会自动调用compareTo()方法(比较器),然后按照规则排序。

但栗子中的比较器仅仅是根据sale属性进行排序,所以会导致存在D被过滤掉。

至此,修改栗子如下:

public class Test14 {
    public static void main(String[] args) {
        Set<Ball> set = new TreeSet<>();
        set.add(new Ball("C",42.3));
        set.add(new Ball("B",32.3));
        set.add(new Ball("A",22.3));
        set.add(new Ball("A",22.3));//重复的内容
        set.add(new Ball("D",22.3));

        for (Ball ball : set) {
            System.out.println(ball);
        }
    }
}

class Ball implements Comparable {
    private String name;
    private double sale;

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

    @Override
    public int compareTo(Object o) {
        Ball b = (Ball) o;//向下转型

        if (this.sale > b.sale) {//this指向调用compareTo()方法的对象
            return 1;
        } else if (this.sale < b.sale) {
            return -1;
        } else {//如果sale属性相等就比较name属性
            return this.name.compareTo(b.name);//使用String类内部默认的比较器进行字符串比较
        }
    }

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

程序运行结果:

Ball{name='A', sale=22.3}
Ball{name='D', sale=22.3}
Ball{name='B', sale=32.3}
Ball{name='C', sale=42.3}

从结果可以发现,D正确的出现了,并且也成功去重。

但此时的去重只是依靠Comparable接口完成的。

如果将TreeSet集合换成HashSet集合也会出现重复的内容。

想要真正的实现去重,必须重写equals() 和 hashCode()

至此,修改栗子如下:

public class Test14 {
    public static void main(String[] args) {
        Set<Ball> set = new HashSet<>();
        set.add(new Ball("C", 42.3));
        set.add(new Ball("B", 32.3));
        set.add(new Ball("A", 22.3));
        set.add(new Ball("A", 22.3));
        set.add(new Ball("D", 22.3));

        for (Ball ball : set) {
            System.out.println(ball);
        }
    }
}

class Ball {//不用比较器的去重
    private String name;
    private double sale;

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;//地址值相同返回T
        if ( !(o instanceof Ball)) return false;//属于Ball或Ball子类的取反 返回F
        Ball ball = (Ball) o;//向下转型
        if (this.name.equals(ball.name) &&this.sale== ball.sale) return true; //全部属性相等 是一个对象
        else return false;//全部属性不相等 不是一个对象
    }

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

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

程序运行结果:

Ball{name='D', sale=22.3}
Ball{name='B', sale=32.3}
Ball{name='A', sale=22.3}
Ball{name='C', sale=42.3}