Java从入门到实战总结-3.2、Java集合

Java从入门到实战总结-3.2、Java集合


文章目录

1、集合

(1)、集合概述

集合:集合是java中提供的一种容器,可以用来存储多个数据。

集合和数组既然都是容器,它们有啥区别呢?

  • 数组的长度是固定的。集合的长度是可变的。

  • 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。

(2)、类集设置的目的(重点)

对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构,所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。

在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完整的提出类集的完整概念。

类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。

所有的类集操作的接口或类都在 java.util 包中。

(3)、集合框架

JAVASE提供了满足各种需求的API,在使用这些API前,先了解其继承与接口操作架构,才能了解何时采用哪个类,以及类之间如何彼此合作,从而达到灵活应用。

集合按照其存储结构可以分为两大类,分别是单列合java.util.Collection和双列集合java.util.Map

  • Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.Listjava.util.Set 。其中, List 的特点是元素有序、元素可重复。 Set的特点是元素无序,而且不可重复。List接口的主要实现类有java.util.ArrayListjava.util.LinkedListSet接口的主要实现类有java.util.HashSetjava.util.TreeSet
  • Map:Map 提供了一个更通用的元素存储方法。Map 集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。Map包括HashMapTreeMap,而TreeMap又包括LinkedHashMap

集合本身是一个工具,它存放在java.util包中。在 Collection 接口定义着单列集合框架中最最共性的内容。

(4)、Java类集结构图(有整体认识)

在这里插入图片描述

2、链表和二叉树思路

学习任务:实现单链表的插入、删除和遍历;实现二叉树的三种便利方式。

链表比数组更加灵活,便于插入和动态扩展;二叉树相对于普通的链表更便于比对大小,便于查找,存在二分法的思维方式,存储的时候就可以按照大小存储到不同的方向节点,这样在搜索时就大大减少了搜索量。

在这里插入图片描述

(1)、链表(实现单链表)
A、什么是链表

链表 [Linked List]:链表是由一组不必相连(不必相连:可以连续也可以不连续)的内存结构(节点),按特定的顺序链接在一起的抽象数据类型。

  • 补充:

抽象数据类型(Abstract Data Type [ADT]):表示数学中抽象出来的一些操作的集合。

  • 内存结构:内存中的结构,如:struct、特殊内存块…等等之类;

数组和链表的区别和优缺点:

  • 数组是一种连续存储线性结构,元素类型相同,大小相等

数组的优点:

​ 存取速度快

数组的缺点:

​ 事先必须知道数组的长度

​ 插入删除元素很慢

​ 空间通常是有限制的

​ 需要大块连续的内存块

​ 插入删除元素的效率很低

  • 链表是离散存储线性结构

n 个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一个后续节点,首节点没有前驱节点,尾节点没有后续节点。

链表优点:

​ 空间没有限制

​ 插入删除元素很快

链表缺点:

​ 存取速度很慢

B、链表共分几类?

链表常用的有 3 类: 单链表、双向链表、循环链表。

在这里插入图片描述

链表的核心操作集有 3 种:插入、删除、查找(遍历)

单链表

单链表 [Linked List]:由各个内存结构通过一个 Next 指针链接在一起组成,每一个内存结构都存在后继内存结构(链尾除外),内存结构由数据域和 Next 指针域组成。

单链表实现图示:

在这里插入图片描述

解析:

Data 数据 + Next 指针,组成一个单链表的内存结构 ;

第一个内存结构称为 链头,最后一个内存结构称为 链尾;

链尾的 Next 指针设置为 NULL [指向空];

单链表的遍历方向单一(只能从链头一直遍历到链尾)

单链表操作集:

在这里插入图片描述

双向链表

双向链表 [Double Linked List]:由各个内存结构通过指针 Next 和指针 Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构(链头没有前驱,链尾没有后继),内存结构由数据域、Prev 指针域和 Next 指针域组成。

双向链表实现图示:

在这里插入图片描述

解析:

Data 数据 + Next 指针 + Prev 指针,组成一个双向链表的内存结构;

第一个内存结构称为 链头,最后一个内存结构称为 链尾;

链头的 Prev 指针设置为 NULL, 链尾的 Next 指针设置为 NULL;

Prev 指向的内存结构称为 前驱, Next 指向的内存结构称为 后继;

双向链表的遍历是双向的,即如果把从链头的 Next 一直到链尾的[NULL] 遍历方向定义为正向,那么从链尾的 Prev 一直到链头 [NULL ]遍历方向就是反向;

双向链表操作集:

在这里插入图片描述

循环链表

单向循环链表 [Circular Linked List] : 由各个内存结构通过一个指针 Next 链接在一起

组成,每一个内存结构都存在后继内存结构,内存结构由数据域和 Next 指针域组成。

双向循环链表 [Double Circular Linked List] : 由各个内存结构通过指针 Next 和指针

Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构,内存结构由数据域、Prev 指针域和 Next 指针域组成。

循环链表的单向与双向实现图示:

在这里插入图片描述

解析:

循环链表分为单向、双向两种;

单向的实现就是在单链表的基础上,把链尾的 Next 指针直接指向链头,形成一个闭环;

双向的实现就是在双向链表的基础上,把链尾的 Next 指针指向链头,再把链头的 Prev指针指向链尾,形成一个闭环;

循环链表没有链头和链尾的说法,因为是闭环的,所以每一个内存结构都可以充当链头和链尾;

循环链表操作集:

在这里插入图片描述

(2)、二叉树(理解概念)
A、什么是二叉树

二叉树是树的一种,每个节点最多可具有两个子树,即结点的度最大为 2(结点度:结点拥有的子树数)。

在这里插入图片描述

树的一些概念

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hrPQ1Awp-1616944317862)(/Users/yz/Library/Application Support/typora-user-images/image-20210323215019697.png)]

二叉树就是每个节点不能多于有两个儿子,上面的图就是一颗二叉树,而且还是一种特殊的二叉树:二叉查找树(binary search tree)。

