Set集合

一、List实现类补充

Vector

描述:和ArrayList类似,都是通过数组扩容进行数据存储;区别在于Vector加了锁;

与ArrayList的区别:

Vector: 安全,性能低;在多线程中数据不会混乱;只是被ArrayList所取代(后面学习线程安全ArrayList)

ArrayList:不安全,性能高;倾向于用在单线程中

//和ArrayList的操作是类似的(扩展)
List list = new Vector();
list.add(1);
list.add(3);
list.add(2);
for (Object object : list) {
    System.out.println(object);
}
//Vector除了上述功能外,自身也提供了存储和遍历的方法
Vector vector = new Vector();
vector.addElement(5);
vector.addElement(2);
vector.addElement(8);
Enumeration enumer = vector.elements();  //枚举器遍历
System.out.println("===============");
while(enumer.hasMoreElements()) { //和迭代器类似
    System.out.println(enumer.nextElement());
}

二、泛型

后续经常要使用泛型集合,在泛型集合中直接进行泛型应用;泛型设计则是先设计,再进行使用

泛型集合

概述:用于约束集合中存储的类型是同一种类型

//案例: 通过集合存储自定义对象,来引出泛型
//需求:存了自定自定义对象后,循环遍历,取出属性---问题:假设存一个非Student类型?
//处理方案:使用泛型约束存储类型
//使用泛型的好处:不用强转,更安全;约束了类型,更规范;使程序更健壮
class Student{
	String name;
	
	public Student(String name) {
		this.name = name;
	}
}
public class Test1 {
	public static void main(String[] args) {
		List<Student> list = new ArrayList<>();
		list.add(new Student("张三丰"));
		list.add(new Student("灭绝"));
		list.add(new Student("张无忌"));
		//list.add("赵敏"); //从源头上规避了存储其他类型
		for (Student st : list) {
			//Student st = (Student) obj; //强转后,取出本身类型
			System.out.println(st.name); //不用强转,直接取出本身类型
		}
	}
}

泛型设计

泛型描述:相当于参数化类型

泛型分类:泛型接口,泛型类,泛型方法

语法: 说明:<>里面是一个大写字母,一般为T(自定义)、E(集合中); 键值对则使用 K和V

//泛型接口:
interface MyList <T>{ //定义了泛型T,在使用时,即可约束使用的类型
	void add(T t);  //往往泛型作为参数或返回值来使用
}
//泛型类:
class MyArrayList<T> implements MyList <T>{
	@Override
	public void add(T t) {
		System.out.println("模拟集合存存元素");
	}
    
    public <E> void Test1(E e){ //泛型方法 <E>
		//T t = e;  //不同的泛型不能赋值
	}
}
//泛型方法:
class MyClass{
	public <T> T test(T t) {
		System.out.println("泛型方法");
		return t;
	}
}
public class Test2 {
	public static void main(String[] args) {
		//使用类泛型类或泛型接口的设计,往往约束传参的类型为同一种类型
		MyList<Integer> list = new MyArrayList<>();
		list.add(666);
		list.add(111);
		
		MyClass class1 = new MyClass();
		class1.test(666); //泛型方法的使用
		
	}
}

三、Collections工具类

Collections:集合工具类; 里面提供了除集合存取以外的方法

//常用方法:
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(3);
list.add(2);
Collections.sort(list);  //排序
System.out.println(list);  //1,2,3

Collections.reverse(list);  //集合元素反转
System.out.println(list);  //3,2,1

Collections.shuffle(list); //随机重置(了解)
System.out.println(list);

//扩充:sort中是如何针对对象排序的?
//存储Integer其实是对象,要重写比较的方法,按照对象的内容来排的
//<T extends Comparable<T>>:说明Integer必须要实现Comparable接口
//<T extends Comparable<? super T>>:说明Integer或父类必须实现Comparable接口

四、Set接口之HashSet

Set接口:Collection的子接口,Set接口没有提供抽象方法,全部继承Collection

Set的特点:无序、无下标,唯一

两个实现类都可以实现该特点:HashSet, TreeSet

基本应用

//基本使用:
Set<Integer> set = new HashSet<Integer>();
set.add(11);
set.add(33);
set.add(22);
set.add(22);  //唯一
System.out.println(set);  //HashSet特点:无序,唯一
//Set是Collection的子接口,所以Collection的常用方法,Set可完全继承

//循环遍历:1.基本for   无下标,不能使用
//for(int i=0;i<set.size();i++) {}

//增强for:有增强for,所以肯定有迭代器
for (Integer integer : set) {
    System.out.println(integer);
}

分析原理

HashSet存储方式:hash算法+数组+链表

细化原理:

存储的对象要得到hash值,hash值在hash表中(数组)得到一个下标位置;判断该下标位置是否有值,如果没有则直接存储;如果有,与该位置的链表元素一一比较;如果相等,则退出,确定唯一性;如果都不相等,则存储到该链表中。

分析规则: 画图分析+原码分析

结论:存储的元素要确定唯一性,则必须重写hashCode和equals

验证原理

通过HashSet存储自定义对象,来验证,是否需要重写hashCode和equals才能确定唯一性

