JAVA笔记:集合及其数据结构与泛型

笔记:集合及其数据结构与泛型

  • 一、Collection集合
    • 1.1 集合概述
    • 1.2 集合框架(单列)
    • 1.3 Collection 常用功能
  • 二、Iterator迭代器
    • 2.1 Iterator接口
    • 2.2 迭代器使用步骤
    • 2.3 样例
    • 2.4 迭代器实现原理
    • 2.5 增强for
  • 三、泛型
    • 3.1 泛型的概念
    • 3.2 泛型的好处
    • 3.3 泛型的定义与使用
    • 3.4 含有泛型的方法
    • 3.5 含有泛型的接口
    • 3.7 通配符的高级使用
  • 四、数据结构常识(集合相关)
    • 4.1 栈
    • 4.2 队列
    • 4.3 数组
    • 4.4 链表
    • 4.5 红黑树
  • 五、List接口
    • 5.1 List接口概述
    • 5.2 List接口中常用方法
    • 5.3 List的子类
      • 5.3.1 ArrayList集合
      • 5.3.2 LinkedList集合
      • 5.3.3 补充(Vector集合)
  • 六、Set接口
    • 6.1 Set接口概述
    • 6.2 HashSet集合
      • 6.2.1 HashSet概述
      • 6.2.2 哈希值
      • 6.2.3 HashSet集合存储数据的结构(哈希表)
      • 6.2.4 Set集合不允许有重复元素的原理
      • 6.2.5 HashSet存储自定义类型元素
      • 6.2.6 LinkedHashSet集合
      • 6.2.7 可变参数
    • 6.3 TreeSet
  • 七、Collections工具类
    • 7.1 常用功能
    • 7.2 自然排序Comparable接口
    • 7.3 比较器排序Comparator
  • 八、Map集合
    • 8.1 Map概述
    • 8.2 Map常用子类
    • 8.3 Map接口常用方法
    • 8.4 Map集合遍历键找值方式
    • 8.5 Entry键值对对象(遍历方式二)
    • 8.6 HashMap存储自定义类型键值
    • 8.7 LinkedHashMap
    • 8.8 Hashtable

一、Collection集合

1.1 集合概述

  集合:集合是java中提供的一种容器,可以用来存储多个数据。
集合和数组既然都是容器,区别为:
  数组的长度是固定的。集合的长度是可变的。
  数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。

1.2 集合框架(单列)

JAVA笔记:集合及其数据结构与泛型_第1张图片

  Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是 java.util.List 和java.util.Set 。其中, List 的特点是元素有序、元素可重复。Set 的特点是元素无序,而且不可重复。List 接口的主要实现类有java.util.ArrayList 和java.util.LinkedList , Set 接口的主要实现类有 java.util.HashSet 和 java.util.TreeSet 。

1.3 Collection 常用功能

  Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

public boolean add(E e) : 把给定的对象添加到当前集合中 。
public void clear() :清空集合中所有的元素。
public boolean remove(E e) : 把给定的对象在当前集合中删除。
public boolean contains(E e) : 判断当前集合中是否包含给定的对象。
public boolean isEmpty() : 判断当前集合是否为空。
public int size() : 返回集合中元素的个数。
public Object[] toArray() : 把集合中的元素,存储到数组中。

二、Iterator迭代器

2.1 Iterator接口

  在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口 java.util.Iterator 。Iterator 接口也是Java集合中的一员,但它与Collection 、Map 接口有所不同,Collection 接口与Map 接口主要用于存储元素,而Iterator 主要用于迭代访问(即遍历) Collection 中的元素,因此Iterator 对象也被称为迭代器。
  想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作:

public Iterator iterator();//获取集合对应的迭代器,用来遍历集合中的元素的。

  迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

  Iterator接口的常用方法如下:

public E next() :返回迭代的下一个元素。
public boolean hasNext() ://如果仍有元素可以迭代,则返回 true

2.2 迭代器使用步骤

  1 使用集合中的方法iterator获取迭代器的实现类对象,然后使用Iterator接口接收(多态)
注意: Iterator接口也有泛型,类型取决于集合的泛型类型
  2 使用Iterator接口中的方法hasNext判断还有没有下一个元素
  3 使用Iterator接口中的方法next取出集合中的下一个元素

2.3 样例

