17、集合框架_HashSet\TreeSet\比较器\泛型


一、Set接口中的实现类

  • Set接口存储一组唯一,无序的对象
  • (存入和取出的顺序不一定一致)
  • 操作数据的方法与List类似,Set接口不存在get()方法

?HashSet:采用Hashtable哈希表存储结构   –优点:添加速度快,查询速度快,删除速度快   –缺点:无序   –LinkedHashSet     ? 采用哈希表存储结构,同时使用链表维护次序     ?有序(添加顺序) ?TreeSet   –采用二叉树(红黑树)的存储结构   –优点:有序(排序后的升序)查询速度比List快   –缺点:查询速度没有HashSet快

1、Hash表原理

public class SetDemo implements Comparator {
    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 {
    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();
        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//            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();
        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//            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 list = new ArrayList();
        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//            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:element K:key V:value A:占位 什么字母无所谓,
*/
public class FanXingClass {

    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 fxc = new FanXingClass();
        fxc.setA("mashibing");
        fxc.setId(1);
        fxc.show();

        FanXingClass fxc2 = new FanXingClass();
        fxc2.setA(34);
        fxc2.setId(2);
        fxc2.show();

        FanXingClass fxc3 = new FanXingClass();
        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 {

    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 fxc = new FanXingClass();
        fxc.setA("mashibing");
        fxc.setId(1);
        fxc.show();

        FanXingClass fxc2 = new FanXingClass();
        fxc2.setA(34);
        fxc2.setId(2);
        fxc2.show();

        FanXingClass fxc3 = new FanXingClass();
        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、泛型接口
 *          在定义接口的时候,在接口的名称后添加,
 *          1、子类在进行实现的时候,可以不填写泛型的类型,此时在创建具体的子类对象的时候才决定使用什么类型
 *          2、子类在实现泛型接口的时候,只在实现父类的接口的时候指定父类的泛型类型即可,此时,测试方法中的泛型类型必须要跟子类保持一致
*/
public interface FanXingInterface {

   public B test();

   public void test2(B b);
}

怎么对它进行实现

public  class   FanXingInterfaceSub implements FanXingInterface {


    @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

FanXingInterfaceSub fxm = new FanXingInterface();

也可以

3、泛型方法

/*
*          在定义方法的时候,指定方法的返回值和参数是自定义的占位符,可以是类名中的T,也可以是自定义的Q,只不过在使用Q的时候需要使用
 *          定义在返回值的前面
 */
public class FanXingMethod {

    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public  void show(Q q){
        System.out.println(q);
        System.out.println(t);
    }
}
public class FanXingDemo {
    public static void main(String[] args) {
        FanXingMethod 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、泛型的下限(工作中不用)

如果子类确定了,子类的所有父类都可以直接传递参数使用