【JavaSE学习笔记】集合02_Set

本文深入探讨Java集合框架中的Set接口及其实现类,包括HashSet、LinkedHashSet和TreeSet的特性和使用方法。详细分析了自定义对象存储时的注意事项,如重写hashCode和equals方法,以及如何利用TreeSet进行自然排序和比较器排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

集合02

Set

1)概述

Set是无序的(存储和取出不一致),集合中的元素不可重复

Set接口的子实现类:hashSet:不保证set的迭代顺序,特别是它不保证该顺序恒久不变

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

public class Demo01 {
	public static void main(String[] args) {
		//创建set集合对象
		Set<String> set = new HashSet<String>();
		
		//给集合中添加元素
		set.add("hello");
		set.add("java");
		set.add("world");
		set.add("world");
		set.add("java");
		set.add("javaee");
		set.add(null);
		
		//增强for遍历
		for (String s : set) {
			System.out.println(s);
		}
	}
}

2)HashSet

HashSet的add()方法,底层依赖于HashMap(属于Map接口的实现类)的put(Key,Value)

看原码

ineterface Collection{}

interface Set extends Collection{}

class HashSet implements Set{
	 private static final Object PRESENT = new Object();
	 
	 //走无参构造方法,创建了一个HashMap对象
	  public HashSet() {
        map = new HashMap<>();
    }
	 
	  public boolean add(E e) {//传递String类型的元素: e= hello world
        return map.put(e, PRESENT)==null;
    }
}

public class HashMap implements Map<K,V>{//k = hello ,world
	  public V put(K key, V value) {
        if (table == EMPTY_TABLE) {//table:哈希表 ;判断哈希表是否为空!
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key); //调用这个方法:其实调用了一个HashCode()方法
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
 			//k=hello,world,java,world
 														//依赖于Object中的一个equals()
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//key=hello,world,java,world
                V oldValue = e.value;//Vaule oldValue = e=key =传入的元素
                					//world(这个元素Hashcode)
                					//最后一次传入了一个world:hasCode()
                e.value = value;
                e.recordAccess(this);
                return oldValue;// 返回是以前的值;world只打印一次!
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    
     transient int hashSeed = 0;
    
    
     final int hash(Object k) {//k=hello,world,java 
     	
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        //位^操作
		
		//已经重写了Object类的hashcode()方法
        h ^= k.hashCode();//每个字符串元素传递过来,hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
}

首先判断它们的hash(),底层是调用HashCode()方法,已经重写了

如果HashCode():哈希码值相同,再比较它们的内容是否相同(equals:底层重写了Object的equals())

public class Demo03 {
	public static void main(String[] args) {
		System.out.println("hello".hashCode());// 99162322
		System.out.println("hello".hashCode());// 99162322
		System.out.println("java".hashCode());// 3254818
		System.out.println("world".hashCode());// 113318802
	}
}

3)HashSet存储自定义对象

还是调用前面的Student类(需要重写hashCode()和equals())

// Student
public class Student {
	private String name;
	private int age;

	public Student() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Student(String name, int age) {
		super();
		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;
	}

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

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

}

测试类

import java.util.HashSet;

public class Demo02 {
	public static void main(String[] args) {
		// 创建HashSet集合对象
		HashSet<Student> hs = new HashSet<Student>();

		// 创建学生对象
		Student s1 = new Student("刘备", 30);
		Student s2 = new Student("刘备", 38);
		Student s3 = new Student("刘备", 30);
		Student s4 = new Student("关羽", 28);
		Student s5 = new Student("关羽", 29);
		Student s6 = new Student("张飞", 30);
		Student s7 = new Student("张飞", 30);
		Student s8 = new Student("赵云", 25);

		// 将学生对象添加到集合中
		hs.add(s1);
		hs.add(s2);
		hs.add(s3);
		hs.add(s4);
		hs.add(s5);
		hs.add(s6);
		hs.add(s7);
		hs.add(s8);

		// 遍历
		for (Student s : hs) {
			System.out.println(s);
		}
	}
}

4)LinkedHashSet

底层数据结构是由哈希表和链表实现,与hashSet(无序性)有点区别

哈希表:保证元素的唯一性

链表:元素的存储和取出一致(有序性)

import java.util.LinkedHashSet;

public class Demo04 {
	public static void main(String[] args) {
		// 创建LinkedHashSet集合对象
		LinkedHashSet<String> link = new LinkedHashSet<String>();

		// 添加元素
		link.add("hello");
		link.add("world");
		link.add("java");
		link.add("java");
		link.add("hello");
		link.add("world");

		// 遍历
		for (String s : link) {
			System.out.println(s);
		}
	}
}

5)TreeSet集合中有两种排序(重点)