public class IteratorDemo {
    public static void main(String[] args) {
        // 使用多态方式 创建对象
        Collection<String> coll = new ArrayList<String>();  // 等号右侧类型可省略不写,即<>
        // 添加元素到集合
        coll.add("串串星人");
        coll.add("吐槽星人");
        coll.add("汪星人");
        //遍历
        //使用迭代器 遍历 每个集合对象都有自己的迭代器
        Iterator<String> it = coll.iterator();
        // 泛型指的是 迭代出 元素的数据类型
        while(it.hasNext()){ //判断是否有迭代元素
        String s = it.next();//获取迭代出的元素
        System.out.println(s);
    	}
	}
}

  注意:因为迭代器取出集合中元素是一个重复过程,所以可以使用循环,又由于未知集合元素个数,所以选择while循环,循环结束的条件为hasNext方法返回False。
  建议使用while循环,但是不代表不能使用for循环,可用以下方法使用for循环:

for(Iterator<String> it2 = coll.iterator() ; it2.hasNext() ; ){  
    //第三个语句省略不写,因为语句中有取出操作,每取出一个就少一个元素
    String e = it2.next();  //取出元素
    System.out.println(e);  //打印
}

  tips::在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生 java.util.NoSuchElementException 没有集合元素的异常。

2.4 迭代器实现原理

  1、当创建完成指向某个集合或者容器的Iterator对象时,这时的指针其实指向的是第一个元素的上方,即指向一个 空

  2、当调用hasNext方法的时候,只是判断下一个元素的有无,并不移动指针

  3、当调用next方法的时候,向下移动指针,并且返回指针指向的元素,如果指针指向的内存中没有元素,会报异 常。

  4、remove方法删除的元素是指针指向的元素。如果当前指针指向的内存中没有元素,那么会抛出异常。

​   更正:实际上调用next方法前,指针指向元素的前方,但是并不是其索引值-1的位置,而是这两个位置中间,在执行next方法后,该指针经过第一个元素指向第一和第二两个元素之间,并将第一个元素取出。

2.5 增强for

  增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
格式:

for(元素的数据类型 变量 : Collection集合or数组){
	//写操作代码
}

  它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。

  补充:Collection extends Iterable: 即Collection 继承了Iterable接口。意味着所有单列集合都可以使用增强for
​ 其中该接口描述为:
  public interface Iterable :实现此接口的对象允许成为“foreach”的目标。

例1:

public class NBForDemo1 {
    public static void main(String[] args) {
        int[] arr = {3,5,6,87};
        //使用增强for遍历数组
        for(int a : arr){//a代表数组中的每个元素
        	System.out.println(a);
        }
    }
}
例2:
public class NBFor {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<String>();
        coll.add("小河神");
        coll.add("老河神");
        coll.add("神婆");
        //使用增强for遍历
        for(String s :coll){//接收变量s代表 代表被遍历到的集合元素
            System.out.println(s);
        }
    }
}

三、泛型

3.1 泛型的概念

JAVA笔记:集合及其数据结构与泛型_第2张图片

3.2 泛型的好处

1、不使用泛型的好处是,默认类型为Object类,可存储任意类型的数据。弊端是不安全,会引发异常。
2、使用泛型的好处:
(1)避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
(2)把运行期异常(代码运行之后会抛出的异常)提升到了编译期(写代码时会报错)

3.3 泛型的定义与使用

定义一个含有泛型的类的定义格式:
  修饰符 class 类名<代表泛型的变量> { }

如:

public class GenericClass<E>{
    private E name;
    public E getName() {
        return name;
    }
    public void setName(E name) {
        this.name = name;
    }
}

  注意,在使用这个类时,若不写<数据类型>,则默认为Object类型。即每次使用这个类创建对象时都可以指定这个对象的类型。

3.4 含有泛型的方法

  定义一个含有泛型的方法的格式:(泛型定义在方法的修饰符和返回类型之间)

格式:
  修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ } //泛型的符号可随意定义
例1:

public class GenericMethod {
    //定义一个含有泛型的方法
    public <M> void method01(M m) {
        System.out.println(m);
    }
    //定义一个含有泛型的静态方法
    public static <S> void method02(S s) {
        System.out.println(s);
    }
}

3.5 含有泛型的接口

定义一个含有泛型的接口的格式:
  修饰符 interface接口名<代表泛型的变量> { }