定义:当前根节点的左边全部比根节点小,当前根节点的右边全部比根节点大。可以看出,这对我们来找一个数是非常方便快捷的

一棵树至少会有一个节点(根节点)

树由节点组成,每个节点的数据结构是这样的:

在这里插入图片描述

因此,我们定义树的时候往往是**->定义节点->节点连接起来就成了树**,而节点的定义就是:一个数据、两个指针(如果有节点就指向节点、没有节点就指向 null)

B、二叉树的种类
斜树

所有结点都只有左子树,或者右子树。

在这里插入图片描述

满二叉树

所有的分支节点都具有左右节点。

在这里插入图片描述

完全二叉树

若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

在这里插入图片描述

C、二叉树的一些性质

二叉树第 i 层上的结点数目最多为 2^(i-1) (i≥1)

深度为 h 的二叉树至多有 2^h-1 个结点(h≥1)

包含 n 个结点的二叉树的高度至少为 log2 (n+1)

在任意一棵二叉树中,若终端结点的个数为 n0,深度为 2 的结点数为 n2,则 n0=n2+1

D、二叉树的遍历方式

二叉树的遍历方式,一般分为先序遍历,中序遍历,后序遍历。

  • 先序遍历

o 先访问根节点,然后访问左节点,最后访问右节点(根->左->右)

  • 中序遍历

先访问左节点,然后访问根节点,最后访问右节点(左->根->右)

  • 后序遍历

o 先访问左节点,然后访问右节点,最后访问根节点(左->右->根)

实现如下结构:

在这里插入图片描述

先序遍历(根-左-右):1-2-4-8-9-5-10-3-6-7

中序遍历:(左-根-右):8-4-9-2-10-5-1-6-3-7

后序遍历(左-右-根):8-9-4-10-5-2-6-7-3-1

(3)、单链表简单实现

实现单链表的插入、删除和遍历

package com.xiaoyaoyou.day9;

/**
 * 实现简单的单链表
 */
class MyLinked {
    public static void main(String[]args){
        MyLinked list = new MyLinked();
        list.addNode(0);
        list.addNode(1);
        Node node = list.addNode(2);
        list.addNode(3);
        list.addNode(4);
        list.addNode(5);
        System.out.println("head.data:" + list.head.data);
        list.printList();
        list.delNodeByIndex(4);
        System.out.println("After deleteNode(4):");
        list.printList();
        list.delNodeByNode(node);
        System.out.println("After node 2:");
        list.printList();
    }

    //链表头节点
    Node head = null;

    /**
     * 定义链表节点
     */
    static class Node {
        Node next = null;
        int data;

        public Node(int data) {
            this.data = data;
        }
    }

    /**
     * 链表末尾追加节点
     * @param d
     */
    public Node addNode(int d) {
        Node newNode = new Node(d);
        //插入头节点
        if (head == null) {
            head = newNode;
            return head;
        }

        //找到节点末尾并追加
        Node tmp = head;
        while (tmp.next != null) {
            tmp = tmp.next;
        }
        tmp.next = newNode;
        return newNode;
    }

    /**
     * 根据序号删除某个节点
     * @param index
     * @return
     */
    public boolean delNodeByIndex(int index) {
        if (index < 1) {
            return false;
        }
        if (index == 0) {
            head = head.next;
            return true;
        }

        //0定义为头节点
        int i = 1;
        Node preNode = head;
        Node curNode = head.next;
        while (curNode != null) {
            if (i == index) {
                //找到节点后将该节点的上一节点的next指向该节点的下一个节点,在c/c++中还需要释放该节点占用的空间
                preNode.next = curNode.next;
                return true;
            }

            preNode = curNode;
            curNode = curNode.next;
            i++;
        }

        return false;
    }

    /**
     * 根据节点删除节点
     * @param node
     * @return
     */
    public boolean delNodeByNode(Node node) {
        if (node == null) {
            return false;
        }

        if (node == head) {
            head = node.next;
            return true;
        }

        Node curNode = head;
        //遍历找到下个节点是该节点的则重新指向即可
        while (curNode.next != null) {
            if(curNode.next == node) {
                curNode.next = node.next;
                return true;
            }
            curNode = curNode.next;
        }

        return false;
    }

    /**
     * 遍历打印该链表
     */
    public void printList() {
        Node tmp = head;
        while (tmp != null) {
            System.out.println(tmp.data);
            tmp = tmp.next;
        }
    }
}

结果:

head.data:0
0
1
2
3
4
5
After deleteNode(4):
0
1
2
3
5
After node 2:
0
1
3
5

进程已结束,退出代码为 0

在这里插入图片描述

(4)、二叉树简单实现

实现二叉树的三种遍历方式:

package com.xiaoyaoyou.day9;

import java.util.ArrayList;
import java.util.List;

public class MyBinaryTree {
    public static void main(String[] args) {
        BinaryTree tree = new BinaryTree();
        System.out.println("根节点是:"+ tree.getRoot().data);

        System.out.println("先序排列:");
        tree.list.clear();
        tree.preOrder(tree.getRoot());
        for(BinaryTree.Node node:tree.getResult()){
            System.out.print(node.data);
            System.out.print(" ");
        }

        System.out.println("\n中序排列:");
        tree.list.clear();
        tree.inOrder(tree.getRoot());
        for(BinaryTree.Node node:tree.getResult()){
            System.out.print(node.data);
            System.out.print(" ");
        }

        System.out.println("\n后序排列:");
        tree.list.clear();
        tree.afterOrder(tree.getRoot());
        for(BinaryTree.Node node:tree.getResult()){
            System.out.print(node.data);
            System.out.print(" ");
        }
    }
}

class BinaryTree {
    private Node root;
    List<Node> list = new ArrayList<Node>();

    public BinaryTree(){
        init();
    }

    public Node getRoot() {
        return root;
    }

