Java 进阶篇

序号

内容

链接地址

1

Java核心Api

2

Java集合框架接口

3

List实现类

4

Set实现类

5

Map实现类

6

常见接口

文章目录

  • Java 进阶篇
  • Set集合的常用实现类 TreeSet和HashSet
  • HashSet
  • TreeSet
  • 关于重复元素的说明(重点)
  • 小结:

Set集合的常用实现类 TreeSet和HashSet

HashSet

  • HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
  • HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。

特点

  • 不能保证元素的排列顺序
  • HashSet 不是线程安全的
  • 集合元素可以是 null
  • HashSet集合判断两个元素相等的标准
  • 两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
  • 对于存放在Set容器中的对象,**对应的类一定要重写equals()**和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

向HashSet中添加元素的过程

  • 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)
  • 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。
  • 如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。

重写 hashCode() 方法的基本原则

  • 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
  • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode()

方法的返回值也应相等。

  • 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

重写 equals() 方法的基本原则

以自定义的Customer类为例,何时需要重写equals()?

  • 当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是

要改写hashCode(),根据一个类的equals方法(改写后),两个截然不

同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,

它们仅仅是两个对象。

  • 因此,违反了“相等的对象必须具有相等的散列码”。
  • 结论:复写equals方法的时候一般都需要同时复写hashCode方法,hashCode的对象的属性也应该参与到equals()中进行计算。

示例

例:观察HashSet

import java.util.HashSet;
import java.util.Set;
public class HashSetDemo01 {
    public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        System.out.println(all);
    }
}

使用HashSet 实例化的Set 接口实例,本身属于无序的存放。

在Collection 接口中定义了将集合变为对象数组,可以用来进行输出。

import java.util.HashSet;
import java.util.Set;
public class HashSetDemo02 {
    public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        Object obj[] = all.toArray(); // 将集合变为对象数组
        for (int x = 0; x < obj.length; x++) {
            System.out.print(obj[x] + "、");
        }
    }
}

但是,以上的操作不好,因为在操作的时候已经指定了操作的泛型类型,那么现在最好的做法是由泛型所指定的类型变为指定的数组。

<T> T[] toArray(T[] a)
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo03 {
    public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        String[] str = all.toArray(new String[] {});// 变为指定的泛型类型数组
        for (int x = 0; x < str.length; x++) {
            System.out.print(str[x] + "、");
        }
    }
}

下面再进一步验证Set 接口中是不能有重复的内容的。

import java.util.HashSet;
import java.util.Set;
public class HashSetDemo04 {
    public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("A"); // 重复元素
        all.add("A"); // 重复元素
        all.add("A"); // 重复元素
        all.add("A"); // 重复元素
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        System.out.println(all);
    }
}

以上字符串“A”设置了很多次,因为Set 接口中是不能有任何的重复元素的,所以其最终结果只能有一个“A”。

TreeSet

特点

  • TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
  • TreeSet底层使用红黑树结构存储数据
  • 有序,查询速度比List快再具体就不说了,可以参看这篇博文对红黑树的讲解写得不错。
  • TreeSet 两种排序方法:自然排序定制排序。默认情况下,TreeSet 采用自然排序。
  • 向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。
  • 新增的方法如下: (了解)
• Comparator comparator()
• Object first()
• Object last()
• Object lower(Object e)
• Object higher(Object e)
• SortedSet subSet(fromElement, toElement)
• SortedSet headSet(toElement)
• SortedSet tailSet(fromElement)

示例

下面通过代码来观察其是如何进行排序的。

import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo01 {
    public static void main(String[] args) {
        Set<String> all = new TreeSet<String>(); // 实例化Set接口对象\
        all.add("D");
        all.add("X");
        all.add("A");
        System.out.println(all);
    }
}

虽然在增加元素的时候属于无序的操作,但是增加之后却可以为用户进行排序功能的实现。

3.4.3 排序的说明(重点)

对于自定义类型Person

如果要进行排序的话,则必须在Person 类中实现Comparable 接口。

public class Person implements Comparable<Person> {
    private String name;
    private int age;
    public int compareTo(Person per) {
        if (this.age > per.age) {
            return 1;
        } else if (this.age < per.age) {
            return -1;
        } else {
           return 0;
        }
    } 
    
    public Person() {} 
    
    public Person(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;
    }
    public String toString() {
        return "姓名:" + this.name + ",年龄:" + this.age;
    }
}
import java.util.Set;
import java.util.TreeSet;
public class TreeSetPersonDemo01 {
public static void main(String[] args) {
        Set<Person> all = new TreeSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
}

程序的执行结果如下:

[姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]

从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则此时必须修改Person 类,如果假设年龄相等的话,按字符串进行排序。

public int compareTo(Person per) {
    if (this.age > per.age) {
        return 1;
    } else if (this.age < per.age) {
        return -1;
    } else {
        return this.name.compareTo(per.name);
    }
}

此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。

关于重复元素的说明(重点)

之前使用Comparable 完成的对于重复元素的判断,那么Set 接口定义的时候本身就是不允许重复元素的,那么证明如果现在真的是有重复元素的话,使用HashSet 也同样可以进行区分。

import java.util.HashSet;
import java.util.Set;
public class HashSetPersonDemo01 {
    public static void main(String[] args) {
        Set<Person> all = new HashSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
}

此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过Comparable接口间接完成的(返回值为0时告诉了程序是相同元素)。


如果要想判断两个对象是否相等,则必须使用Object 类中的equals()方法。

从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:

  • 第一种判断两个对象的编码是否一致,这个方法需要通过hashCode()完成,即:每个对象有唯一的编码
  • 还需要进一步验证对象中的每个属性是否相等,需要通过equals()完成。
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    } if (!(obj instanceof Person)) {
        return false;
    }
    Person per = (Person) obj;
    if (per.name.equals(this.name) && per.age == this.age) {
        return true;
    } else {
        return false;
    }
} 

public int hashCode() {
    return this.name.hashCode() * this.age;
}

发现,此时已经不存在重复元素了,所以如果要想去掉重复元素需要依靠hashCode()和equals()方法共同完成。

小结:

关于TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现

Comparable 接口,则不能实现TreeSet 的排序,会报类型转换(转向Comparable 接口)错误。

换句话说要添加到TreeSet 集合中的对象的类型必须实现了Comparable 接口。

不过TreeSet 的集合因为借用了Comparable 接口,同时可以去除重复值,而HashSet 虽然是

Set 接口子类,但是对于没有复写Object 的equals 和hashCode 方法的对象,加入了HashSet

集合中也是不能去掉重复值的。