//验证HashSet存储原理:
//结论:存储自定义对象,该对象的类必须重写hashCode和equals,重写后,按属性来匹配
class Student{
	String name;
	int    age;
	public Student(String name,int age) {
		this.name = name;
		this.age  = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int hashCode() {
		//...系统产生
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		//...系统产生
		return true;
	}
}
public class Test2 {
	public static void main(String[] args) {
		Set<Student> set = new HashSet<Student>();
		set.add(new Student("zs", 30));
		set.add(new Student("ls", 20));
		set.add(new Student("zs", 30));
		System.out.println(set); //几个?
	}
}

LinkedHashSet

LinkedHashSet也是Set接口的实现类,是HashSet的子类;存储方式与HashSet类似,就是调用父类的方法实现的。(了解)

存储特点:有序,唯一

//LinkedHashSet:HashSet的子类; 特点:有序,唯一
public class Test3 {
	public static void main(String[] args) {
		Set<Integer> set = new LinkedHashSet<Integer>();
		set.add(1);
		set.add(5);
		set.add(3);
		set.add(2);
		set.add(1); //唯一性
		System.out.println(set); //[1, 5, 3, 2]  有序
	}
}

五、TreeSet实现类

Set接口的另一个重要实现类-TreeSet

存储特点:可排序,无下标,唯一

基本应用

//TreeSet基本应用
Set<Integer> set = new TreeSet<Integer>();
set.add(11);
set.add(33);
set.add(22);
set.add(22);
System.out.println(set);  //[11, 22, 33]  可排序,唯一

//循环遍历:  1.基本for--没有基本for(无下标)
//for(int i=0;i<set.size();i++) {}

//2.增强for  可以有--迭代器一样也有
for (Integer integer : set) {
    System.out.println(integer);
}

分析原理

TreeSet的存储方式:二叉树

细化原理:存第一个元素时,作为根节点;再次存储,则于根比较;比根大则放右边,比根小放左边;查看左/右子树是否有节点,如果没有,则直接存;如果有节点,则继续比较;依次类推;如果找到相等的,则退出,确定唯一性;否则,比较到最后,存进来。

分析规则:画图分析+源码分析

结论:存自定义对象主要要看比较的规则:1.自然排序法(Comparable接口) 2.比较器法

验证原理

TreeSet存储自定义对象,验证原理,看是否能确定唯一性和排序

自定义的类只有一个属性的情况:

//自定义类为一个属性的情况:
//说明:存储自定义对象,自定义类要么实现Comparable接口;要么使用比较器
class Student implements Comparable<Student>{
	String name;
	public Student(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}
	@Override
	public int compareTo(Student o) { //重写的比较方法中,只要返回结果于源码对应上即可
		//将对象的比较,转成属性的比较
		//String的compareTo:前面的小返回<0  前面的大则返回>0  相等则返回0
		//return this.name.compareTo(o.name); //返回<0则存左子树;>0则存右子树;=0唯一性
		
		return o.name.compareTo(this.name);  //比较规则反过来则是降序排列
	}
} 
public class Test2 {
	public static void main(String[] args) {
		Set<Student> set = new TreeSet<Student>();
		set.add(new Student("zs"));
		//ClassCastException: Student cannot be cast to Comparable
		set.add(new Student("ls"));
		set.add(new Student("ww"));
		set.add(new Student("zs"));
		
		System.out.println(set);
	}
}

自定义类有两个属性的情况:

//自定义类有两个属性:比较使,按两个属性的规则来比
//比较规则,按两个属性来比较
class Man implements Comparable<Man>{
	String name;
	int    age;
	public Man(String name,int age) {
		this.name = name;
		this.age  = age;
	}
	@Override
	public int compareTo(Man o) {
		//规则:先按姓名的升序比较;如果姓名相同,再按年龄降序比较
		if(this.name.equals(o.name)) {
			return o.age-this.age; //int类型直接用-比较
		}
		return this.name.compareTo(o.name);
	}
	@Override
	public String toString() {
		return "Man [name=" + name + ", age=" + age + "]";
	}
}
public class Test3 {
	public static void main(String[] args) {
		Set<Man> set = new TreeSet<Man>();
		set.add(new Man("zs", 20));
		set.add(new Man("ls", 20));
		set.add(new Man("zs", 20));
		set.add(new Man("ls", 30));
		set.add(new Man("ls", 25));
		System.out.println(set);
	}
}

六、总结与作业

总结

作业

1.将TreeSet存两个属性对象课堂案例中的比较方式进行变更,先按年龄的升序比较,如果年龄相同,则按姓名的降序比较
2.自己重写对象Student的hashCode和equals方法,保证在set集合中能正确判断是否重复
3.两个HashSet集合中存储字符串元素,集合A:"zs","ls"   集合B:"zs","ww",
  最终使用新的hashSet集合得到交集的“zs”
4.创建一个Student类,有成员变量name和分数(Double类型)。如果两个学生对象的姓名和分数一样视为同一个学生,
先按姓名的降序排,如果姓名相同,则按分数的升序排;
提示:--分数属性使用Double,可使用compareTo方法