例1:

public interface GenericInterface<I> {
    public abstract void method(I i);
}
//第一种实现方式:定义接口实现类,实现接口,指定接口的类型
//定义接口实现类
public class GenericInterfaceImpl1 implements GenericInterface<String> {
    @Override
    public void method(String s) {
        System.out.println(s);
    }
}
//第二种实现方式:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走。即相当于定义了一个含有泛型的类,创建对象的时候指定泛型的类型
//定义接口实现类
public class GenericInterfaceImpl2 implements GenericInterface<I> {
    @Override
    public void method(I i) {
        System.out.println(i);
    }
}

3.6 泛型通配符
  当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

  ?:代表任意的数据类型。使用方式:不能创建对象使用,只能作为方法的参数使用

public static void main(String[] args) {
    Collection<Intger> list1 = new ArrayList<Integer>();
    getElement(list1);
    Collection<String> list2 = new ArrayList<String>();
    getElement(list2);
}
public static void getElement(Collection<?> coll) { }
//?代表可以接收任意类型

例1:

package com.itheima.demo03.Generic;

import java.util.ArrayList;
import java.util.Iterator;

/*
泛型的通配符:
?:代表任意的数据类型
使用方式:
不能创建对象使用
只能作为方法的参数使用
*/
public class Demo05Generic {
    public static void main(String[] args) {
        ArrayList<Integer> list01 = new ArrayList<>();
        list01.add(1);
        list01.add(2);

        ArrayList<String> list02 = new ArrayList<>();
        list02.add("a");
        list02.add("b");

        printArray(list01);
        printArray(list02);

//ArrayList list03 = new ArrayList();
    }

/*
定义一个方法,能遍历所有类型的ArrayList集合
这时候我们不知道ArrayList集合使用什么数据类型,可以泛型的通配符?来接收数据类型
注意:
泛型没有继承概念的
*/
    public static void printArray(ArrayList<?> list){
//使用迭代器遍历集合
        Iterator<?> it = list.iterator();
        while(it.hasNext()){
//it.next()方法,取出的元素是Object,可以接收任意的数据类型
            Object o = it.next();
            System.out.println(o);
        }
    }
}
//运行结果为 1 2 a b

  我的疑问1:在最后的静态方法中,去掉描述代码运行结果与上述代码一致,即

public static void printArray(ArrayList list){
    Iterator it = list.iterator();
    while(it.hasNext()){
        Object o = it.next();
        System.out.println(o);
    }
}
//运行结果为 1 2 a b

  这个案例中,如果用代替将会发生编译错误,理由是泛型并没有继承的概念。但是如果干脆不使用泛型,在每一个集合都确定了各自的类型(数组储存的元素都是相同类型)的情况下,与使用泛型的代码运行结果一致,那在这个案例中使用泛型有什么优势吗,还是说根本没必要使用泛型?

3.7 通配符的高级使用

  之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。
  泛型的上限:
格式: 类型名称 对象名称
意义: 只能接收该类型及其子类
  泛型的下限:
格式: 类型名称 对象名称
意义: 只能接收该类型及其父类型
  例子:

public static void main(String[] args) {
    Collection<Integer> list1 = new ArrayList<Integer>();
    Collection<String> list2 = new ArrayList<String>();
    Collection<Number> list3 = new ArrayList<Number>();
    Collection<Object> list4 = new ArrayList<Object>();
    getElement(list1);
    getElement(list2);//报错
    getElement(list3);
    getElement(list4);//报错
    getElement2(list1);//报错
    getElement2(list2);//报错
    getElement2(list3);
    getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}

四、数据结构常识(集合相关)

4.1 栈

  栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
  栈的特点:先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。
  压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
  弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
JAVA笔记:集合及其数据结构与泛型_第3张图片

4.2 队列

  队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。
  队列的特点:先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)
  队列的入口、出口各占一侧。
JAVA笔记:集合及其数据结构与泛型_第4张图片

4.3 数组

  数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。
  数组的特点:查找元素快:通过索引,可以快速访问指定位置的元素。增删元素慢:指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置
JAVA笔记:集合及其数据结构与泛型_第5张图片

4.4 链表

  链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表。
  链表的特点:多个结点之间,通过地址进行连接。
  查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
  增删元素快:增加元素:只需要修改连接下个元素的地址即可。删除元素:只需要修改连接下个元素的地址即可。
