今日学习内容

  • Set集合

    • HashSet

      • LinkedHashSet
    • TreeSet
  • 泛型
  • 可变参数

1 Set集合

1.1 体系

Collection

  • List

    • ArrayList
    • LinkedList
  • Set:存取无序,没有索引,不能存储重复元素

    • HashSet

      • LinkedHashSet
    • TreeSet

Set接口中没有特有方法,其中和Collection完全相同

1.2 哈希值

  • 哈希值

    ​ 是 JDK 根据对象的地址或者字符串或者数字算出来的 int 类型的数值

  • 如何获取哈希值

    ​ Object 类中的 public int hashCode():返回对象的哈希码值

  • 哈希值的特点

    • 同一个对象多次调用 hashCode() 方法返回的哈希值是相同的
    • 默认情况下,不同对象的哈希值是不同的。而重写 hashCode() 方法,可以实现让不同对象的哈希值相同。

1.3 HashSet集合

  • HashSet 集合的特点

    • 底层数据结构是哈希表
    • 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
    • 没有带索引的方法,所以不能使用普通 for 循环遍历
    • 由于是 Set 集合,所以是不包含重复元素的集合

1.4 HashSet集合保证元素唯一性源码分析

  • HashSet集合保证元素唯一性的原理

    1.根据对象的哈希值计算存储位置

    ​ 如果当前位置没有元素则直接存入

    ​ 如果当前位置有元素存在,则进入第二步

    2.当前元素的元素和已经存在的元素比较哈希值

    ​ 如果哈希值不同,则将当前元素进行存储

    ​ 如果哈希值相同,则进入第三步

    3.通过equals()方法比较两个元素的内容

    ​ 如果内容不相同,则将当前元素进行存储

    ​ 如果内容相同,则不存储当前元素

  • HashSet如果存储JDK提供的一些类型的对象(比如:String)不需要去重写hashCode和equals方法,因为JDK已经实现了,所以直接存储就能做到去重。

    如果存储一些自定义类型的对象(比如:Student)就需要去重写hashCode和equals方法,重写之后才能保证去重。

    如何重写这两个方法:快捷键 alt+insert 选择 hashCode & equals 即可

  • HashSet集合保证元素唯一性的图解

01.png

1.5 常见数据结构之哈希表

02.png

1.6 LinkedHashSet集合

  • LinkedHashSet 集合特点

    • 底层的数据结构:双向链表+哈希表(数组+链表),具有可预测的迭代次序
    • 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
    • 由哈希表保证元素唯一,也就是说没有重复的元素

2 Set集合排序

2.1 TreeSet集合概述和特点

  • TreeSet集合概述

    • 元素有序,可以按照一定的规则进行排序,具体排序方式取决于构造方法

      • TreeSet():根据其元素的自然排序进行排序
      • TreeSet(Comparator comparator) :根据指定的比较器进行排序
    • 没有带索引的方法,所以不能使用普通 for 循环遍历
    • 由于是 Set 集合,所以不包含重复元素的集合
  • 两种排序方式:

    1、自然排序

    ​ 元素所在的类要实现 Comparable 接口,重写 compareTo 方法

    重写compareTo方法

    (1)参数

    ​ this:即将添加进行排序的元素

    ​ o:集合中已经排好的元素

    ​ this - o:升序

    ​ o - this:降序

    (2)返回值

    ​ 正数:将元素放右边,返回值固定是正数,升序

    ​ 0:重复元素,去除

    ​ 负数:将元素放左边,返回值固定是负数,降序

    2、比较器排序

    ​ 在TreeSet的构造方法中传递一个Comparator的实现类对象,重写compare方法

    重写compareTo方法

    (1)参数

    ​ s1:即将添加进行排序的元素,相当于之前的this

    ​ s2:集合中已经排好的元素,相当于之前的o

    ​ s1 - s2:升序

    ​ s2 - s1:降序

    (2)返回值

    ​ 正数:将元素放右边,返回值固定是正数,升序

    ​ 0:重复元素,去除

    ​ 负数:将元素放左边,返回值固定是负数,降序

    PS:两种比较方式可以达到一模一样的效果,两种可以随意选择一个。

    而且这两种比较方式可以同时存在,它会优先选择比较器排序。