    //树的初始化:先从叶节点开始,由叶到根
    private void init(){
        Node node10 = new Node(10,null,null);
        Node node9 = new Node(9,null,null);
        Node node8 = new Node(8, null, null);
        Node node7 = new Node(7,null,null);
        Node node6 = new Node(6,null,null);
        Node node5 = new Node(5, node10, null);
        Node node4 = new Node(4, node8, node9);
        Node node3 = new Node(3, node6, node7);
        Node node2 = new Node(2, node4, node5);
        Node node1 = new Node(1, node2, node3);
        root = node1;
    }

    class Node {
        int data;
        Node leftNode;
        Node rightNode;

        public Node(int data, Node leftNode, Node rightNode) {
            this.data = data;
            this.leftNode = leftNode;
            this.rightNode = rightNode;
        }
    }

    /**
     * 对该二叉树进行先序遍历 结果存储到list中 先序遍历(根-左-右):1-2-4-8-9-5-10-3-6-7
     * @param node
     */
    public void preOrder(Node node)
    {
        list.add(node);
        //如果左子树不为空继续往左找,在递归调用方法的时候一直会将子树的根存入list,这就做到了先遍历根节点
        if(node.leftNode != null)
        {
            preOrder(node.leftNode);
        }
        //无论走到哪一层,只要当前节点左子树为空,那么就可以在右子树上遍历,保证了根左右的遍历顺序
        if(node.rightNode != null)
        {
            preOrder(node.rightNode);
        }
    }

    /**
     * 对该二叉树进行中序遍历 结果存储到list中 中序遍历:(左-根-右):8-4-9-2-10-5-1-6-3-7
     * @param node
     */
    public void inOrder(Node node)
    {
        //左
        if(node.leftNode != null){
            inOrder(node.leftNode);
        }
        //根
        list.add(node);
        //右
        if(node.rightNode != null){
            inOrder(node.rightNode);
        }
    }

    /**
     * 对该二叉树进行后序遍历 结果存储到list中 后序遍历(左-右-根):8-9-4-10-5-2-6-7-3-1
     * @param node
     */
    public void afterOrder(Node node)
    {
        //左-右-根
        if(node.leftNode != null) {
            afterOrder(node.leftNode);
        }
        if(node.rightNode != null) {
            afterOrder(node.rightNode);
        }
        list.add(node);
    }

    //得到遍历结果
    public List<Node> getResult()
    {
        return list;
    }
}

结果为:

根节点是:1
先序排列:
1 2 4 8 9 5 10 3 6 7 
中序排列:
8 4 9 2 10 5 1 6 3 7 
后序排列:
8 9 4 10 5 2 6 7 3 1

在这里插入图片描述

3、常见数据结构

数据存储的常用结构有:栈、队列、数组、链表和红黑树。我们分别来了解一下:

(1)、栈

stack,又称堆栈, 栈(stack)是限定仅在表尾进行插入和删除操作的线性表。我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。栈又称为先进后出的线性表 。

简单的说:采用该结构的集合,对元素的存取有如下的特点

  • 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。

  • 栈的入口、出口的都是栈的顶端位置。

在这里插入图片描述

这里两个名词需要注意:

压栈:存元素。

弹栈:取元素。

(2)、队列

队列queue,简称队, 队列是一种特殊的线性表,是运算受到限制的一种线性表,只允许在表的一端进行插入,而在另一端进行删除元素的线性表。队尾(rear)是允许插入的一端。队头(front)是允许删除的一端。空队列是不含元素的空表。

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。

  • 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。

在这里插入图片描述

(3)、数组

数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 查找元素快:通过索引,可以快速访问指定位置的元素

  • 增删元素慢

指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。

**指定索引位置删除元素:**需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。

(4)、链表

链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表

在这里插入图片描述

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。

在这里插入图片描述

  • 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素

  • 增删元素快:

    • 增加元素:只需要修改连接下个元素的地址即可。
      在这里插入图片描述

    • 删除元素:只需要修改连接下个元素的地址即可。

在这里插入图片描述

(5)、红黑树

