17、集合框架_HashSet\TreeSet\比较器\泛型
一、Set接口中的实现类
- Set接口存储一组唯一,无序的对象
- (存入和取出的顺序不一定一致)
- 操作数据的方法与List类似,Set接口不存在get()方法
▪HashSet:采用Hashtable哈希表存储结构
–优点:添加速度快,查询速度快,删除速度快
–缺点:无序
–LinkedHashSet
▪ 采用哈希表存储结构,同时使用链表维护次序
▪有序(添加顺序)
▪TreeSet
–采用二叉树(红黑树)的存储结构
–优点:有序(排序后的升序)查询速度比List快
–缺点:查询速度没有HashSet快
1、Hash表原理
public class SetDemo implements Comparator<Person> { public static void main(String[] args) { Set set = new HashSet(); set.add("123"); set.add(1); set.add(true); set.add("123"); System.out.println(set); }
源码:
HashSet的本质其实是HashMap
public class SetDemo { public static void main(String[] args) { Set set = new HashSet(); set.add("123"); set.add(1); set.add(true); set.add("123"); System.out.println(set); Iterator iterator = set.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } System.out.println("---------"); //将while循环改成for循环,推荐使用 for(Iterator iter = set.iterator(); iter.hasNext();){ System.out.println(iter.next()); } } }
打印结果为:
/* [1, 123, true] 1 123 true --------- 1 123 true Process finished with exit code 0 */
二、Treeset
public class SetDemo { public static void main(String[] args) { TreeSet treeSet = new TreeSet(); treeSet.add("123"); treeSet.add(1); treeSet.add("a"); System.out.println(treeSet); } }
报错:
为什么会报错,需要了解一下TreeSet的数据结构
源码
TreeSet本质上是TreeMap
TreeMap
用的是红黑数
/* 基础知识补充 BST树: 二叉搜索比(Binary Search Tree, 简写BST),又称为二叉排序树,属于树的一种.通过二叉树将数据组织起来,树的每个 节点包含了键值 Key、数据值 data、左子节点指针、右子节点指针.其中键值key是最核心的部分,它的值次定了树的 组织形状; 数据值data是该节点对应的数据, 有些场景可以忽略,举个例子, key 为身份证号而data人名,通过身份证号找人名;左子节点指针指向左子节点,右子节点指针指向右子节点。 特点: 左右子树也分别是二又搜索树. 左子树的所有节点key值都小于它的根节点的key值 右子树的所有节点key值都大于它的根节点的key值 二又搜索树可以为一颗空树. 一般来说.树中的每个节点的key值都不相等,唱根据需要也可以将相同的key值插入树中 AVL树: AVL树.也称平衡二叉搜索树.AVL树属于树的一种.而且它也是一颗二叉搜索树.不同的是 他通过一定机制能保证二叉搜索树的平衡,平衡的二叉搜索树的查询效率更高 特点: AVL树是一颗二叉搜索树. AVL树的左右子书点也是AVL树. AVI树拥有二叉树的所有基本特点. 每个节点的左右子节点的高度之差的绝对值最多为1,即平衡因子的范围为[-1,1] 红黑树 红黑(Red-black)树 是一种自平衡二叉查找树,它与AVL树类似.都在插入和删除操作时能通过旋转操作保持二叉查找树的平衡,以便能获取高效的查找性能。它可以在O(logn)时间内查找,插入和删除等操作。红黑树是2-3-4树的一种等同,但有些红黑树设定只能左边是红树,这种情况就是2-3树的一种等同了。对于AVL树来说,红黑树牺牲了部分平衡性。 特点: 节点是红色或黑色. 根节点是黑也. 每个叶节点(NIL节点)是黑色的. 每个红色节点的两个子节点都为黑色.(从每个叶子到根的所有路径上不能有两个连续的红色节点 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点 最长路径不超过最短路径的2倍 */
左子树小于根,右子树大于根,有一个自动排序的过程
public class SetDemo { public static void main(String[] args) { TreeSet treeSet = new TreeSet(); treeSet.add(31); treeSet.add(1); treeSet.add(6); System.out.println(treeSet); } }
打印结果为:
/* [1, 6, 31] Process finished with exit code 0 */
Set
public class Person implements Comparable{ private String name; private int age; 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; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
public class SetDemo { public static void main(String[] args) { TreeSet treeSet = new TreeSet(); treeSet.add(34); treeSet.add(1); treeSet.add(65); System.out.println(treeSet.ceiling(1)); System.out.println(treeSet); HashSet hashSet = new HashSet(); hashSet.add(new Person("zhangsan",12)); hashSet.add(new Person("zhangsan",12)); hashSet.add(new Person("lisi",13)); System.out.println(hashSet); } }
会打印两个,因为是Set
/* ▪ 总结:
▪ HashSet是如何保证元素的唯一性的呢?
▪ 答:是通过元素的两个方法,hashCode和equals方法来完成 ▪ 如果元素的HashCode值相同,才会判断equals是否为true ▪ 如果元素的hashCode值不同,不会调用equals方法 */
▪ TreeSet
– 采用二叉树(红黑树)的存储结构
– 优点:有序(排序后的升序)查询速度比List快
– 缺点:查询速度没有HashSet快
1、Comparable接口
/* * 比较器分类: * 内部比较器 * 定义在元素的类中,通过实现comparable接口来进行实现 * 外部比较器 * 定义在当前类中,通过实现comparator接口来实现,但是要将该比较器传递到集合中 * 注意:外部比较器可以定义成一个工具类,此时所有需要比较的规则如果一致的话,可以复用,而 * 内部比较器只有在存储当前对象的时候才可以使用 * 如果两者同时存在,使用外部比较器 * 当使用比较器的时候,不会调用equals方法 * */
树中的元素是要默认进行排序操作的,如果是基本数据类型,自动比较,如果是引用类型的话,需要自定义比较器
1.1、内部比较器
• 所有可以“排序”的类都实现了java.lang.Comparable 接口,
Comparable接口中只有一个方法
public int compareTo(Object obj);
public class Person implements Comparable{ private String name; private int age; 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; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } /** * 此比较器按照name的长度来进行比较 * @param o * @return */ @Override public int compareTo(Object o) { Person p = (Person) o; // 强制转换 if (p.name.length()>this.name.length()){ return -1; }else if(p.name.length()<this.name.length()){ return 1; }else{ return 0; } } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
public class SetDemo { public static void main(String[] args) { TreeSet treeSet = new TreeSet(); treeSet.add(new Person("lisi",15)); treeSet.add(new Person("wangwu",13)); treeSet.add(new Person("maliu",12)); treeSet.add(new Person("zhangsan",19)); treeSet.add(new Person("zhangsan",12)); System.out.println(treeSet); } }
1.2、外部比较器
public class Person{ private String name; private int age; 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; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
public class SetDemo implements Comparator<Person> { public static void main(String[] args) { TreeSet treeSet = new TreeSet(new SetDemo()); treeSet.add(new Person("lisi",15)); treeSet.add(new Person("wangwu",13)); treeSet.add(new Person("maliu",12)); treeSet.add(new Person("zhangsan",19)); treeSet.add(new Person("zhangsan",12)); System.out.println(treeSet); } @Override public int compare(Person o1, Person o2) { if(o1.getAge()>o2.getAge()){ return -1; }else if(o1.getAge() < o2.getAge()){ return 1; }else{ return 0; } } }
三、泛型
这时就报错了
list里面有整形、字符串、布尔、自定义对象
可以放object
public class FanXingDemo { public static void main(String[] args) { List list = new ArrayList<String>(); list.add(1); // new Integer(1) list.add("abc");//new String("abc) list.add(true);//new Boolean(true) list.add(new Person("zhangsan",12).toString()); System.out.println(list); // for(int i = 0;i<list.size();i++){ // System.out.println(list.get(i)); // } for(Object iter:list){ System.out.println(iter); } } }
打印结果为:
/* [1, abc, true, Person{name='zhangsan', age=12}] 1 abc true Person{name='zhangsan', age=12} Process finished with exit code 0 */
我现在想要的是只拿Person里的
能强转吗
public class FanXingDemo { public static void main(String[] args) { List list = new ArrayList<String>(); list.add(1); // new Integer(1) list.add("abc");//new String("abc) list.add(true);//new Boolean(true) list.add(new Person("zhangsan",12).toString()); System.out.println(list); // for(int i = 0;i<list.size();i++){ // System.out.println(list.get(i)); // } for(Object iter:list){ Person p = (Person) iter; System.out.println(iter); } } }
报错
不能进行转换int 类型不能转成persion对象
这种情况
当做一些集合的统一操作的时候,需要保证集合的类型是统一的,此时需要泛型来进行限制
/** * 优点: * 1、数据安全 * 2、获取数据时效率比较高 * 给集合中的元素设置相同的类型就是泛型的基本需求 * 使用: * 在定义对象的时候,通过<>中设置合理的类型来进行实现 */
public class FanXingDemo { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("1"); // new Integer(1) list.add("abc");//new String("abc") list.add("true");//new Boolean(true) list.add(new Person("zhangsan",12).toString()); System.out.println(list); // for(int i = 0;i<list.size();i++){ // System.out.println(list.get(i)); // } // for(String iter:list){ System.out.println(iter); } } }
打印结果为:
/* [1, abc, true, Person{name='zhangsan', age=12}] 1 abc true Person{name='zhangsan', age=12} Process finished with exit code 0 */
四、泛型的高阶应用
1、泛型类
/* 在定义类的时候在类名的后面添加<E,K,V,A,B>,起到占位的作用,类中的方法的返回值类型和属性的类型都可以使用
E:element K:key V:value A:占位 什么字母无所谓, */
public class FanXingClass<A> { private int id; private A a; public int getId() { return id; } public void setId(int id) { this.id = id; } public A getA() { return a; } public void setA(A a) { this.a = a; } public void show(){ System.out.println("id : "+id+" ,A : "+a); } }
public class FanXingDemo { public static void main(String[] args) { FanXingClass<String> fxc = new FanXingClass<String>(); fxc.setA("mashibing"); fxc.setId(1); fxc.show(); FanXingClass<Integer> fxc2 = new FanXingClass<Integer>(); fxc2.setA(34); fxc2.setId(2); fxc2.show(); FanXingClass<Person> fxc3 = new FanXingClass<Person>(); fxc3.setA(new Person("aaa",123)); fxc3.setId(3); fxc3.show(); } }
打印结果为:
/* id : 1 ,A : mashibing id : 2 ,A : 34 id : 3 ,A : Person{name='aaa', age=123} Process finished with exit code 0 */
返回特定的类型
public class FanXingClass<A> { private int id; private A a; public int getId() { return id; } public void setId(int id) { this.id = id; } public A getA() { return a; } public void setA(A a) { this.a = a; } public void show(){ System.out.println("id : "+id+" ,A : "+a); } public A get(){ return a; } public void set(A a){ System.out.println("执行set方法:" + a); } }
public class FanXingDemo { public static void main(String[] args) { FanXingClass<String> fxc = new FanXingClass<String>(); fxc.setA("mashibing"); fxc.setId(1); fxc.show(); FanXingClass<Integer> fxc2 = new FanXingClass<Integer>(); fxc2.setA(34); fxc2.setId(2); fxc2.show(); FanXingClass<Person> fxc3 = new FanXingClass<Person>(); fxc3.setA(new Person("aaa",123)); fxc3.setId(3); fxc3.show(); System.out.println(fxc3.get()); fxc3.set(new Person("hhe",123)); } }
打印结果为:
/* id : 1 ,A : mashibing id : 2 ,A : 34 id : 3 ,A : Person{name='aaa', age=123} Person{name='aaa', age=123} 执行set方法:Person{name='hhe', age=123} Process finished with exit code 0 */
2、泛型接口
/* * 2、泛型接口 * 在定义接口的时候,在接口的名称后添加<E,K,V,A,B>, * 1、子类在进行实现的时候,可以不填写泛型的类型,此时在创建具体的子类对象的时候才决定使用什么类型 * 2、子类在实现泛型接口的时候,只在实现父类的接口的时候指定父类的泛型类型即可,此时,测试方法中的泛型类型必须要跟子类保持一致 */
public interface FanXingInterface<B> { public B test(); public void test2(B b); }
怎么对它进行实现
public class FanXingInterfaceSub implements FanXingInterface<String> { @Override public String test() { return null; } @Override public void test2(String string) { System.out.println(string); } }
public class FanXingDemo { public static void main(String[] args) { FanXingInterfaceSub fxi = new FanXingInterfaceSub() ; fxi.test2("123"); } }
打印结果为:
/* 123 Process finished with exit code 0 */
return null 这里的返回值应该如何写, FanXingInterfaceSub<String> 不是真正的String
FanXingInterfaceSub<Integer> fxm = new FanXingInterface<Ingeger>();
也可以
3、泛型方法
/* * 在定义方法的时候,指定方法的返回值和参数是自定义的占位符,可以是类名中的T,也可以是自定义的Q,只不过在使用Q的时候需要使用 * <Q>定义在返回值的前面 */
public class FanXingMethod<T> { private T t; public T getT() { return t; } public void setT(T t) { this.t = t; } public <Q> void show(Q q){ System.out.println(q); System.out.println(t); } }
public class FanXingDemo { public static void main(String[] args) { FanXingMethod<String> fxm = new FanXingMethod<>(); fxm.setT("ttt"); fxm.show(123); fxm.show(true); } }
打印结果为:
/* 123 ttt true ttt Process finished with exit code 0 */
4、泛型的上限(工作中不用)
extends 继承
如果父类确定了,所有的子类都可以直接使用
5、泛型的下限(工作中不用)
如果子类确定了,子类的所有父类都可以直接传递参数使用