3 如何选择集合?

  • 能存储重复元素

    • 选择List集合,优先ArrayList
    • 只有当增删多的时候,可以选择LinkedList,否则都选择ArrayList
  • 不能存储重复元素

    • 选择Set集合,优先选择HashSet
    • 如果需要保证存取有序,可以选择LinkedHashSet
    • 如果需要保证元素存完之后可以排序,可以选择TreeSet

4 泛型

4.1 泛型概述和好处

  • 泛型概述

    ​ 泛型是JDK5中引入的新特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型;

    ​ 泛型是一种广泛的类型,可以它是一个变量,你想让它是什么类型它就是什么类型;

    ​ 泛型中传递的类型只能是引用数据类型,不能是基本数据类型。

    ArrayList<int> ---> ArrayList<Integer>
    ArrayList<double> ---> ArrayList<Double>
  • 泛型定义格式

    • <类型>:指定一种类型的格式。这里的类型可以看成是形参
    • <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参
    • 将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型


      <K,V>
      <K,V,...>

  • 泛型的好处

    • 把运行时期的问题提前到了编译期间
    • 避免了强制类型转换,简化了代码

4.2 泛型类

​ 在类名后面加上泛型

  • 定义格式

    修饰符 class 类名<类型> {  }
  • 示例代码

    public class Generic<T> {
        private T t;
    
        public T getT() {
            return t;
        }
    
        public void setT(T t) {
            this.t = t;
        }
    }

4.3 泛型方法

​ 在方法的声明上定义泛型

  • 定义格式

    修饰符 <类型> 返回值类型 方法名(类型 变量名) {  }
  • 示例代码

    public class Generic {
        public <T> void show(T t) {
            System.out.println(t);
        }
    }

4.4 泛型接口

  • 定义格式

    修饰符 interface 接口名<类型> {  }
  • 示例代码

    • 泛型接口

      public interface Generic<T> {
          void show(T t);
      }
    • 泛型接口实现类

      public class GenericImpl<T> implements Generic<T> {
          @Override
          public void show(T t) {
              System.out.println(t);
          }
      }

4.5 类型通配符

  • 类型通配符的作用

    ​ 为了表示各种泛型List的父类,可以使用类型通配符

  • 类型通配符的分类

    • 类型通配符:<?>

      • List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
      • 这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中
    • 类型通配符上限:<? extends 类型>

      • List<? extends Number>:它表示的类型是Number或者其子类型
    • 类型通配符下限:<? super 类型>

      • List<? super Number>:它表示的类型是Number或者其父类型

5 可变参数

  • JDK1.5的新特性(自动拆装箱、增强for、泛型、可变参数)

    • 可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了
    • 可变参数只能在方法的小括号中(在形式参数位置)使用

      格式:

      数据类型... 变量名
      
      String... strs
      int... arr
      Student... arr
      
      修饰符 返回值类型 方法名(数据类型... 变量名){
      方法体;
      }
  • 注意事项:

    1、 可变参数的本质是数组

    2、可变参数传递的实参可以是0~无穷多个

    ​ 可以传递数组,也可以将数组中的元素拿出来进行传递

    3、如果一个方法的参数包含可变参数,且参数个数有多个,

​ 这时可变参数必须位于最后一位,所以一个方法的可变参数只能有一个

  • 数组和可变参数的区别

    1、形参是数组类型,实参可能传递数组

    ​ 形参是可变参数类型,实参可以传递数组也可以单独传递数组中的元素

    2、形参是数组类型,实参不能不传递

    ​ 形参是可变参数类型,实参可以不传递(0个实参)

    3、数组位置灵活

    ​ 可变参数的位置只能在方法的小括号中,当做形参使用