集合(二)~Set

一、Set集合概述和特点

1. Set集合概述和特点

java.util.Set 接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加严格了。与List 接口不同的是,Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。Set 集合有多个子类,这里我们介绍其中的 HashSet 、LinkedHashSet及TreeSet这三个集合。

二、集合 HashSet

1.HashSet类概述

java.util.HashSet 是 Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。 java.util.HashSet底层的实现其实是一个java.util.HashMap 支持,由于我们暂时还未学习,先做了 解。
​ HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode 与 equals 方法。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

6.2.2 HashSet使用-去重

1. 存储字符串并遍历

public class Test {
    public static void main(String[] args) {
        // Set是接口,创建对象new 的是 实现类HashSet 是其中一个实现类
        Set<String> set = new HashSet<String>();
        //给集合  添加元素
        set.add("Hello1");
        set.add("Hello1");
        set.add("Hello2");
        set.add("Hello3");
        set.add("Hello4");
        //增强for遍历
        for (String string : set) {
            //不含有重复元素   Hello1
            //且存取顺序不一致
            System.out.println(string);
        }
    }
}

运行结果:

img02.jpg

2. 存储自定义对象并遍历

public class Test {
    public static void main(String[] args) {
        // Set是接口,创建对象new的是实现类,HashSet 是其中一个实现类
        Set<Student> set = new HashSet<Student>();
        //给集合  添加元素
        set.add(new Student(15, "小李"));
        set.add(new Student(15, "小王"));
        set.add(new Student(15, "小王"));
        set.add(new Student(15, "小王"));
        set.add(new Student(15, "小王"));
        //增强for遍历
        for (Student s : set) {
            System.out.println(s);
        }
    }

    //自定义类
    public static class Student {
        int age;
        String name;

        public Student(int age, String name) {
            super();
            this.age = age;
            this.name = name;
        }

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

        //使用的是HashSet实现类,由 hashCode 和 equals 保证唯一
        // 如果是自定义类,作为元素要重写 hashCode 和 equals 方法
        @Override
        public int hashCode() {
            //调用工具类,获取当前对象的hashcode
            return Objects.hash(name, age);
        }

        @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)
                    //比较的对象名字是null,而被比较的不是,则不是同一个对象
                    return false;
            } else if (!name.equals(other.name))
                //name不相同,不是同一个对象
                return false;

            return true;
        }
    }
}

运行结果:

img03.jpg

三、集合 LinkedHashSet

6.3.1 LinkedHashSet类概述和特点

我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢? 在HashSet下面有一个子类 java.util.LinkedHashSet ,它是链表和哈希表组合的一个数据存储结构,由链表保证元素有序,由哈希表保证元素唯一。

1. 存储字符串并遍历

public class Test {
    public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet<String>();
        //给集合  添加元素
        set.add("Hello1");
        set.add("Hello1");
        set.add("Hello2");
        set.add("Hello3");
        set.add("Hello4");
        //增强for   遍历
        for (String string : set) {
            /*
             *  Hello1
                Hello2
                Hello3
                Hello4
                去掉重复的
                存取有序
             */
            System.out.println(string);
        }
    }
}

运行效果:

img04.jpg

2. 存储自定义对象并遍历

public class Test {
    public static void main(String[] args) {
        LinkedHashSet<Student> set = new LinkedHashSet<Student>();
        //给集合  添加元素
        set.add(new Student(15, "小李"));
        set.add(new Student(15, "小王"));
        set.add(new Student(16, "小王"));
        set.add(new Student(15, "小王"));
        set.add(new Student(15, "小王"));
        //增强for遍历
        for (Student s : set) {
            System.out.println(s);
        }
    }

    //自定义类
    public static class Student {
        int age;
        String name;

        public Student(int age, String name) {
            super();
            this.age = age;
            this.name = name;
        }

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

        //使用的是HashSet实现类,由 hashCode 和 equals 保证唯一
        //如果是自定义类,作为元素要重写 hashCode 和 equals 方法
        @Override
        public int hashCode() {
            //调用工具类,获取当前对象的hashcode
            return Objects.hash(name, age);
        }