二叉树binary tree ,是每个结点不超过2的有序树(tree

简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子结点。

二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。

如图:

在这里插入图片描述

我们要说的是二叉树的一种比较有意思的叫做红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。

红黑树的约束:

  • 1、节点可以是红色的或者黑色的

  • 2、根节点是黑色的

  • 3、叶子节点(特指空节点)是黑色的

  • 4、每个红色节点的子节点都是黑色的

  • 5、任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同

红黑树的特点:

​ 速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍

4、Collection接口(重点)

Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。

此接口定义在 java.util 包中。

此接口定义如下:

public interface Collection<E> extends Iterable<E>

此接口使用了泛型技术,在 JDK 1.5 之后为了使类集操作的更加安全,所以引入了泛型。

此接口的常用方法如下所示。

在这里插入图片描述

本接口中一共定义了 15 个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。

但是,在开发中不会直接使用 Collection 接口。而使用其操作的子接口:List、Set。

之所以有这样的明文规定,也是在 JDK 1.2 之后才有的。一开始在 EJB 中的最早模型中全部都是使用 Collection 操作 的,所以很早之前开发代码都是以 Collection 为准,但是后来为了更加清楚的区分,集合中是否允许有重复元素所以 SUN 在其开源项目 —— PetShop(宠物商店)中就开始推广 List 和 Set 的使用。

5、Collection接口子接口-List接口(重点)

(1)、List接口介绍

java.util.List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List 接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。

看完API,我们总结一下:

List接口特点:

  • 1、它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。

  • 2、它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。

  • 3、集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。

(2)、List接口中常用方法

List 子接口的定义:

public interface List extends Collection

此接口上依然使用了泛型技术。此接口对于 Collection 接口来讲有如下的扩充方法:

在这里插入图片描述

在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。

所以,证明,List 接口拥有比 Collection 接口更多的操作方法。

了解了 List 接口之后,那么该如何使用该接口呢?需要找到此接口的实现类,常用的实现类有如下几个:

(3)、List子类-ArrayList(重点)

最多的功能为查询数据、遍历数据,所以 ArrayList 是最常用的集合。

许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。

ArrayList 是 List 接口的子类,此类的定义如下:

public class ArrayList<E> extends AbstractList<E> 

implements List<E>, RandomAccess, Cloneable, Serializable

此类继承了 AbstractList 类。AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。

**范例:**增加及取得元素

package com.xiaoyaoyou.day9;

import java.util.ArrayList;

public class TestArrayList {
    public static void main(String[] args) {
        //ArrayList:使用的是数组结构,对于增删慢,查找快
        ArrayList<Integer> data = new ArrayList<>();
        data.add(100);
        data.add(200);
        data.add(300);
        System.out.println(data.get(0));
        System.out.println(data);
    }
}

结果:

100
[100, 200, 300]

可以发现,此时的对象数组并没有长度的限制,长度可以任意长,只要是内存够大就行。当增加时空间不够后会通过创建新数组后将旧数组内容复制到新数组。

(4)、Vector(重点)

与 ArrayList 一样,Vector 本身也属于 List 接口的子类,此类的定义如下:

public class Vector<E> extends AbstractList<E> 

implements List<E>, RandomAccess, Cloneable, Serializable

此类与 ArrayList 类一样,都是 AbstractList 的子类。所以,此时的操作只要是 List 接口的子类就都按照 List 进行操作。

package com.xiaoyaoyou.day9;

import java.util.Vector;

public class VectorTest {
    public static void main(String[] args) {
        Vector<Integer> data = new Vector<>();

        data.add(200);
        data.add(300);
        System.out.println(data.get(0));
        System.out.println(data);
    }
}

结果:

100
[100, 200, 300]

以上的操作结果与使用 ArrayList 本身并没有任何的区别。因为操作的时候是以接口为操作的标准。

但是 Vector 属于 Java 元老级的操作类,是最早的提供了动态对象数组的操作类,在 JDK 1.0 的时候就已经推出了此类的使用,只是后来在 JDK 1.2 之后引入了 Java 类集合框架。但是为了照顾很多已经习惯于使用 Vector 的用户,所以在 JDK 1.2 之后将 Vector 类进行了升级了,让其多实现了一个 List 接口,这样才将这个类继续保留了下来。

(5)、Vector类和ArrayList类的区别(重点)

这两个类虽然都是 List 接口的子类,但是使用起来有如下的区别,为了方便大家笔试,列出此内容:

在这里插入图片描述

(6)、List子类-链表操作类:LinkedList(理解)

java.util.LinkedList 集合数据存储的结构是链表结构。方便元素添加、删除的集合。

LinkedList是一个双向链表

实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可:

此类的使用几率是非常低的,但是此类的定义如下:

public class LinkedList<E> extends AbstractSequentialList<E> 

implements List<E>, Deque<E>, Cloneable, Serializable

此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法:

在这里插入图片描述

LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。(了解即可)

package com.xiaoyaoyou.day9;

import java.util.LinkedList;

public class LinkedListTest {
    public static void main(String[] args) {
        //LinkedList:使用的是双向链表,对于增删快,查找慢
        LinkedList<Integer> data = new LinkedList<>();
        data.add(1);
        data.add(2);
        data.add(3);
        //模拟压栈,先进后出,所以100先进,弹出一个后先弹出的是最后的200
        data.push(100);
        data.push(200);
        System.out.println(data);
        //模拟弹栈
        data.pop();
        System.out.println(data);
    }
}

结果:

在这里插入图片描述

6、集合输出(重点)

对于集合的输出本身也是有多种形式的,可以使用如下的几种方式:

  • Iterator 迭代输出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%)

但是在讲解输出的时候一定要记住以下的原则:“只要是碰到了集合,则输出的时候想都不想就使用 Iterator 进行输出。

(1)、Iterator接口(绝对重点)

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

Iterator 属于迭代输出,基本的操作原理:是不断的判断是否有下一个元素,有的话,则直接输出。

此接口定义如下:

 public interface Iterator<E>

要想使用此接口,则必须使用 Collection 接口,在 Collection 接口中规定了一个 iterator()方法,可以用于为 Iterator 接口进行实例化操作。

此接口规定了以下的三个方法:

在这里插入图片描述

通过 Collection 接口为其进行实例化之后,一定要记住,Iterator 中的操作指针是在第一条元素之上,当调用 next()方法的时候,获取当前指针指向的值并向下移动,使用 hasNext()可以检查序列中是否还有元素。

在这里插入图片描述

 package com.xiaoyaoyou.day9;
 
 import java.util.ArrayList;
 import java.util.Iterator;
 
 public class IteratorTest {
     public static void main(String[] args) {
         //Iterator
         //ListIterator
         ArrayList<Integer> data = new ArrayList<>();
         data.add(1);
         data.add(2);
         data.add(3);
         Iterator<Integer> iterator = data.iterator();
         while (iterator.hasNext()) {
             Integer i = iterator.next();
             System.out.println(i);
         }
     }
 }

结果:

 1
 2
 3
(2)、ListIterator(理解)

ListIterator 是可以进行双向输出的迭代接口,此接口定义如下:

 public interface ListIterator<E> 
 
 extends Iterator<E>

此接口是 Iterator 的子接口,此接口中定义了以下的操作方法:

在这里插入图片描述

但是如果要想使用 ListIterator 接口,则必须依靠 List 接口进行实例化。

List 接口中定义了以下的方法:ListIterator listIterator()

 package com.xiaoyaoyou.day9;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.ListIterator;
 
 public class LinkedIteratorTest {
     public static void main(String[] args) {
         List<String> all = new ArrayList<String>();
         all.add("A");
         all.add("B");
         all.add("C");
         all.add("D");
         all.add("E");
         ListIterator<String> iter = all.listIterator();
         System.out.print("从前向后输出:");
         while (iter.hasNext()) {
             System.out.print(iter.next() + "、");
         }
         System.out.print("\n从后向前输出:");
         while (iter.hasPrevious()) {
             System.out.print(iter.previous() + "、");
         }
     }
 }

结果:

 从前向后输出:A、B、C、D、E、
 从后向前输出:E、D、C、B、A、