保证元素的唯一性并且排序

自然排序

比较器排序

TreeMap<K,V>---><String, Integer>/<Integer, String>基于红黑树


import java.util.TreeSet;

public class Demo05 {
	public static void main(String[] args) {
		// 创建TreeSet集合对象
		TreeSet<Integer> ts = new TreeSet<Integer>();

		// 给集合中添加元素
		ts.add(20);
		ts.add(17);
		ts.add(23);
		ts.add(18);
		ts.add(22);
		ts.add(24);
		ts.add(19);
		ts.add(18);
		ts.add(24);

		// 使用增强for进行遍历
		for (Integer i : ts) {
			System.out.print(i + " ");
		}
	}
}

为什么能保证元素的唯一性,排序到底怎么选择两种排序?

6)用TreeSet来存储自定义对象(自然排序)

自定义的对象一定要实现Compareable接口

要给排序指定一个主要条件:按照年龄大小

有个问题:如果对象的成员变量值一样(年龄一样),就认为是同一个变量

所以:要有附加条件:如果年龄一样,比较姓名

// 要定义的类中实现Comparable接口:进行自然排序
public class Student implements Comparable<Student> {
	private String name;
	private int age;

	public Student() {
		super();
	}

	public Student(String name, int age) {
		super();
		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;
	}

	@Override
	public String toString() {
		return name + "----" + age;
	}

	// 重写Comparable接口中的compareTo()方法,自己设定排序规则
	@Override
	public int compareTo(Student s) {
		/*
		 * 返回值是要按照刚才的主要条件进行实现
		 * 要按照年龄进行冲小到大排序:主要条件(需求中明确给出主要条件)
		 * 你自己要分析附件的次要条件
		 * 用当前对象的年龄和传入的时间年龄进行比较
		 */
		int num = this.age - s.age; // 从小到大
		/*
		 * num若为0,表示相等;
		 * num若为正,表示this < age;
		 * num若为负,表示大this > age;
		 */
		/*
		 * 当年龄相等,再判断姓名是否一样
		 * 使用三元运算符
		 */
		int num2 = (num == 0) ? this.name.compareTo(s.name) : num;
		// 返回
		return num2;
	}
}
测试类

// 测试类
public class Demo01 {
	public static void main(String[] args) {
		// 创建TreeSet集合对象
		TreeSet<Student> ts = new TreeSet<Student>();

		// 创建8个学生对象
		Student s1 = new Student("zhangguorong", 38);
		Student s2 = new Student("gaoyuanyuan", 27);
		Student s3 = new Student("wanglihong", 29);
		Student s4 = new Student("liushishi", 28);
		Student s5 = new Student("gaoyuanyuan", 27);
		Student s6 = new Student("zhangsan", 27);
		Student s7 = new Student("linqingxia", 49);
		Student s8 = new Student("wuqilong", 38);

		// 将学生对象存储到集合中
		// java.lang.ClassCastException: org.westos_treeset.Student cannot be cast to
		// java.lang.Comparable
		/**
		 * 类转换异常
		 * 当前使用的集合是TreeSet集合:使用元素的自然顺序对元素进行排序(默认),当前并且没有实现Compareable接口进行自然排序
		 * 他底层要依赖于:compareTo:用此对象与指定对象进行比较
		 */
		ts.add(s1);
		ts.add(s2);
		ts.add(s3);
		ts.add(s4);
		ts.add(s5);
		ts.add(s6);
		ts.add(s7);
		ts.add(s8);
		
		// 增强for变量
		for (Student s : ts) {
			System.out.println(s);
		}
	}
}

7)TreeSet集合里面的add()的原码分析

public interface Collection{

}

public interface Set extends Collection{

}

public class TreeMap(双列集合)<K,V>extends AbstractMap<K,V> implements NavigableMap<K,V>{
		 public V put(K key, V value) {
       	 Entry<K,V> t = root; //通过键值对对象创建了一个根节点
        if (t == null) {//现在的代码有根节点有元素
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;//创建Comparator对象(本身就是TreeSet集合的比较排序)
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key); //(s1,s2)
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)//里面的元素是空的
                throw new NullPointerException();//对象是空的,那么抛出一个空指针异常!
            Comparable<? super K> k = (Comparable<? super K>) key;// 创建一个Comparable对象,实现自然排序
            do {
                parent = t;//生成一个根节点(存储第一个元素的时候:作为根节点):parent=entry<K,V>=t=root
                cmp = k.compareTo(t.key);//比较此对象与指定对象的顺序:根节点值和后面存储的元素进行比较
                if (cmp < 0)
                    t = t.left;//小了,作为左孩子
                else if (cmp > 0)
                    t = t.right;//大了,作为孩子
                else
                    return t.setValue(value);//不添加到集合中
            } while (t != null);//给根节点做了一个非空判断
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        //取出元素
        if (cmp < 0)
            parent.left = e;//前序遍历(左边取出)
        else
            parent.right = e;//后续遍历右边取出
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
}