        @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)
                    //比较的对象名字是null,而被比较的不是,则不是同一个对象
                    return false;
            } else if (!name.equals(other.name))
                //name不相同,不是同一个对象
                return false;

            return true;
        }
    }
}

运行效果:

img05.jpg

四、集合 TreeSet

1. TreeSet类概述和特点

TreeSet是一个有序的集合,它的作用是提供有序的Set集合。它继承了AbstractSet抽象类,实现了NavigableSet<E>,Cloneable,Serializable接口。TreeSet是基于TreeMap实现的,TreeSet的元素支持2种排序方式:自然排序或者根据提供的Comparator进行排序。

特点如下:
​ ①元素唯一且有序;
​ ②支持2种排序方式:自然排序或者根据提供的Comparator进行排序。
​ ③底层数据结构是红黑树(红黑树是一种自平衡的二叉树)

2. TreeSet是如何保证元素的排序和唯一性的

TreeSet底层是通过TreeMap实现的。它把Map中的Key作为一类元素归结起来,而Map的Key是唯一的。Map我们后面会学习到。


img06.png

3. TreeSet的自然排序和比较器排序

1. 自然排序

a.定义一个类(示例是Student)实现Comparable接口,不实现会报异常 java.lang.ClassCastException: com.xts.im.test.Test$Student cannot be cast to java.lang.Comparable
b.重写Comparable接口中的compareTo()方法;
c.在compareTo()中按指定属性进行排序(示例中先按年龄,如果年龄一样按姓名排序)

示例代码如下:

public class Test {
    public static void main(String[] args) {
        TreeSet<Student> set = new TreeSet<>();
        //给集合  添加元素
        set.add(new Student(100, "小张"));
        set.add(new Student(15, "小王"));
        set.add(new Student(15, "小李"));
        set.add(new Student(15, "小王"));
        set.add(new Student(16, "小王"));
        //增强for遍历
        for (Student s : set) {
            System.out.println(s);
        }
    }

    //自定义类,实现了Comparable接口
    public static class Student implements Comparable{
        int age;
        String name;

        public Student(int age, String name) {
            super();
            this.age = age;
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

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

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


        @Override
        public int compareTo(@NonNull Object obj) {
            Student student = (Student) obj;
            //1.优先以年龄排序
            //2.年龄一样使用name排序

            //先比较年龄
            int a = this.age - student.getAge();
            //System.out.println(this.age);
            //System.out.println(student.getAge());
            //System.out.println(a);

            //次要排序条件name
            //如果年龄一样就比较姓名
            if(a==0){
                a = this.name.compareTo(student.getName());
            }
            // 根据a是 负整数、零或正整数,此对象是小于、等于还是大于指定对象
            //a是0:当前对象和obj一样,不存储
            //a是正整数:当前对象比obj大,在二叉树中,obj排在当前对象的左边节点
            //a是负整数:当前对象比obj小,在二叉树中,obj排在当前对象的右边节点
            return a;
        }
    }
}

2. 比较器排序

a.创建一个比较器类,实现Comparator接口,重写compare()方法;
b.在compare()中按指定属性进行排序(示例中先按年龄,如果年龄一样按姓名排序)
c.创建TreeSet的时候,传入比较器对象;

示例代码如下:

public class Test {
    public static void main(String[] args) {
        //创建TreeSet的时候传入自定义的比较器
        TreeSet<Student> set = new TreeSet<>(new MyComparator());
        //给集合  添加元素
        set.add(new Student(100, "小张"));
        set.add(new Student(15, "小王"));
        set.add(new Student(15, "小李"));
        set.add(new Student(15, "小王"));
        set.add(new Student(16, "小王"));
        //增强for遍历
        for (Student s : set) {
            System.out.println(s);
        }
    }
    
    //自定义的比较器类
    public static class MyComparator implements Comparator<Student>{

        @Override
        public int compare(Student o1, Student o2) {

            //1.优先以年龄排序
            //2.年龄一样使用name排序

            //先比较年龄
            int a = o1.age - o2.getAge();
            //System.out.println(this.age);
            //System.out.println(student.getAge());
            //System.out.println(a);

            //次要排序条件name
            //如果年龄一样就比较姓名
            if(a==0){
                a = o1.name.compareTo(o2.getName());
            }
            // 根据a是 负整数、零或正整数,此对象是小于、等于还是大于指定对象
            //a是0:o1和o2一样,不存储
            //a是正整数:o1比o2大,在二叉树中,o2排在o1的左边节点
            //a是负整数:o1比o2小,在二叉树中,o2排在o1的右边节点
            return a;
        }
    }