但是,此处有一点需要注意的是,如果要想进行由后向前的输出,则首先必须先进行由前向后的输出。

但是,此接口一般使用较少。

(3)、废弃的接口Enumeration(了解)

Enumeration 是一个非常古老的输出接口,其也是一个元老级的输出接口,最早的动态数组使用 Vector 完成,那么只要是使用了 Vector 则就必须使用 Enumeration 进行输出。

此接口定义如下:

 public interface Enumeration<E>

在 JDK 1.5 之后,此接口实际上也已经加入了泛型操作。此接口常用方法如下:

在这里插入图片描述

但是,与 Iterator 不同的是,如果要想使用 Enumeration 输出的话,则还必须使用 Vector 类完成,在类中定义了如下

方法:public Enumeration elements()

需要注意的是,在大部分的情况下,此接口都不再使用了,但是对于一些古老的类库,本身依然要使用此接口进行操作,所以此接口一定要掌握。

(4)、foreach(理解)

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

foreach 可以用来输出数组的内容,那么也可以输出集合中的内容。

 package com.xiaoyaoyou.day9;
 
 import java.util.ArrayList;
 
 public class ForeachTest {
     public static void main(String[] args) {
         //增强for循环foreach,最早出现在c#
         //用于迭代数组或集合
 
         //遍历数组
         System.out.println("遍历数组:");
         int[] arr = {6, 5, 3, 2};
         for (int res: arr) {
             System.out.println(res);
         }
 
         //遍历集合
         System.out.println("遍历集合:");
         ArrayList<String> data = new ArrayList<>();
         data.add("A");
         data.add("B");
         data.add("C");
         for (String res:
              data) {
             System.out.println(res);
         }
     }
 }

结果:

 遍历数组:
 6
 5
 3
 2
 遍历集合:
 A
 B
 C

tips: 新for循环必须有被遍历的目标。目标只能是Collection或者是数组。新式for仅仅作为遍历操作出现。

7、Collect接口子接口-Set接口(重点)

java.util.Set接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比Collection 接口更加严格了。与 List 接口不同的是, Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。

  • tips:Set集合取出元素的方式可以采用:迭代器、增强for。
(1)、散列存放:HashSet集合介绍(重点)

既然 Set 接口并没有扩充任何的 Collection 接口中的内容,所以使用的方法全部都是 Collection 接口定义而来的。

HashSet 属于散列的存放类集,里面的内容是无序存放的。

java.util.HashSetSet 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。 java.util.HashSet 底层的实现其实是一个 java.util.HashMap 支持,由于我们暂时还未学习,先做了解。

HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCodeequals 方法。

package com.xiaoyaoyou.day9;

import java.util.HashSet;
import java.util.Iterator;

public class HashSetTest {
    public static void main(String[] args) {
        //HashSet:散列存放(哈希表在学习HashMap时再了解)
        HashSet<String> set = new HashSet<>();
        set.add("A");
        set.add("C");
        set.add("B");
        set.add("D");
        //即使有两个D在迭代时也没有
        set.add("D");

        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        for (String res:
             set) {
            System.out.println(res);
        }
    }
}

输出结果如下,说明SET集合中不能存储重复元素,并且可以看出存储顺序和打印顺序不一致,这也是HashSet散列性的体现:

A
B
C
D
A
B
C
D
(2)、HashSet集合存储数据的结构(哈希表)

什么是哈希表呢?

JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。

在这里插入图片描述

看到这张图就有人要问了,这个是怎么存储的呢?

为了方便大家的理解我们结合一个存储流程图来说明一下:

在这里插入图片描述

总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

(3)、排序的子类-TreeSet(重点)

与 HashSet 不同的是,TreeSet 本身属于排序的子类,有序是指按照数据类型进行排序,此类的定义如下:

public class TreeSet<E> extends AbstractSet<E> 

implements **NavigableSet**<E>, Cloneable, Serializable

下面通过代码来观察其是如何进行排序的。

package com.xiaoyaoyou.day9;

import java.util.TreeSet;

public class TreeSetTest {
    public static void main(String[] args) {
        TreeSet<String> set = new TreeSet<>();

        set.add("A");
        set.add("X");
        set.add("C");

        for (String s : set) {
            System.out.println(s);
        }
    }
}

结果:

A
C
X
(4)、排序的说明(重点)

既然 Set 接口的 TreeSet 类本身是允许排序,那么现在自定义一个类是否可以进行对象的排序呢?

**范例:**定义 Person 类

package com.xiaoyaoyou.day9;

import java.util.Set;
import java.util.TreeSet;

public class TreeSetSortTest {
    public static void main(String[] args) {
        Set<Person> all = new TreeSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 11));
        all.add(new Person("王五", 12));
        all.add(new Person("赵六", 13));
        System.out.println(all);
    }
}

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 String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

结果:

Exception in thread "main" java.lang.ClassCastException: com.xiaoyaoyou.day9.Person cannot be cast to java.lang.Comparable
	at java.util.TreeMap.compare(TreeMap.java:1290)
	at java.util.TreeMap.put(TreeMap.java:538)
	at java.util.TreeSet.add(TreeSet.java:255)
	at com.xiaoyaoyou.day9.TreeSetSortTest.main(TreeSetSortTest.java:9)

此时的提示是:Person 类不能向 Comparable 接口转型的问题?

所以,证明,如果现在要是想进行排序的话,则必须在 Person 类中实现 Comparable 接口。

package com.xiaoyaoyou.day9;

import java.util.Set;
import java.util.TreeSet;

public class TreeSetSortTest {
    public static void main(String[] args) {
        Set<Person> all = new TreeSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("王五", 12));
        all.add(new Person("赵六", 13));
        System.out.println(all);
    }
}

class Person implements Comparable<Person> {
    private String name;
    private int age;