class TreeSet implements Set{
	//成员变量
	  private transient NavigableMap<E,Object> m;
	   private static final Object PRESENT = new Object();
	
	 public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
}


		ts.add(20) ;
		ts.add(17) ;
		ts.add(23) ;
		ts.add(18) ;
		ts.add(22) ;
		ts.add(24) ;
		ts.add(19) ;
		ts.add(18) ;
		ts.add(24) ;
		//17 18 19 20 22 23 24
		
总结:
	TreeSet集合会默认的进行自然排序,最终底层实现的是Compareable接口!

8)使用TreeSet集合存储自定义对象,并遍历

要求:按照姓名的长度进行排序(主要条件)

// 要定义的类中实现Comparable接口:进行自然排序
public class Student implements Comparable<Student> {
	private String name;
	private int age;

	public Student() {
		super();
	}

	public Student(String name, int age) {
		super();
		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;
	}

	@Override
	public String toString() {
		return name + "----" + age;
	}

	@Override
	public int compareTo(Student s) {
		// 逻辑:主要条件:姓名的长度
		int num1 = this.name.length() - s.name.length();
		// 次要条件:长度一样,姓名的内容不一样
		int num2 = (num1 == 0) ? this.name.compareTo(s.name) : num1;
		// 次要条件:如果姓名内容一样,年龄有可能不一样
		int num3 = (num2 == 0) ? this.age - s.age : num2;
		return num3;
	}
}
测试类

import java.util.TreeSet;

// 测试类
public class Demo01 {
	public static void main(String[] args) {
		// 创建TreeSet集合对象
		TreeSet<Student> ts = new TreeSet<Student>();

		// 创建8个学生对象
		Student s1 = new Student("zhangguorong", 38);
		Student s2 = new Student("gaoyuanyuan", 27);
		Student s3 = new Student("wanglihong", 29);
		Student s4 = new Student("liushishi", 28);
		Student s5 = new Student("gaoyuanyuan", 27);
		Student s6 = new Student("zhangsan", 27);
		Student s7 = new Student("linqingxia", 49);
		Student s8 = new Student("wuqilong", 38);

		// 给集合中添加元素
		ts.add(s1);
		ts.add(s2);
		ts.add(s3);
		ts.add(s4);
		ts.add(s5);
		ts.add(s6);
		ts.add(s7);
		ts.add(s8);

		// 增强for变量
		for (Student s : ts) {
			System.out.println(s);
		}
	}
}

9)使用比较器排序,方法一:子实现类

public TreeSet(Comparator<T> comparator)

构造一个新的空TreeSet,它根据指定比较器进行排序

形式参数是个接口类型,所以需要该类的子实现类对象(自定义一个接口子实现类)

// 学生类
public class Student {
	private String name;
	private int age;

	public Student() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Student(String name, int age) {
		super();
		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;
	}

	@Override
	public String toString() {
		return name + "---" + age;
	}
}
子实现类

import java.util.Comparator;

// 自定义一个类:是Comparator接口的子实现类
public class MyComparator implements Comparator<Student> {

	@Override
	public int compare(Student s1, Student s2) {
		// 主要条件是姓名的长度
		int num1 = s1.getName().length() - s2.getName().length();
		// 姓名内容
		int num2 = (num1 == 0) ? s1.getName().compareTo(s2.getName()) : num1;
		// 年龄
		int num3 = (num2 == 0) ? s1.getAge() - s2.getAge() : num2;
		return num3;
	}
}

测试类

import java.util.TreeSet;

public class Demo01 {
	public static void main(String[] args) {
		// 创建TreeSet集合对象
		TreeSet<Student> ts = new TreeSet<Student>(new MyComparator());

		// 创建学生对象
		Student s1 = new Student("gaoyuanyuan", 27);
		Student s2 = new Student("zhangguorong", 29);
		Student s3 = new Student("wanglihong", 27);
		Student s4 = new Student("liushishi", 27);
		Student s5 = new Student("feqingyan", 38);
		Student s6 = new Student("linqingxia", 38);
		Student s7 = new Student("gaoyuanyuan", 26);
		Student s8 = new Student("gaoyuanyuan", 27);

		// 给集合中添加元素
		ts.add(s1);
		ts.add(s2);
		ts.add(s3);
		ts.add(s4);
		ts.add(s5);
		ts.add(s6);
		ts.add(s7);
		ts.add(s8);
		
		//增强for遍历
		for (Student s : ts) {
			System.out.println(s);
		}
	}
}

