Set接口(本质是一个Map接口)
set接口中没有扩充任何方法,set中有的方法都是Collection中有的方法。
set下的两个常用子类:
1.HashSet(无序存储),底层实现哈希表 + 红黑树(JDK1.8以后),JDK1.8以前只有哈希表。
public class SetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Hello");
set.add("Java");
set.add("SE");
set.add("EE");
set.add("Hello");//重复元素
set.add(null);
set.add(null);//重复元素
System.out.println(set);
}
}
结果:
2.TreeSet(如果没有自定义类则按照ASCII码,升序存储),不允许存放null,底层实现:红黑树
public class SetTest {
public static void main(String[] args) {
Set<String> set = new TreeSet<>();
set.add("e");
set.add("a");
set.add("a");
set.add("c");
set.add("A");
System.out.println(set);
}
}
1. TreeSet中自定义类如何保证元素升序与不重复?
首先看一下使用自定义类时,直接使用Java定义好的工具类什么都不做出现的结果:
class Person{
private Integer age;
private String name;
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
public Integer getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class SetTest {
public static void main(String[] args) {
Set<Person> set = new TreeSet<>();
set.add(new Person(20,"张三"));
set.add(new Person(25,"李四"));
System.out.println(set);
}
}
结果:自定义的类如果不实现Comparable接口,或者comparator接口则不能比较。
非自定义的类可以比较是因为已经自己实现了Compara接口。
所以想让自定义类升序有两种方法:
1.java.lang.Comparable:内部排序接口
int compareTo(T o);
大于0:大于目标对象
等于0:等于目标对象
小于0:小于目标对象
类现了Comparable接口就表示此类具备可比较的性质,所以我们自定义的Person类想要能比较就需要实现Comparable接口。
class Person implements Comparable<Person>{
private Integer age;
private String name;
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
public Integer getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
//首先比较年龄,如果年龄相同,则比较名字(名字按ASCII码排序)
public int compareTo(Person o) {
if (this.age < o.age)
return -1;
if (this.age > o.age)
return 1;
return this.name.compareTo(o.name);
}
}
public class SetTest {
public static void main(String[] args) {
Set<Person> set = new TreeSet<>();
set.add(new Person(25,"王五"));
set.add(new Person(25,"李四"));
set.add(new Person(20,"张三"));
System.out.println(set);
}
}
结果:
2.java.util.Comparator:外部排序接口(推荐使用)策略模式,不用修改源代码
类本身不具备可比较的特性,专门有一个类来比较自定义类的大小,此类专门用于比较两个Person的大小,以后如果需要更换比较策略,只需再写一个新的类就可以,不需要再次更改原代码。
int cpmpare(T o1,T o2);
package fwb.collection.Set;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
class Person2 {
private Integer age;
private String name;
public Person2(Integer age, String name) {
this.age = age;
this.name = name;
}
public Integer getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person2{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
/*
此类专门用于比较两个Person的大小,以后如果需要更换比较策略,
只需再写一个新的类就可以,不需要再次更改原代码
*/
class PersonAgeSec implements Comparator<Person2>{
@Override
public int compare(Person2 o1, Person2 o2) {
if(o1.getAge() < o2.getAge()){
return -1;
}
if (o1.getAge() > o2.getAge()){
return 1;
}
return 0;
}
}
//若以后需要更改比较策略,只需要重新写一个类即可
class PersonAgeDesc implements Comparator<Person2>{
@Override
public int compare(Person2 o1, Person2 o2) {
if(o1.getAge() < o2.getAge()){
return 1;
}
if (o1.getAge() > o2.getAge()){
return -1;
}
return 0;
}
}
public class SetTest2 {
public static void main(String[] args) {
PersonAgeSec personAgeSec = new PersonAgeSec();
//TreeSet有一个构造方法可以接收Comparator的子类
Set<Person2> set = new TreeSet<>(personAgeSec);
set.add(new Person2(25,"王五"));
set.add(new Person2(25,"李四"));
set.add(new Person2(20,"张三"));
System.out.println(set);
}
}
结果:
总结:
自定义类要想使用TreeSet:
- 自定义类自己实现了Comparable
- 要么是从外部传入一个该类的比较器对象(实现了Comparator接口)
通过compare的返回值来判断是否相等,如果为0则重复。
2. HashSet如何保证元素不重复?
HashSet使用equals与hashCode共同来判断元素是否重复
equals:判断两个对象属性是否相同。
hashCode:对象在内存中的地址根据Hash算法转为int。
所以如果自定义类需要覆写equals与hashCode方法:
package fwb.collection.Set;
import java.util.*;
/**
* @program: collection
* @description: HashSet
* @author: fwb
* @create: 2019-07-15 15:06
**/
class Person3 {
private Integer age;
private String name;
public Person3(Integer age, String name) {
this.age = age;
this.name = name;
}
//覆写equals方法
@Override
public boolean equals(Object obj){
//当前对象 == obj(自己和自己比)
if (this == obj){
return true;
}
//如果obj不是当前类对象
if (!(obj instanceof Person3)){
return false;
}
//传进来的时此类对象,并且和自己不是同一个地址
Person3 per = (Person3) obj;
return this.age.equals(per.age)&& this.name.equals(per.name);
}
//覆写hashCode()方法
@Override
public int hashCode() {
return Objects.hash(name,age);
}
public Integer getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person3{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class SetTest3 {
public static void main(String[] args) {
Set<Person3> set = new HashSet<>();
set.add(new Person3(25,"王五"));
set.add(new Person3(25,"李四"));
set.add(new Person3(20,"张三"));
set.add(new Person3(20,"张三"));
System.out.println(set);
}
}
注:
- equlas相同,hashCode一定返回相同
- hashCode相同,equals不一定相同,因为hash算法不同。例:
//hash算法1:
//x = 10
hash(int i){
return i + 1;
}
//hash算法2:
//x = 20
hash(int i){
return i - 9;
}