    @Override
    public int compareTo(Person o) {
        if (this.age > o.age) {
            return 1;
        } else if (this.age < o.age) {
            return -1;
        }
        return 0;
    }

    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 String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

结果:

[Person{name='张三', age=10}, Person{name='王五', age=12}, Person{name='赵六', age=13}]

从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则此时必须修改 Person 类,如果假设年龄相等的话,按字符串进行排序。

@Override
    public int compareTo(Person o) {
        if (this.age > o.age) {
            return 1;
        } else if (this.age < o.age) {
            return -1;
        }
        return name.compareTo(o.name);
    }

结果:

[Person{name='张三', age=10}, Person{name='李四', age=10}, Person{name='王五', age=12}, Person{name='赵六', age=13}]

此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。

(5)、关于重复元素的说明(重点)

之前使用 Comparable 完成的对于重复元素的判断,那么 Set 接口定义的时候本身就是不允许重复元素的,那么证明如果现在真的是有重复元素的话,使用 HashSet 也同样可以进行区分。

package com.xiaoyaoyou.day9;

import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetSortTest {
    public static void main(String[] args) {
        Set<Person> all = new HashSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("王五", 12));
        all.add(new Person("赵六", 13));
        all.add(new Person("赵六", 13));
        System.out.println(all);
    }
}

class Person implements Comparable<Person> {
    private String name;
    private int age;

    @Override
    public int compareTo(Person o) {
        if (this.age > o.age) {
            return 1;
        } else if (this.age < o.age) {
            return -1;
        }
        return name.compareTo(o.name);
    }

    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 String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

结果:

[Person{name='赵六', age=13}, Person{name='张三', age=10}, Person{name='王五', age=12}, Person{name='李四', age=10}, Person{name='赵六', age=13}]

此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过 Comparable接口间接完成的。

如果要想判断两个对象是否相等,则必须使用 Object 类中的 equals()方法。

从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:

  • 第一种判断两个对象的编码是否一致,这个方法需要通过 hashCode()完成,即:每个对象有唯一的编码

  • 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。

所以此时需要覆写 Object 类中的 hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。

8、Map接口(重点)

以上的 Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用 Map 了,类似于以下一种情况:

  • 张三 123456

  • 李四 234567

那么保存以上信息的时候使用 Collection 就不那么方便,所以要使用 Map 接口。里面的所有内容都按照 key->value的形式保存,也称为二元偶对象。

此接口定义如下:

public interface Map<K,V>

此接口与 Collection 接口没有任何的关系,是第二大的集合操作接口。此接口常用方法如下:

在这里插入图片描述

Map 本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable

(1)、新的子类-HashMap(重点)

HashMap 是 Map 的子类,此类的定义如下:

public class HashMap<K,V> extends AbstractMap<K,V> 

implements Map<K,V>, Cloneable, Serializable

此类继承了 AbstractMap 类,同时可以被克隆,可以被序列化下来。

**范例:**向集合中增加内容、得到全部的 key 或 value以及打印Map

package com.xiaoyaoyou.day9;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapTest {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(1, "张三B");
        map.put(2, "张三A");
        map.put(3, "李四");
        map.put(4, "王五");
        String val = map.get(6);
        System.out.println(val);

        //得到全部的key和value
        Set<Integer> set = map.keySet();
        Collection<String> value = map.values();
        System.out.println("key:");
        for (int k:
             set) {
            System.out.println(k);
        }
        System.out.println("value:");
        for (String v:
             value) {
            System.out.println(v);
        }

        System.out.println("直接打印map:");
        System.out.println(map);
        System.out.println("循环打印map:");
        for (int k:
             set) {
            System.out.println(k+":"+map.get(k));
        }
    }
}

结果:

null
key:
1
2
3
4
value:
张三B
张三A
李四
王五
直接打印map:
{1=张三B, 2=张三A, 3=李四, 4=王五}
循环打印map:
1:张三B
2:张三A
3:李四
4:王五

以上的操作是 Map 接口在开发中最基本的操作过程,根据指定的 key 找到内容,如果没有找到,则返回 null,找到了则返回具体的内容。

HashMap 本身是属于无序存放的。

(2)、旧的子类-HashTable(重点)

Hashtable 是一个最早的 keyvalue 的操作类,本身是在 JDK 1.0 的时候推出的。其基本操作与 HashMap 是类似的。

操作的时候,可以发现与 HashMap 基本上没有什么区别,而且本身都是以 Map 为操作标准的,所以操作的结果形式都一样。但是 Hashtable 中是不能向集合中插入 null 值的。

(3)、HashMap、HashTable、ConcurrentHashMap的区别

HashMap:线程不安全,效率高

HashTable:线程安全,效率低

ConcurrentHashMap:采用分段锁机制,保证线程安全,效率又比较高

(4)、排序的子类:TreeMap(理解)

TreeMap 子类是允许 key 进行排序的操作子类,其本身在操作的时候将按照 key 进行排序,另外,key 中的内容可以为任意的对象,但是要求对象所在的类必须实现 Comparable 接口。

(5)、Map集合的输出

在 Collection 接口中,可以使用 iterator()方法为 Iterator 接口实例化,并进行输出操作,但是在 Map 接口中并没有此方法的定义,所以 Map 接口本身是不能直接使用 Iterator 进行输出的。

因为 Map 接口中存放的每一个内容都是一对值,而使用 Iterator 接口输出的时候,每次取出的都实际上是一个完整的对象。如果此时非要使用 Iterator 进行输出的话,则可以按照如下的步骤进行:

  • 1、 使用 Map 接口中的 entrySet()方法将 Map 接口的全部内容变为 Set 集合

  • 2、 可以使用 Set 接口中定义的 iterator()方法为 Iterator 接口进行实例化

  • 3、 之后使用 Iterator 接口进行迭代输出,每一次的迭代都可以取得一个 Map.Entry 的实例

  • 4、 通过 Map.Entry 进行 key 和 value 的分离

那么,到底什么是 Map.Entry 呢?

Map.Entry 本身是一个接口。此接口是定义在 Map 接口内部的,是 Map 的内部接口。此内部接口使用 static 进行定义,所以此接口将成为外部接口。