    //自定义类
    public static class Student{
        int age;
        String name;

        public Student(int age, String name) {
            super();
            this.age = age;
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

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

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

3. 两种方式的区别

a.TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)
b.TreeSet如果传入Comparator, 就优先按照Comparator

五、增强for概述及使用

1. 增强for概述,原因

增强for又叫foreach,是一种加强型的for循环操作,主要可以用于简化数组或者集合数据的输出操作。普通for循环需要控制索引,容易出现索引越界的问题(IndexOutOfBoundsException)。

2. 增强for的格式,好处,注意事项

1. 增强for循环格式如下:

for(数据类型 变量:数组|集合){
  //每次循环都会自动将数组或者集合中的内容依次取出,设置给变量,可以避免索引问题
}

示例代码:

public class Test {
    public static void main(String[] args) {
        //数组
        int[] arr = {1, 4, 62, 431, 2};
        //增强for遍历输出
        for (int value :arr) {
            System.out.println(value);
        }
    }
}

2. foreach好处

a.简化数组或者集合
b.出现索引越界的问题

3. 使用增强for注意事项

a.在使用增强型for循环不支持遍历时删除元素(会报异常:ConcurrentModificationException)
b.使用增强型for循环时,对遍历的集合需要做null判断,不然可能引发空指针异常。

看如下案例:

public class Test {
    public static void main(String[] args) {
        //list集合
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        //增强for遍历输出
        for (int value :list) {
            System.out.println(value);
            //移除元素
            list.remove(value);
        }
    }
}

六、 了解静态导入概述及使用

1. 静态导入概述,注意事项

​如果某个类中定义的方法全部都是static类型的,那么其他类要引用此类时必须先使用import导入所需要的包,再使用“类名称.方法()”进行调用

示例如下:

package com.xts.im.util;
public class MyMath {
    //相加
    public static int add(int a,int b){
        return a+b;
    }

    //相除
    public static int div(int a,int b){
        return a/b;
    }
}
package com.xts.im.test;
//导包
import com.xts.im.util.MyMath;
public class Test {
    public static void main(String[] args) {
        int a = 10;
        int b = 2;
        //类名称.方法()调用
        System.out.println("a+b = "+ MyMath.add(a,b));
        System.out.println("a/b = "+ MyMath.div(a,b));
    }
}

当前程序MyMath两个方法都使用了static定义,而后在不同包中的类实现调用,如果在调用的时候不希望出现类名称,即直接在主方法中可以直接调用不同包中的static方法,那么就可以使用静态导入的操作完成,语法格式如下:

import static 包路径.类名称.*(*是通配符)

示例如下:

package com.xts.im.test;
//静态导入
import static com.xts.im.util.MyMath.*;

public class Test {
    public static void main(String[] args) {
        int a = 10;
        int b = 2;
        //使用的时候就不用再加 类名称. 了
        System.out.println("a+b = "+ add(a,b));
        System.out.println("a/b = "+div(a,b));
    }
}

七、了解可变参数概述及使用

1. 可变参数概述,格式

在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成

如下格式:

修饰符 返回值类型 方法名(参数类型... 形参名){ }

其实这个书写完全等价于:

修饰符 返回值类型 方法名(参数类型[] 形参名){ }

​只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。 JDK1.5以后。出现了简化操作。... 用在参数上,称之为可变参数。出现了简化操作。... 用在参数上,称之为可变参数。

同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素 作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。

示例代码:

public class Test {
    public static void main(String[] args) {
        //数组的写法
        int[] arr = {1, 4, 62, 431, 2};
        int sum = getSum1(arr);
        System.out.println("数组求和:"+sum);
        //可变参数,不用再写数组了
        int sum2 = getSum2(6, 7, 2, 12, 2121);
        System.out.println("可变参数求和:"+sum2);
    }