10)使用比较器排序,方法二:匿名内部类(开发中常用)

import java.util.Comparator;
import java.util.TreeSet;

public class Demo01 {
	public static void main(String[] args) {
		// 创建TreeSet集合对象
		TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {

			@Override
			public int compare(Student s1, Student s2) {
				// 按照姓名长度比较
				int num1 = s2.getName().length() - s1.getName().length();
				// 长度一样,比较内容
				int num2 = (num1 == 0) ? s2.getName().compareTo(s1.getName()) : num1;
				// 内容一样,比较年龄
				int num3 = (num2 == 0) ? s2.getAge() - s1.getAge() : num2;
				return num3;
			}

		});

		// 创建学生对象
		Student s1 = new Student("gaoyuanyuan", 27);
		Student s2 = new Student("zhangguorong", 29);
		Student s3 = new Student("wanglihong", 27);
		Student s4 = new Student("liushishi", 27);
		Student s5 = new Student("feqingyan", 38);
		Student s6 = new Student("linqingxia", 38);
		Student s7 = new Student("gaoyuanyuan", 26);
		Student s8 = new Student("gaoyuanyuan", 27);

		// 给集合中添加元素
		ts.add(s1);
		ts.add(s2);
		ts.add(s3);
		ts.add(s4);
		ts.add(s5);
		ts.add(s6);
		ts.add(s7);
		ts.add(s8);

		// 增强for遍历
		for (Student s : ts) {
			System.out.println(s);
		}
	}
}

11)练习

 键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低输出到控制台

 分析:

1)创建学生类,里面提供成员变量,姓名,语文成绩,数学成绩,英语成绩

2)创建TreeSet集合对象,使用有参构造方式(比较器排序:Coparator comparator)

3)开始录入数据写一个for循环,录入5个学生姓名,成绩等等..

4)遍历,输出

// 学生类
public class Student {
	private String name;
	private int chinese;
	private int math;
	private int english;

	public Student() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Student(String name, int chinese, int math, int english) {
		super();
		this.name = name;
		this.chinese = chinese;
		this.math = math;
		this.english = english;
	}

	public String getName() {
		return name;
	}

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

	public int getChinese() {
		return chinese;
	}

	public void setChinese(int chinese) {
		this.chinese = chinese;
	}

	public int getMath() {
		return math;
	}

	public void setMath(int math) {
		this.math = math;
	}

	public int getEnglish() {
		return english;
	}

	public void setEnglish(int english) {
		this.english = english;
	}

	public int getSum() {
		return getChinese() + getMath() + getEnglish();
	}

	@Override
	public String toString() {
		return name + "\t" + chinese + "\t" + math + "\t" + english + "\t" + getSum();
	}
}
测试类

import java.util.Comparator;
import java.util.Scanner;
import java.util.TreeSet;

public class Demo01 {
	public static void main(String[] args) {
		// 创建TreeSet学生对象
		TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {

			@Override
			public int compare(Student s1, Student s2) {
				// 主要条件:总分从高到底
				int num1 = s2.getSum() - s1.getSum();
				// 总分相同比语文
				int num2 = (num1 == 0) ? s2.getChinese() - s1.getChinese() : num1;
				// 语文相同比数学
				int num3 = (num2 == 0) ? s2.getMath() - s1.getMath() : num2;
				// 数学相同比英语(这步可以省略,前面三个都相同,这个肯定相同)
				// 最后比姓名
				int num4 = (num3 == 0) ? s2.getName().compareTo(s1.getName()) : num3;
				return num4;
			}
		});

		// 开始录入数据
		System.out.println("开始录入学生成绩:");
		System.out.println("-------------------------------------");
		// for循环,明确次数
		for (int i = 0; i < 5; i++) {
			// 键盘录入对象
			Scanner sc = new Scanner(System.in);
			System.out.println("请输入第" + (i + 1) + "个学生的信息:");
			System.out.println("姓名:");
			String name = sc.next();
			// 之所以用sc.next(),不用nextLine():nextLine()后面再输入nextInt()类型会报错
			System.out.println("语文成绩:");
			int chinese = sc.nextInt();
			System.out.println("数学成绩:");
			int math = sc.nextInt();
			System.out.println("英语成绩:");
			int english = sc.nextInt();

			// 创建Student对象
			Student s = new Student(name, chinese, math, english);

			// 将学生对象添加到集合中
			ts.add(s);
			System.out.println("-------------------------------------");
		}

		System.out.println("成绩录入完毕!");
		System.out.println("姓名\t语文\t数学\t英语\t总分");

		// 使用增强for遍历
		for (Student s : ts) {
			System.out.println(s);
		}
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值