JAVA笔记:集合及其数据结构与泛型_第6张图片
关于链表概述
关于链表的有序性
(虽然看了很多文章,还是不懂为什么单链是无序的,咋看都是有序的,一堆人,后一个人搭钱一个人肩膀站着,少一个人多一个人,难道大家所有人还要打乱顺序站吗……)

4.5 红黑树

  二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。
  二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。
  红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
  红黑树的约束:
1. 节点可以是红色的或者黑色的
2. 根节点是黑色的
3. 叶子节点(特指空节点)是黑色的
4. 每个红色节点的子节点都是黑色的
5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
  红黑树的特点: 速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍

五、List接口

5.1 List接口概述

  java.util.List 接口继承自Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了List 接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
  List接口特点:
1. 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
2. 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
3. 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。

5.2 List接口中常用方法

  List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下(增删改查):

public void add(int index, E element);//将指定的元素,添加到该集合中的指定位置上。
public E get(int index);//返回集合中指定位置的元素。
public E remove(int index);//移除列表中指定位置的元素, 返回的是被移除的元素。(有点剪切的意思)
public E set(int index, E element);//用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

5.3 List的子类

5.3.1 ArrayList集合

  java.util.ArrayList 集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList 是最常用的集合。
  注意:此实现不是同步的(多线程)
  适合需要经常进行查询操作但不经常进行增删操作的需求解决。增删效率低下的一大原因就是在操作的最底层进行了数组创建、复制等操作。

5.3.2 LinkedList集合

  java.util.LinkedList 集合数据存储的结构是链表结构。方便元素添加、删除的集合。
  实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法作为了解即可:

public void addFirst(E e) :将指定元素插入此列表的开头。
public void addLast(E e) :将指定元素添加到此列表的结尾。
public E getFirst() :返回此列表的第一个元素。
public E getLast() :返回此列表的最后一个元素。
public E removeFirst() :移除并返回此列表的第一个元素。
public E removeLast() :移除并返回此列表的最后一个元素。
public E pop() :从此列表所表示的堆栈处弹出一个元素。
public void push(E e) :将元素推入此列表所表示的堆栈。
public boolean isEmpty() :如果列表不包含元素,则返回true

  LinkedList是List的子类,List中的方法LinkedList都是可以使用,只需要再了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。(了解即可)

  注意:此实现是不同步的(多线程)

5.3.3 补充(Vector集合)

  上述提到的集合均为JAVA1.2版本之后的集合,而在JDK1.0版本中,提供的单列集合为Vector,是其他单列集合的祖宗。底层是Array,但是是单线程的,所以在1.2版本中被ArrayList取代。此实现是同步的。

六、Set接口

6.1 Set接口概述

  java.util.Set 接口和java.util.List 接口一样,同样继承自Collection 接口,它与Collection 接口中的方法基本一致,并没有对Collection 接口进行功能上的扩充,只是比Collection 接口更加严格了。与List 接口不同的是, Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
  特点:不允许存储重复的元素。
    没有索引,没有带索引的方法,也不能使用普通的for循环遍历。

6.2 HashSet集合

6.2.1 HashSet概述

  java.util.HashSet 是Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。java.util.HashSet 底层的实现其实是一个java.util.HashMap 支持。
  HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode 与equals 方法。(底层是一个哈希表结构,其特点是查询速度特别快)

6.2.2 哈希值

  哈希值是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来的地址,不是数据实际存储的物理地址,在Object类中有一个方法可以获取对象的哈希值)

  int hashCode() : 返回该对象的哈希码值
  hashCode的方法源码: public native int hashCode(); // 其中native代表该方法调用的是本地操作系统的方法。

  注意:对于哈希值,不能错误的认为不同对象的哈希值就是不同的,hashCode方法是可以被重写的,比如重写hashCode使其返回数字1,则所有对象的hashCode都会是1,此时用“==”将两个对象进行比较,返回的是False。再比如String类,就重写了hashCode方法,举个例子,“重地”.hashCode()的值与“通话”.hashCode()的值相等,都为1179395。但两个对象明显不可能存储在同一个物理地址中。

6.2.3 HashSet集合存储数据的结构(哈希表)

  在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
JAVA笔记:集合及其数据结构与泛型_第7张图片
JAVA笔记:集合及其数据结构与泛型_第8张图片
  JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