实际上来讲,对于每一个存放到 Map 集合中的 key 和 value 都是将其变为了 Map.Entry 并且将 Map.Entry 保存在了Map 集合之中。

在这里插入图片描述

在 Map.Entry 接口中以下的方法最为常用:

在这里插入图片描述

package com.xiaoyaoyou.day9;

import java.util.*;

public class HashMapTest {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(1, "张三B");
        map.put(2, "张三A");
        map.put(3, "李四");
        map.put(4, "王五");
        Set<Map.Entry<Integer, String>> set = map.entrySet();
        Iterator<Map.Entry<Integer, String>> iter = set.iterator();
        while (iter.hasNext()) {
            Map.Entry<Integer, String> me = iter.next();
            System.out.println(me.getKey() + "-->" + me.getValue());
        }
    }
}

结果:

1-->张三B
2-->张三A
3-->李四
4-->王五

以上的代码一定要记住,Map 集合中每一个元素都是 Map.Entry 的实例,只有通过 Map.Entry 才能进行 key 和 value的分离操作。

除了以上的做法之外,在 JDK 1.5 之后也可以使用 foreach 完成同样的输出,只是这样的操作基本上不使用。

for (Map.Entry<String, String> me : map.entrySet()) {      System.out.println(me.getKey() + " --> " + me.getValue());
}

9、两种关系(理解)

使用类集,除了可以清楚的表示出动态数组的概念及各个数据结构的操作之外,也可以表示出以下的两种关系。

(1)、第一种关系:一对多关系

一个学校有多个学生,是一个典型的一对多的关系。

定义学生类,一个学生属于一个学校

定义学校类,一个学校有多个学生

之后在主方法处建立以上两者的关系

package com.xiaoyaoyou.day9;

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

public class StudentSchool {
    public static void main(String[] args) {
        Student stu1 = new Student("张三", 10);
        Student stu2 = new Student("李四", 11);
        Student stu3 = new Student("王五", 12);

        School sch = new School("LAMP JAVA");
        sch.getAllStudents().add(stu1);
        // 一个学校有多个学生
        stu1.setSchool(sch);
        // 一个学生属于一个学校
        sch.getAllStudents().add(stu2);
        // 一个学校有多个学生
        stu2.setSchool(sch);
        // 一个学生属于一个学校
        sch.getAllStudents().add(stu3);
        // 一个学校有多个学生
        stu3.setSchool(sch);
        // 一个学生属于一个学校
        System.out.println(sch);

        Iterator<Student> iter = sch.getAllStudents().iterator();
        while (iter.hasNext()) {
            System.out.println(iter.next());
        }
        System.out.println(stu1.getSchool());
    }
}

class Student {
    private String name;
    private int age;
    private School school;

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

    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

class School {
    private List<Student> allStudents = null;
    private String name;

    public School() {
        this.allStudents = new ArrayList<Student>();
    }

    public School(String name) {
        this();
        this.name = name;
    }

    public List<Student> getAllStudents() {
        return allStudents;
    }

    @Override
    public String toString() {
        return "School{" +
                ", name='" + name + '\'' +
                '}';
    }
}

结果:

School{, name='LAMP JAVA'}
Student{name='张三', age=10, school=School{, name='LAMP JAVA'}}
Student{name='李四', age=11, school=School{, name='LAMP JAVA'}}
Student{name='王五', age=12, school=School{, name='LAMP JAVA'}}
School{, name='LAMP JAVA'}
(2)、第二种关系:多对多关系

一个学生可以选择多门课程,一门课程允许有多个学生参加。

定义学生类,一个学生可以选择多门课程

定义课程类,一门课程可以有多个学生参加

下面同样在主方法处设置关系,但是必须注意的是,这个时候设置的关系也应该是双向操作完成的。

package com.xiaoyaoyou.day9;

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

public class StudentCourse {
    public static void main(String[] args) {
        Student stu1 = new Student("张三", 10);
        Student stu2 = new Student("李四", 11);
        Student stu3 = new Student("王五", 12);
        Student stu4 = new Student("赵六", 15);
        Student stu5 = new Student("孙七", 13);
        Course c1 = new Course("Oracle", 5);
        Course c2 = new Course("Java SE基础课程", 10);
        c1.getAllStudents().add(stu1);// 参加第一门课程
        c1.getAllStudents().add(stu2); // 参加第一门课程
        stu1.getAllCourses().add(c1); // 学生选择课程
        stu2.getAllCourses().add(c1); // 学生选择课程
        c2.getAllStudents().add(stu1); // 参加第二门课程
        c2.getAllStudents().add(stu2); // 参加第二门课程
        c2.getAllStudents().add(stu3); // 参加第二门课程
        c2.getAllStudents().add(stu4); // 参加第二门课程
        c2.getAllStudents().add(stu5); // 参加第二门课程
        stu1.getAllCourses().add(c2); // 学生选择课程
        stu2.getAllCourses().add(c2); // 学生选择课程
        stu3.getAllCourses().add(c2); // 学生选择课程
        stu4.getAllCourses().add(c2); // 学生选择课程
        stu5.getAllCourses().add(c2); // 学生选择课程
        System.out.println(c2);
        Iterator<Student> iter = c2.getAllStudents().iterator();
        while (iter.hasNext()) {
            System.out.println(iter.next());
        }

        System.out.println("----------------------------");
        System.out.println(stu1);
        Iterator<Course> iters = stu1.getAllCourses().iterator();
        while (iters.hasNext()) {
            System.out.println(iters.next());
        }
    }
}

class Student {
    private String name;
    private int age;
    private List<Course> allCourse;

    public Student() {
        this.allCourse = new ArrayList<Course>();
    }

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

    public List<Course> getAllCourses() {
        return allCourse;
    }

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

class Course {
    private String name;
    private int credit;
    private List<Student> allStudents = null;

    public Course() {
        this.allStudents = new ArrayList<Student>();
    }

    public Course(String name, int credit) {
        this();
        this.name = name;
        this.credit = credit;
    }