    //数组写法,所有元素的求和
    public static int getSum1(int[] arr) {
        int sum = 0;
        for (int a : arr) {
            sum += a;
        }
        return sum;
    }

    //可变参数写法,所有元素的求和
    public static int getSum2(int... arr) {
        int sum = 0;
        for (int a : arr) {
            sum += a;
        }
        return sum;
    }
}

*注意:如果在方法书写时,这个方法拥有多参数,参数中包含可变参数,可变参数一定要写在参数列表的末尾位置。

八、Arrays工具类的使用

Arrays类位于 java.util 包中,主要包含了操纵数组的各种方法。

1. public static void sort(byte[] a) :对数组按照升序排序

示例代码:

public class Test {
    public static void main(String[] args) {
        int[] nums = {2,5,0,4,6,-10};
        //按照升序排序
        Arrays.sort(nums);
        for(int i :nums)
            System.out.print(i+" ");
        /* 之前:2 5 0 4 6 -10
         * 结果:-10 0 2 4 5 6
         */
    }
}

2. public static void fill(byte[] a, byte val) :可以为数组元素填充相同的值

示例代码:

public class Test {
    public static void main(String[] args) {
        int[] nums = {2,5,0,4,1,-10};
        Arrays.fill(nums, 1);
        for(int i :nums)
            System.out.print(i+" ");
        /* 之前:2 5 0 4 1 -10
         * 结果:1 1 1 1 1 1
         */
    }
}

3. public static String toString(byte[] a):返回数组的字符串形式

示例代码:

public class Test {
    public static void main(String[] args) {
        int[] nums = {2,5,0,4,1,-10};
        System.out.println(Arrays.toString(nums));
        /*
         * 结果:[2, 5, 0, 4, 1, -10]
         */
    }
}

4. public static int binarySearch(byte[] a, byte key):二分法查找给定数据在数组中的索引,要求数组有序

找到的情况下:
    如果key在数组中,则返回搜索值的索引。
找不到的情况下:
    a.搜索值在数组范围内,从1开始计数,得“ - 插入点位置”;
    b.搜索值大于数组内元素,索引值为 – (length + 1);
    c.搜索值小于数组内元素,索引值为 – 1。

示例代码:

public class Test {
    public static void main(String[] args) {
        int a[] = new int[] {1, 3, 4, 6, 8, 9};
        //x1 = -4: 5不在数组中,应该插入在4和6中间,位置是从1开始,所以是4,前面加-
        int x1 = Arrays.binarySearch(a, 5);
        //x2 = 2,找到了,返回索引
        int x2 = Arrays.binarySearch(a, 4);
        //x3 = -1,没有找到,且比最小的1还小,返回-1
        int x3 = Arrays.binarySearch(a, 0);
        //x4 = -7, 10不在数组中,比最大的9还大,位置是从1开始,所以是7,前面加-
        int x4 = Arrays.binarySearch(a, 10);
        //x1 = -2: 2不在数组中,应该插入在1和3中间,位置是从1开始,所以是2,前面加-
        int x5 = Arrays.binarySearch(a, 2);
        System.out.println("x1="+x1);
        System.out.println("x2="+x2);
        System.out.println("x3="+x3);
        System.out.println("x4="+x4);
        System.out.println("x5="+x5);
    }
}

5. 特殊方法asList方法使用

//将数组/可变参数转换为List
public static <T> List<T> asList(T... a)

示例代码:

public class Test {
    public static void main(String[] args) {
        Integer[] a = new Integer[] {1, 3, 4, 6, 8, 9};
        List<Integer> list = Arrays.asList(a);
        System.out.println(list);
        
        String[] str = new String[] {"a","b","c"};
        List<String> strList = Arrays.asList(str);
        System.out.println(strList);
    }
}

*注意事项

a.该方法适用于对象型数据的数组(String、Integer...)
b.该方法不建议使用于基本数据类型的数组(byte,short,int,long,float,double,boolean)
c.该方法将数组与List列表链接起来:当更新其一个时,另一个自动更新
d.不支持add()、remove()、clear()等方法

九、Collections

1. sort()

2. max()

3. min()

4. reverse()

5. shuffle()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353