6.2.4 Set集合不允许有重复元素的原理

JAVA笔记:集合及其数据结构与泛型_第9张图片

6.2.5 HashSet存储自定义类型元素

  给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。
  例:

import java.util.Objects;

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        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 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;
    }
}
import java.util.HashSet;
/**
HashSet存储自定义类型元素

set集合报错元素唯一:
存储的元素(String,Integer,...Student,Person...),必须重写hashCode方法和equals方法

要求:
同名同年龄的人,视为同一个人,只能存储一次
*/
public class Demo03HashSetSavePerson {
    public static void main(String[] args) {
        //创建HashSet集合存储Person
        HashSet<Person> set = new HashSet<>();
        Person p1 = new Person("欣欣",18);
        Person p2 = new Person("欣欣",18);
        Person p3 = new Person("欣欣",19);
        System.out.println(p1.hashCode());//1967205423
        System.out.println(p2.hashCode());//42121758

        System.out.println(p1==p2);//false
        System.out.println(p1.equals(p2));//false
        set.add(p1);
        set.add(p2);
        set.add(p3);
        System.out.println(set);
    }
}

6.2.6 LinkedHashSet集合

  HashSet保证元素唯一,可是元素存放进去是没有顺序的。在HashSet下面有一个子类java.util.LinkedHashSet ,它是链表和哈希表组合的一个数据存储结构。
  LinkedHashSet是HashSet的子类,根据API文档:具有可预知迭代顺序的Set接口的哈希表和链表列表实现。此实现与HashSet的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链表定义了迭代顺序,即按照将元素插入到set中的顺序(插入顺序)进行迭代。注意,插入顺序不受在set中重新插入的元素的影响。(如果在调用s.contains(e)返回true后立即调用s.add(e),则元素e会被重新插入到set s中)此实现可以让客户免遭未指定的、由HashSet提供的通常杂乱无章的排序工作,而又不致引起与TreeSet关联的成本增加。使用它可以生成一个与原来顺序相同的set副本,并且与原set的实现无关。

6.2.7 可变参数

  在JDK1.5之后,如果我们定义一个方法需要接受多个数据,并且多个参数类型一致,我们可以对其简化成如下格式:
  修饰符 返回值类型 方法名(参数类型… 形参名){ }
其实这个书写完全等价与
  修饰符 返回值类型 方法名(参数类型[] 形参名){ }
  只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。JDK1.5以后。出现了简化操作。… 用在参数上,称之为可变参数。
  同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。

  注意事项:
1、一个方法的参数列表,只能有一个可变参数
2、如果方法的参数有多个数据类型,那么可变参数必须写在参数列表的末尾

  可变参数终极形态:

    //使用obj接收任意类型的参数
private static void method(Object...obj) {
}

6.3 TreeSet

【待补充】

七、Collections工具类

7.1 常用功能

java.utils.Collections 是集合工具类,用来对集合进行操作。部分方法如下:

public static <T> boolean addAll(Collection<T> c, T... elements);//往集合中添加一些元素。
public static void shuffle(List<?> list);//打乱顺序:打乱集合顺序。
public static <T> void sort(List<T> list);//将集合中元素按照默认规则排序。
public static <T> void sort(List<T> list,Comparator<? super T> );//将集合中元素按照指定规则排序。

  注意:public static void sort(List list) 这个方法完成的排序,实际上要求了被排序的类型需要实现Comparable接口完成比较的功能

7.2 自然排序Comparable接口

  自定义的类需要实现该接口,才能正常排序,其中的compareTo方法return的是正数,则调用该对象的元素放在被比较元素后方,否则在前方,假设是对数字进行比较,则return this.a - other,按升序排列(this.a大的时候,返回的为正数,把a排在了other后面),当return other - this.a时,为降序排列。
  假设在数字相同时,各元素还有字符串属性,且字符串内容不同,则不能只根据数字进行比较,否则数字相同时,return 0,将认为这两个元素是相同元素。(Set集合元素唯一,将不会把这个元素添加进去)由于字符串String类本身就重写了compareTo方法,所以可以直接调用。

  例:

public class Student implements Comparable<Student> {
    private String name;
    private int age;
    public Student() {
    }
    public Student(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 int compareTo(Student s) {
    // return 0;
    // return 1;
    // return -1;
    //按照年龄从小到大排序
    int num = this.age - s.age;
    // int num = s.age - this.age;
    //年龄相同时,按照姓名的字母顺序排序
    int num2 = num==0?this.name.compareTo(s.name):num;
    return num2;
    }
}
public class TreeSetDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Student> ts = new TreeSet<Student>();
        //创建学生对象
        Student s1 = new Student("xishi", 29);
        Student s2 = new Student("wangzhaojun", 28);
        Student s3 = new Student("diaochan", 30);
        Student s4 = new Student("yangyuhuan", 33);
        Student s5 = new Student("linqingxia",33);
        Student s6 = new Student("linqingxia",33);
        //把学生添加到集合
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        ts.add(s6);
        //遍历集合
        for (Student s : ts) {
            System.out.println(s.getName() + "," + s.getAge());
        }
    }
}

7.3 比较器排序Comparator

  Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则

  Comparator:强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
  相当于找一个第三方的裁判来比较两个元素。
  Comparator排序规则 o1-o2(升序)(反之降序)
例:

public class Student {
    private String name;
    private int age;

    public Student() {
    }

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

    @Override
    public String toString() {
        return "Student{" +
        "name='" + name + '\'' +
        ", 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;
    }
}
public class Demo03Sort {
    public static void main(String[] args) {
        ArrayList<Integer> list01 = new ArrayList<>();
        list01.add(1);
        list01.add(3);
        list01.add(2);
        System.out.println(list01);//[1, 3, 2]

        Collections.sort(list01, new Comparator<Integer>() {
            //重写比较的规则
            @Override
            public int compare(Integer o1, Integer o2) {
                //return o1-o2;//升序
                return o2-o1;//降序
            }
        });

        System.out.println(list01);

        ArrayList<Student> list02 = new ArrayList<>();
        list02.add(new Student("a迪丽热巴",18));
        list02.add(new Student("古力娜扎",20));
        list02.add(new Student("杨幂",17));
        list02.add(new Student("b杨幂",18));
        System.out.println(list02);

/*Collections.sort(list02, new Comparator() {
@Override
public int compare(Student o1, Student o2) {
//按照年龄升序排序
return o1.getAge()-o2.getAge();
}
});*/

//扩展:了解
        Collections.sort(list02, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                //按照年龄升序排序
                int result = o1.getAge()-o2.getAge();
                //如果两个人年龄相同,再使用姓名的第一个字比较
                if(result==0){
                    result = o1.getName().charAt(0)-o2.getName().charAt(0);
                }
                return result;
            }
        });

        System.out.println(list02);
    }
}
//其中对姓名排序可以用字符串重写的compareTo方法

八、Map集合

8.1 Map概述

  现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map 接口。
  我们通过查看Map 接口描述,发现Map 接口下的集合与Collection 接口下的集合,它们存储数据的形式不同,如下图。
JAVA笔记:集合及其数据结构与泛型_第10张图片
  java.util.Map集合即Map集合的特点:
  1.Map集合是一个双列集合,一个元素包含两个值(一个key一个value)
  2.Map集合中的元素,key和value的数据类型可以相同也可以不同
  3.Map集合中的元素,key是不允许重复的,value可以重复
  4.Map集合中的元素,key和value一 一对应

8.2 Map常用子类

  java,util.HashMap集合 implement Map接口,特点:
1.HashMap集合底层是哈希表:查询的速度特别快,JDK1.8之前为数组+单向链表,JDK1.8之后为数组+单向链表/红黑树(链表长度超过八)提高查询速度
2.HashMap是一个无序集合,存储元素和取出元素的顺序有可能不一致。
  java.util.LinkedHashMap集合 extends HashMap集合(HashMap集合的子类),特点:
1.LinkedHashMap集合底层为哈希表+链表(保证迭代顺序)
2.LinkedHashMap集合是一个有序集合,存储元素和取出元素的顺序是一致的

8.3 Map接口常用方法

  Map接口中定义了很多方法,常用的如下:

public V put(K key, V value);//把指定的键与指定的值添加到Map集合中。(若key已存在,则将新元素替换旧元素并返回旧元素,否则返回null)
public V remove(Object key);//把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。若键值不存在返回null。
public V get(Object key);//根据指定的键,在Map集合中获取对应的值。若键值不存在返回null。
public Set<K> keySet();//获取Map集合中所有的键,存储到Set集合中。
public Set<Map.Entry<K,V>> entrySet();//获取到Map集合中所有的键值对对象的集合(Set集合)。
public boolean containsKey(Object key);//如果此映射包含指定键的映射关系,则返回true。
public boolean containsValue(Object value);//如果此映射将一个或多个键映射到指定值,则返回true。

8.4 Map集合遍历键找值方式

JAVA笔记:集合及其数据结构与泛型_第11张图片

步骤:
1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中
2.遍历set集合,获取Map集合中的每一个key
3.通过Map集合中的方法get(key),通过key找到value

例:

public class MapDemo01 {
    public static void main(String[] args) {
        //创建Map集合对象
        HashMap<String, String> map = new HashMap<String,String>();
        //添加元素到集合
        map.put("胡歌", "霍建华");
        map.put("郭德纲", "于谦");
        map.put("薛之谦", "大张伟");
        //获取所有的键 获取键集
        Set<String> keys = map.keySet();
        // 遍历键集 得到 每一个键
            for (String key : keys) {
            //key 就是键
            //获取对应值
            String value = map.get(key);
            System.out.println(key+"的CP是:"+value);
        }
    }
}

8.5 Entry键值对对象(遍历方式二)

  Map 中存放的是两种对象,一种称为key(键),一种称为value(值),它们在在Map 中是一一对应关系,这一对对象又称做Map 中的一个Entry(项) 。Entry 将键值对的对应关系封装成了对象。即键值对对象,在遍历Map 集合时,就可以从每一个键值对( Entry )对象中获取对应的键与对应的值。
JAVA笔记:集合及其数据结构与泛型_第12张图片
  Entry表示了一对键和值,提供了获取对应键和对应值得方法:

public K getKey() ;//获取Entry对象中的键。
public V getValue() ;//获取Entry对象中的值。

  在Map集合中提供了获取所有Entry对象的方法:
public Set> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。

例:

public class MapDemo02 {
    public static void main(String[] args) {
        // 创建Map集合对象
        HashMap<String, String> map = new HashMap<String,String>();
        // 添加元素到集合
        map.put("胡歌", "霍建华");
        map.put("郭德纲", "于谦");
        map.put("薛之谦", "大张伟");
        // 获取 所有的 entry对象 entrySet
        Set<Entry<String,String>> entrySet = map.entrySet();
        // 遍历得到每一个entry对象
        for (Entry<String, String> entry : entrySet) {
            // 解析
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+"的CP是:"+value);
        }
    }
}

tips:Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。

8.6 HashMap存储自定义类型键值

  当给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCode和equals方法。如果要保证map中存放的key和取出的顺序一致,可以使用java.util.LinkedHashMap 集合来存放。(参考HasSet)

8.7 LinkedHashMap

  Map接口的哈希表和链表实现,具有可预知的迭代顺序。是一个有序的集合。底层原理:哈希表+链表(记录元素顺序)

8.8 Hashtable

  此类也继承了Map接口,实现一个哈希表。(但是不允许存储null),是最早期的双列集合,且是同步的(单线程),是线程安全的集合,速度慢。Hashtable和Vector集合一样,在JDK1.2版本之后被更先进的集合(HashMap,ArrayList)所取代。
  Hashtable的子类Properties依然活跃在历史舞台,是唯一的和IO流相结合的集合。

九、补充

9.1 JDK9对集合添加的优化

  Java 9,添加了几种集合工厂方法,更方便创建少量元素的集合、map实例。新的List、Set、Map的静态工厂方法可以更方便地创建集合的不可变实例。
  List,Set,Map接口,增加了一个静态方法of,可以给集合一次性添加多个元素。
注意:
1:of()方法只是Map,List,Set这三个接口的静态方法,其父类接口和子类实现并没有这类方法,比如HashSet,ArrayList等;
2:返回的集合是不可变的;
3:Map和Set接口不能调用of方法存储重复元素,否则抛出异常。

9.2 Debug追踪

IDEA
F8:逐行执行程序
F7:进入到方法中
shift+F8:跳出方法
F9:跳到下一个断点,如果没有下一个断点,那么就结束程序
Ctrl+F2:退出debug模式,停止程序
Console:切换到控制台

你可能感兴趣的:(JAVA基础,java,数据结构)