    public List<Student> getAllStudents() {
        return allStudents;
    }

    @Override
    public String toString() {
        return "Course{" +
                "name='" + name + '\'' +
                ", credit=" + credit +
                '}';
    }
}

结果:

Course{name='Java SE基础课程', credit=10}
Student{name='张三', age=10}
Student{name='李四', age=11}
Student{name='王五', age=12}
Student{name='赵六', age=15}
Student{name='孙七', age=13}
----------------------------
Student{name='张三', age=10}
Course{name='Oracle', credit=5}
Course{name='Java SE基础课程', credit=10}

10、Collections类(理解)

Collections 实际上是一个集合的操作类,此类的定义如下:

public class Collections extends Object

这个类与 Collection 接口没有任何的关系。是一个单独存在的类。

(1)、常用功能
  • java.utils.Collections 是集合工具类,用来对集合进行操作。部分方法如下:public static boolean addAll(Collection c, T… elements) :往集合中添加一些元素。
  • public static void shuffle(List<?> list) 打乱顺序 :打乱集合顺序。
  • public static void sort(List list) :将集合中元素按照默认规则排序。
  • public static void sort(List list,Comparator<? super T> ) :将集合中元素按照指定规则排序。
package com.xiaoyaoyou.day9;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class CollectionsTest {
    public static void main(String[] args) {
        List<String> all = new ArrayList<>();
        Collections.addAll(all, "A", "B", "C");
        System.out.println(all);
    }
}

结果:

[A, B, C]

但是,从实际考虑,使用此类操作并不是很方便,最好的做法就是使用各个接口的直接操作的方法完成。此类只是一个集合的操作类。

(2)、Comparator比较器

我们还是先研究这个方法

public static void sort(List list) :将集合中元素按照默认规则排序。

不过这次存储的是字符串类型。

我们使用的是默认的规则完成字符串的排序,那么默认规则是怎么定义出来的呢?

说到排序了,简单的说就是两个对象之间比较大小,那么在JAVA中提供了两种比较实现的方式,一种是比较死板的采用 java.lang.Comparable 接口去实现,一种是灵活的当我需要做排序的时候在去选择的 java.util.Comparator 接口完成。

那么我们采用的 public static void sort(List list) 这个方法完成的排序,实际上要求了被排序的类型需要实现Comparable接口完成比较的功能,在String类型上如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

String类实现了这个接口,并完成了比较规则的定义,但是这样就把这种规则写死了,那比如我想要字符串按照第一个字符降序排列,那么这样就要修改String的源代码,这是不可能的了,那么这个时候我们可以使用

public static void sort(List list,Comparator<? super T> ) 方法灵活的完成,这个里面就涉及到了Comparator这个接口,位于位于java.util包下,排序是comparator能实现的功能之一,该接口代表一个比较器,比较器具有可比性!顾名思义就是做排序的,通俗地讲需要比较两个对象谁排在前谁排在后,那么比较的方法就是:

  • public int compare(String o1, String o2) :比较其两个参数的顺序。

两个对象比较的结果有三种:大于,等于,小于。

如果要按照升序排序,

则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数)

如果要按照降序排序

则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)

(3)、简述Comparable和Comparator两个接口的区别

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

Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

11、分析equals、hashCode与内存泄露(理解)

(1)、equals

equals 的作用:比较两个对象的地址值是否相等

equals()方法在 object 类中定义如下:

public boolean equals(Object obj) { return (this == obj); }

但是我们必需清楚,当 String 、Math、还有 Integer、Double。。。。等这些封装类在使用 equals()方法时,已经覆盖了 object类的 equals()方法,不再是地址的比较而是内容的比较。

我们还应该注意,Java语言对 equals()的要求如下,这些要求是必须遵循的:

  • 1、对称性:如果 x.equals(y)返回是“true”,那么 y.equals(x)也应该返回是“true”。
  • 2、反射性:x.equals(x)必须返回是“true”。
  • 3、类推性:如果 x.equals(y)返回是“true”,而且 y.equals(z)返回是“true”,那么 z.equals(x)也应该返回是“true”。
  • 4、还有一致性:如果 x.equals(y)返回是“true”,只要 x 和 y 内容一直不变,不管你重复 x.equals(y)多少次,返回都是 “true”。
  • 5、任何情况下,x.equals(null),永远返回是“false”;x.equals(和 x 不同类型的对象)永远返回是“false”。

以上这五点是重写 equals()方法时,必须遵守的准则,如果违反会出现意想不到的结果,请大家一定要遵守。

(2)、hashCode

hashCode()方法,在object类中定义如下:

public native int hashCode(); 

说明是一个本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖 hashcode()方法,比如 String、

Integer、Double。。。。等等这些类都是覆盖了 hashcode()方法的。

  • java.lnag.Object中对hashCode的约定(很重要):
  • 1、在一个应用程序执行期间,如果一个对象的 equals 方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode 方法多次,它必须始终如一地返回同一个整数。
  • 2、如果两个对象根据 equals(Object o)方法是相等的,则调用这两个对象中任一对象的 hashCode 方法必须产生相同的整数结果。
  • 3、如果两个对象根据 equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的 hashCode 方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

java 的集合中,判断两个对象是否相等的规则是:

  • (1)判断两个对象的 hashCode 是否相等如果不相等,认为两个对象也不相等,完毕如果相等,转入 2 (这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。后面会重点讲到这个问题。)

  • (2)判断两个对象用 equals 运算是否相等如果不相等,认为两个对象也不相等,如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)

(3)、内存泄露

当一个对象被存进 HashSet 集合后,就不能修改这个对象中的那些参与计算的哈希值的字段了,否则,对象被修改后的哈希值与最初存储进 HashSet 集合中时的哈希值就不同了,在这种情况下,即使在 contains 方法使用该对象的当前引用作为的参数去 HashSet 集合中检索对象,也将返回找不到对象的结果,这也会导致无法从 HashSet 集合中删除当前对象,从而造成内存泄露。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昵称系统有问题

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值