return Java2.0--第二阶段(提升编程能力)

35 篇文章 0 订阅

第十四章

集合

14.1 集合的理解
14.1.1、集合 VS 数组

在这里插入图片描述
在这里插入图片描述

14.2 集合框架图

基本介绍:
1、集合主要是两组(单列集合,双列集合)
2、CoLlection接口有两个重要的子接口 List Set ,他们的实现子类都是单列集合
3、Map接口的实现子类是双列集合,存放的 K - V

①Collectin(单列集合)(单个元素)

在这里插入图片描述
Map(双列集合)(k-v毽子对)
在这里插入图片描述
案例演示分析:
在这里插入图片描述

14.3 Collection接口
14.3.1 Collection接口实现类的特点

特点:
1、Collection 的实现子类可以存放多个元素,每个元素可以是Object或者其子类
2、有些Collection的实现类,可以存放重复的元素,有些不可以
3、有些Collection的实现类,有些事有序的(List),有些是无序的(Set)
4、Collection接口没有直接的实现子类,是通过它的子接口List和Set来实现的

14.3.2 Collection接口的常用方法

Collection接口的方法有很多,可以查看API和类图中的diagram去查看,接下来演示下常用的方法即可
但是因为Collection是接口,接口时不能够直接去实现的,所以我们用其子类ArrayList来演示

1、add( )添加单个元素
在这里插入图片描述
案例:

package com.xiaowang.collection_;

import java.util.*;

/**
 * @Author 小王
 * @DATE: 2022/4/12
 */
public class CollectionMethod {
    public static void main(String[] args) {
        List arrayList = new ArrayList();
        //1.add()  在末尾添加单个字符
        //添加的可以是Object类的所有类
        arrayList.add("小王");
        arrayList.add(true);
        arrayList.add(52);
        System.out.println("未指定位置添加:"+arrayList);
        //add()  在指定位置(指定索引处)添加单个字符
        arrayList.add(1,"江仔");
        System.out.println("索引为1的位置添加:"+arrayList);
    }
}
输出:
未指定位置添加:[小王, true, 52]
索引为1的位置添加:[小王, 江仔, true, 52]

2、remove( )删除指定元素
在这里插入图片描述
案例:

package com.xiaowang.collection_;

import java.util.*;

/**
 * @Author 小王
 * @DATE: 2022/4/12
 */
public class CollectionMethod {
    public static void main(String[] args) {
        List arrayList = new ArrayList();
        //1.add()  在末尾添加单个字符
        //添加的可以是Object类的所有类
        arrayList.add("小王");
        arrayList.add(true);
        arrayList.add(52);
        arrayList.add("@");
        arrayList.add("@");
       //System.out.println("未指定位置添加:"+arrayList);
        //add()  在指定位置(指定索引处)添加单个字符
        arrayList.add(1,"江仔");
      //  System.out.println("索引为1的位置添加:"+arrayList);


        //2.remove()删除指定元素
        //因为ArrayList的元素创建有点点麻烦,所以直接利用1.add()方法的ArrayList去写remove()
        System.out.println("未删除元素之前的ArrayList:"+arrayList);
        //删除索引为1 的对象
        arrayList.remove(1);
        System.out.println("删除指定索引后:"+arrayList);
        //删除第一次出现的指定元素(如果存在的情况)
        arrayList.remove("@");
        //删除指定元素(如果不存在的情况)不会报错
        arrayList.remove("9549");
        System.out.println("删除指定元素"+arrayList);
        //删除指定集合的全部元素
        arrayList.removeAll(arrayList);
        System.out.println("指定集合全部元素删除后:"+arrayList);

    }
}
输出:
未删除元素之前的ArrayList:[小王, 江仔, true, 52, @, @]
删除指定索引后:[小王, true, 52, @, @]
删除指定元素[小王, true, 52, @]
指定集合全部元素删除后:[]

3、contains 查找元素是否存在
在这里插入图片描述
案例:
整个代码见本章末尾

4、size 获取元素个数
在这里插入图片描述

5、isEmpty 判断是否未空
在这里插入图片描述

6、clear 清空元素
在这里插入图片描述
7、addAll 添加多个元素
在这里插入图片描述

案例:

package com.xiaowang.collection_;

import java.util.*;

/**
 * @Author 小王
 * @DATE: 2022/4/12
 */
public class CollectionMethod {
    public static void main(String[] args) {
        List arrayList = new ArrayList();//下面去添加元素
        List arrayList1 = new ArrayList();//创建一个空的集合
        //1.1.add()  在末尾添加单个字符
        //添加的可以是Object类的所有类
        arrayList.add("小王");//在这里会自动装箱
        arrayList.add(true);
        arrayList.add(52);//在这里会自动装箱
        arrayList.add("@");//在这里会自动装箱
        arrayList.add("@");//在这里会自动装箱
       //System.out.println("未指定位置添加:"+arrayList);
        //1.2.add()  在指定位置(指定索引处)添加单个字符
        arrayList.add(1,"江仔");
      //  System.out.println("索引为1的位置添加:"+arrayList);
        System.out.println("ArrayList:"+arrayList);

       /*
        //2.remove()删除指定元素
        //因为ArrayList的元素创建有点点麻烦,所以直接利用1.add()方法的ArrayList去写remove()
        System.out.println("未删除元素之前的ArrayList:"+arrayList);
        //2.1 删除索引为1 的对象
        arrayList.remove(1);
        System.out.println("删除指定索引后:"+arrayList);
        //2.2 删除第一次出现的指定元素(如果存在的情况)
        arrayList.remove("@");
        //删除指定元素(如果不存在的情况)不会报错
        arrayList.remove("9549");
        System.out.println("删除指定元素"+arrayList);
        //2.3 删除指定集合的全部元素
        arrayList.removeAll(arrayList);
        System.out.println("指定集合全部元素删除后:"+arrayList);
        */

        //3.contains 查找元素是否存在
        //存在就返回:true
        //不存在就返回:false
        System.out.println(arrayList.contains("小王"));
        System.out.println(arrayList.contains("9549"));

        //4.size 获取元素个数
        System.out.println("arrayList元素个数="+arrayList.size());
        System.out.println("arrayList1元素个数="+arrayList1.size());

        //5.isEmpty 判断是否未空
        System.out.println("arrayList是否是空:"+arrayList.isEmpty());
        System.out.println("arrayList1是否是空:"+arrayList1.isEmpty());

        //6.clear 清空元素
        //其实和removeAll是一个意思
        arrayList.clear();
        System.out.println("arrayList清空后:"+arrayList);

        //7.addAll 添加多个元素
        arrayList1.add("@");//因为arrayList1是空的,先添加一个元素去验证后面添加
        arrayList1.addAll(arrayList);//在末尾添加arrayList集合,在这里的时候要先把6的清空给注释了,不然没用
        System.out.println(arrayList1);
        arrayList1.addAll(1,arrayList1);//指定位置添加指定集合arrayList1
        System.out.println(arrayList1);
    }
}

8、containsAll( )查找多个元素
在这里插入图片描述
注意:
在对集合进行处理的时候,如果是进行单个处理的时候,一般都是可以传入Object类的所有类,但是,进行多个元素的时候,比如containsAll()、addAll( )、removeAll( ),里面传入的必须都是集合collection

14.2.3 Collection接口遍历元素
14.2.3.1 使用Iterator(迭代器)

Collection接口遍历元素 方式1:使用Iterator(迭代器)
在这里插入图片描述

在这里插入图片描述
iterrator(迭代器)执行原理
在这里插入图片描述
irerator(迭代器)方法
在这里插入图片描述
案例分析:
注意每一步的分析

package com.xiaowang.collection_;

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

/**
 * @Author 小王
 * @DATE: 2022/4/17
 */
public class CollectionIterator {
    @SuppressWarnings("all")//抑制一下警告
    public static void main(String[] args) {
        //创建一个接口去接受它的实现类
        Collection col = new ArrayList();
        //添加集合元素
        col.add(new Book("三国演义","罗贯中",100.1));
        col.add(new Book("红楼梦","曹雪芹",10.1));
        col.add(new Book("小李飞刀","古龙",50.1));
        //1.输出集合col的元素
        //这里调用的时toString方法,把col的所有元素一起输出
        System.out.println("col="+col);

        //2.希望能够遍历输出col集合
        // 2.1先用得到col对应的迭代器,因为collection实现了Iterator接口,所以都必须要重写接口的方法
        //    iterator()是一个得到 迭代器 的方法,可以用来遍历数组
        System.out.println("===第一次遍历===");
        Iterator iterator = col.iterator();
        // 2.2得到迭代器后,使用while循环去遍历集合
        //    因为迭代器里里面有一个hasNext()方法,失去判断集合中的下一个元素是否存在,存在就返回true
        while (iterator.hasNext()){
            //用一个Object类去接收iterator迭代器获取得到的集合元素
            Object obj = iterator.next();//编译类型是Object,运行类型就是col的实际类型
            System.out.println("cpl="+obj);
        }
        //快捷键完成while循环 itit,
        // 可以通过Ctrl+j 去查看所有快捷键的快捷键

        //3.当while循环结束过后,iterator迭代器指向了集合中最后一个元素,就不能再往下指向了,
        //  因为已经没有元素了,所以就会报错 NoSuchElementException
     //   iterator.next();//这里就会报错,因为下面已经没有元素了

        //4.如果想再次遍历的话,就需要将迭代器iterator重置,然后再进行遍历
        iterator = col.iterator();//重置迭代器
        System.out.println("===第二次遍历===");
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println("col="+next);
        }
    }
}
class Book{
    private String name;
    private String author;
    private double price;

    public Book(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }

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

14.2.3.2 使用for循环增强

Collection接口遍历元素 方式1:使用for循环增强

在这里插入图片描述
增强 for 简易理解:
在这里插入图片描述
案例分析:

package com.xiaowang.collection_;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @Author 小王
 * @DATE: 2022/4/17
 */
public class CollectionFor {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        //创建一个接口去接受它的实现类
        Collection col = new ArrayList();
        //添加集合元素
        col.add(new Book("三国演义","罗贯中",100.1));
        col.add(new Book("红楼梦","曹雪芹",10.1));
        col.add(new Book("小李飞刀","古龙",50.1));

        //1.使用增强for 在Collection集合
        //2.增强for,底层还是一个iterator迭代器,下断点可以查看
        //3.增强for可以理解成是简化版的 iterator迭代器遍历
        //4.快捷键 I (大写I)
        for (Object book :col){
            System.out.println("book="+book);
        }

        //增强for不仅用于集合,也可以用于数组
        //元素类型,元素名,集合或者数组名
        int [] arr = {1,5,2,3,4};
        for (int i:arr) {
            System.out.print(i+"\t");
        }
    }
}

14.2.3.3 课堂练习

题目:
//1.创建 3 个Dog{name,age}对象,放入到ArrayList中,赋给List用
//2.用迭代器和增强for循环两种方式来遍历
//3.重写Dog 的toString方法,输出name和age
代码:

package com.xiaowang.collection_;

import java.util.*;

/**
 * @Author 小王
 * @DATE: 2022/4/17
 */
//1.创建 3 个Dog{name,age}对象,放入到ArrayList中,赋给List用
//2.用迭代器和增强for循环两种方式来遍历
//3.重写Dog 的toString方法,输出name和age
public class CollectionExercise {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        //1.创建3 个Dog{name,age}对象,放入到ArrayList中,赋给List用
        List list = new ArrayList();
        list.add(new Dog("tom",10));
        list.add(new Dog("jack",5));
        list.add(new Dog("mary",2));
        //2.迭代器
        System.out.println("====迭代器输出====");
        Iterator iterator = list.iterator();//获取list的迭代器
        while (iterator.hasNext()) {//while去循环判断
            Object next =  iterator.next();
            System.out.println(next);
        }

        System.out.println("====增强for输出====");
        for (Object o :list) {
            System.out.println(o);
        }

    }
}
class Dog{
    private String name;
    private int age;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
输出:
====迭代器输出====
Dog{name='tom', age=10}
Dog{name='jack', age=5}
Dog{name='mary', age=2}
====增强for输出====
Dog{name='tom', age=10}
Dog{name='jack', age=5}
Dog{name='mary', age=2}


14.4 List接口
14.4.1 List接口基本介绍

先来看一张关系图
在这里插入图片描述
可以知道List接口是Collection接口的子接口,所以List接口的实现类的方法,Collection也可以用,但是区别在于Collection还有Set接口

List介绍:

  1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复[案例]
  1. List 集合中的每个元素都有其对应的顺序索引,即支持索引。[案例]
  2. List 容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
  3. List有很多实现子类,可以通过API(是java.util包里面的list)去查看

在这里插入图片描述

14.4.2 List常用方法

以下是List的常用方法,更多的方法,可以去查看java.util包下面的List接口去查看方法
在这里插入图片描述
案例演示:

package com.xiaowang.list;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author 小王
 * @DATE: 2022/4/17
 */
public class ListMethods {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        List list = new ArrayList();
        //先添加几个元素进去了进行方法操作:
        list.add(1);
        list.add(2);
        System.out.println("最初list="+list);//list=[1, 2]
        //1. void add ( dnt index , Object ele ):在 index 位置插入 ele 元素
        list.add(1,"A");//list=[1, A, 2]
        System.out.println("add ( dnt index , Object ele )后="+list);

        //2. boolean addAlI ( int index , Collection eles ):从 index 位置开始将eles(集合)中的所有元素添加进来
        List list1 = new ArrayList();
        list1.add(1);
        list1.add(2);
        list.addAll(list1);//将list1集合添加到list集合末尾,list=[1, A, 2, 1, 2]
        System.out.println("addAlI ( Collection eles )后="+list);
        list.addAll(1,list1);//将list1集合添加到list集合中index=1的位置后面,list=[1, 1, 2, A, 2]
        System.out.println("addAlI ( int index , Collection eles )后="+list);

        //3.Object get ( int index ):获取指定 index 位置的元素
        System.out.println("get ( int index )后="+list.get(1));//获取索引为1的元素

        //4.int indexOf ( Object obj ):返回 obj 在集合中首次出现的位置
        System.out.println("A在list中首次出现的位置="+list.indexOf("A"));

        //5.int lastlndexOf ( Object obj ):返回 obj 在当前集合中末次出现的位置
        System.out.println("1在list中末次出现的位置="+list.lastIndexOf(1));

        //6.Object remove ( int index ):移除指定 index 位置的元素,并返回此元素
        System.out.println("移除指定 index=1 位置的元素,并返回此元素="+list.remove(2));

        //7.Object set ( int index , Object ele ):设置指定index 位置的元素为 ele .相当于是替换
        list.set(2,"小");//将list中索引为2的位置替换成小
        System.out.println("set ( int index , Object ele )后="+list);

        //8.List subList ( int fromIndex , int tolndex ):返回从 fromIndex 到 tolndex 位置的子集合
        //包含fromIndex,但是不包含toIndex
        System.out.println("subList ( int fromIndex , int tolndex )后="+list.subList(1,4));
    }
}

课堂练习:
题目:
添加10个以上的元素(比如String “hello”),在2号位插入小王,获得第5g饿元素,删除第6个元素,修改第7个元素,再使用迭代器遍历集合,要求:使用List的实现类ArrayList完成

package com.xiaowang.list;

import java.util.*;

/**
 * @Author 小王
 * @DATE: 2022/4/17
 */

public class ListExercise {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        List list = new ArrayList();
        //1.利用for循环去添加是个以上的元素
        for (int i = 0; i < 12; i++) {
            list.add("hello"+i);
        }
        System.out.println("初始list="+list);
        //2.在2号为插入小王
        list.add(2,"小王");
        System.out.println("插入后list="+list);
        //3.获得第5个元素
        System.out.println("第5个元素="+list.get(4));
        //4.删除第6个元素
        list.remove(5);
        System.out.println("删除后list="+list);
        //5.修改第7个元素为A
        list.set(6,"A");
        //6.使用迭代器去遍历list集合
        Iterator iterator = list.iterator();//获取迭代器
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.print("next= "next+"\t");
        }
    }
}

14.4.3 List 遍历方式

List的三种遍历方式【ArrayList,LinkList,Vector】

方式1:迭代器
在这里插入图片描述
方式2:强制for
在这里插入图片描述
方法3:普通法for
在这里插入图片描述
注意:ArrayList 和 LinkList 和 Vector 的遍历一模一样,不会报错,因为都是List的实现子类

课堂练习:
题目:
在这里插入图片描述
分析:

package com.xiaowang.list;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

/**
 * @Author 小王
 * @DATE: 2022/4/17
 */
public class ListExercise2 {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        //用三种方式去实现很简单,换一下运行类型就行
        List list = new ArrayList();
        //List list = new LinkedList();
       // List list = new Vector();

        list.add(new Book("三国演义","罗贯中",100.1));
        list.add(new Book("红楼梦  ","曹雪芹",10.1));
        list.add(new Book("小李飞刀","古龙",50.1));
        list.add(new Book("水浒传  ","施耐庵",20.2));
        //按照价格,使用冒泡排序(升序)完成
        //直接调用排序方法sort就可以完成
        sort(list);
        //使用增强for去输出信息
        for (Object o :list) {
            System.out.println(o);
        }
    }
    //写一个方法去排序
    public static void sort(List list){//传入一个接口,无论是哪个实现类都可以使用
        //1.因为通过价格去排序,所以我们的先获取书的价格
        //2.list.get(index)方法获取的list集合中index处的元素,这个元素的类型根据实际来定,这里肯定就是一个Book类
        //   获得的是元素,肯定是不能去比较的啊,
        //3.所以我们通过创建Book对象,去接收get来的每一个对象
        //4.然后通过Book对象去调用getPrice方法,再进行判断
        //5.因为list有一个set方法,功能是进行替换。所以直接使用就行,不用再去利用中间变量
        for (int i = 0; i <list.size()-1 ; i++) {
            for (int j = 0; j <list.size()-1-i; j++) {
                Book book1 =(Book) list.get(j);
                //因为list.get(j)获得的是一个Object的类对象,这里我们要将其向下转型为Book,然后方便后面使用
                Book book2 =(Book) list.get(j+1);
                if (book1.getPrice()>book2.getPrice()){
                    list.set(j,book2);//也就是将j那个位置的元素,替换成book2元素
                    list.set(j+1,book1);
                }
            }
        }
    }
}
class Book{
    private String name;
    private String author;
    private double price;

    public Book(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public String getAuthor() {
        return author;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public String toString() {
        return "名称:" + name + "\t\t" +
                "作者:" + author + "\t\t" +
                "价格:" + price ;
    }
}

14.4.4 ArrayLIst 注意事项

1、可以存放所有元素,包括空,也可以包括很多的空

在这里插入图片描述

2、 ArrayLIst是由数组来实现数据存储的(查看源码可知)

3、 ArrayLIst 基本等同于Vector,区别就是: ArrayLIst不是线程安全的(没有synchronized修饰),Vector是线程安全的,方法
在这里插入图片描述
4、jdk7之前,在创建一个ArrayList的时候,是由指定一个10的长度的,在jdk8后就没有了,里面长度就是0了。

14.4.5 ArrayList底层结构分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
无参构造器----底层源码分析:

1、创建一个集合对象,
在这里插入图片描述
2、其调用的是无参构造器
在这里插入图片描述
3、调用这个ensureCapacityInternal方法,去确定:①确定是否要扩容;②然后再执行 赋值
在这里插入图片描述
4、确定最小扩容容量minCapacity
在这里插入图片描述
5、判断所需要的长度,和实际的长度是否匹配
在这里插入图片描述
6、真正的扩容机制
在这里插入图片描述
注:第一次使用 右位移去扩容的时候,长度使为0 的,所以相当于没有扩容,而是直接扩容长度为10,第二次及其以后的扩容就是在现有的长度上以1.5倍去扩容

有参构造器----底层源码分析:

1、创建指定大小的集合对象
在这里插入图片描述
2、查看构造器
在这里插入图片描述
3、然后进入add.()
以下步骤和无参的就区别不大了,最好是自己去查看底层去过一遍。

有参构造器和无参构造器的区别是:

①创建对象的时候,里面Object的长度不一样,无参的长度为0,有参的长度为:传入的大小

②在确定最小扩容量minCapacity的时候,值不一样,无参时候是传入的size+1 =0 ,而有参的minCapacity就是传入的长度大小

③在真正扩容时候,因为minCapacity的值不一样,所以oldCapacity的值也是不一样的

14.4.6 Vector注意事项

基本介绍:

1、Vectoer类定义说明
在这里插入图片描述

2、Vector底层也是一个对象数组:

protected Object[ ] elementData;

3、Vector是线程同步的,即使线程安全的,因为Vector类的操作方法都有synchronized修饰

4、在实际开发中,需要线程同步安全的时候,考虑使用Vector

14.4.7 vector底层结构分析

其实和ArrayList的底层差不多,最大区别就是,扩容机制的算法不一样
用以下案例代码分析:

public class Vector1 {
    public static void main(String[] args) {
        //无参构造器
        List vector = new Vector();
        for (int i = 0; i <10 ; i++) {
            vector.add(i);
        }
        vector.add(100);
    }
}

1、创建无参对象:new Vector()
在这里插入图片描述
2、进行add操作:vector.add(i);
在这里插入图片描述
3、判断是否要进行扩容:ensureCapacityHelper(int minCapacity)
在这里插入图片描述
4、当执行到vector.add(100);的时候,这时的长度就不够用了所以就要进行扩容
在这里插入图片描述
5、下面就是Vector的扩容机制
在这里插入图片描述

有参构造器和无参构造器的区别是:
就是在new一个对象的时候,里面的初始元素长度就是指定的了,每一次也是按照2倍去扩容

14.4.8 ArrayList和vector比较

在这里插入图片描述

14.4.9 LinkedList基本介绍

全面介绍:
在这里插入图片描述
LinkedList底层操作机制:
在这里插入图片描述
在这里插入图片描述
模拟一个简单的双向链表:

package com.xiaowang.list;

/**
 * @Author 小王
 * @DATE: 2022/4/21
 */
@SuppressWarnings("all")
public class LinkedList01 {
    public static void main(String[] args) {
        //模拟一个简单的双向链表
        //先创建几个Node对象
        Node xiaowang = new Node("xiaowang");
        Node jiangzai = new Node("jiangzai");
        Node xiao = new Node("小王");

        //形成双向链表
        //1.next指向下一个
        xiaowang.next =jiangzai;
        jiangzai.next =xiao;
        //2.pre指向前一个对象
        xiao.pre = jiangzai;
        jiangzai.pre =xiaowang;

        //遍历链表信息
        //1.因为重写了toString方法,所以可以直接使用toString去输出
        //2.又因为链表有first和last两个属性的,所以用first去指向第一个对象,
        //  last指向最后一个对象,我们就可以进行链表相关操作

        // 正序遍历链表
        Node first = xiaowang;
        Node last = xiao;
        //使用while循环去遍历
        System.out.println("==正序遍历==");
        while (true){
            if (first == null){
                break;//先判断一下第一个对象有没有,没有的话直接就退出程序
            }
            System.out.println(first);//输出first第一个的信息
            first = first.next;//将first通过next去往后移
        }
        //逆序遍历链表
        //和正序的区别不大
        System.out.println("==逆序遍历==");
        while (true){
            if (last == null){
                break;//先判断一下最后一个对象有没有,没有的话直接就退出程序
            }
            System.out.println(last);//输出first第一个的信息
            last = last.pre;//将last通过pre去往前移
        }

        //都知道链表的最大好处就是元素对象的增加删除,演示一下增加:
        //分析:
        //1.比如要在xiaowang和jiangzai中间增加一个露露
        //2.得先创建一个所要添加的对象(露露)对象
        //3.直接就可以将xiaowang的next去指向露露这个对象,露露的next指向jiangzai对象
        //4.再将jiangzai对象的pre指向露露对象,露露对象的pre指向小王对象
        //5.这样一个对象就增加成功了,删除一个对象的话其实是一样的道理,通过pre和next来指向就好了

        Node lulu = new Node("露露");
        xiaowang.next =lulu;
        lulu.next =jiangzai;
        jiangzai.pre=lulu;
        lulu.pre =xiaowang;

        first = xiaowang;
        last = xiao;

        System.out.println("==增加后的链表==");
        while (true){
            if (first == null){
                break;//先判断一下第一个对象有没有,没有的话直接就退出程序
            }
            System.out.println(first);//输出first第一个的信息
            first = first.next;//将first通过next去往后移
        }
    }
}
//先写一个Node类,这是代表链表的类
class Node{
    public Object item;//存放真正的数据
    public Node next;//指向后一个对象
    public Node pre;//指向前一个对象
    public Node(Object name){
        this.item=name;
    }

    @Override
    public String toString() {
        return "Node name = "+item;
    }
}
输出:
==正序遍历==
Node name = xiaowang
Node name = jiangzai
Node name = 小王
==逆序遍历==
Node name = 小王
Node name = jiangzai
Node name = xiaowang
==增加后的链表==
Node name = xiaowang
Node name = 露露
Node name = jiangzai
Node name = 小王

Process finished with exit code 0

常用方法CRUD(增删改查)

public class LinkedList02DiCeng {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        //先添加两个元素对象
        for (int i = 0; i < 2; i++) {
            linkedList.add(i);
        }
        //再添加
        linkedList.add(100);
        linkedList.add(100);
        //用强制for去遍历一下链表
        for (Object object :linkedList) {
            System.out.println(object);
        }

        /*删除
        linkedList.remove(0);//删除索引为0的元素对象
        linkedList.remove(kk);//删除kk这个元素的对象
        linkedList.remove();//删除第一个元素结点
        */

        linkedList.set(0,"小王");//将链表第一个元素替换成小王
        System.out.println("=======");//一个分隔符
        //经过替换操作后,再次强制for一遍元素
        for (Object object :linkedList) {
            System.out.println(object);
        }
        Object object = linkedList.get(0);//将第一个元素给object这个对象
        System.out.println("objrct ="+object);//查看objecy的值
        System.out.println(linkedList.getFirst());//获取第一个元素
        System.out.println(linkedList.getLast());//获取最后一个元素
    }
}

14.4.10 LinkedList底层结构分析

1、当只添加一个对象的时候:
下列代码分析:

public class LinkedList03 {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        System.out.println("linkedList="+linkedList);
    }
}

2、执行add语句时
在这里插入图片描述
3、第一次add最重要机制:
在这里插入图片描述

在这里插入图片描述
4、第二次重要机制:
在这里插入图片描述
5、删除时的重要机制:
在这里插入图片描述

14.5 Set接口
14.5.1 Set接口基本介绍

1、无序的(添加和去除的顺序不一致),并且没有索引
2、不允许重复元素,所以最多包含一个null
3、JDK API中Set接口的实现类有:

在这里插入图片描述

14.5.2 Set接口的常用方法

见14.3.2 Collection接口的常用方法~
演示一下add( ):

package com.xiaowang.set_;

import java.util.*;

/**
 * @Author 小王
 * @DATE: 2022/4/22
 */
@SuppressWarnings("all")
public class SetMethod {
    public static void main(String[] args) {
        //1.用Set接口的HashSet类来演示
        Set set = new HashSet();

        //2.添加几个对象元素
        set.add("tom");
        set.add("jack");
        set.add("tom");//重复添加试试,看看情况
        set.add(null);
        set.add(null);//再次添加null看看情况
        System.out.println("set= "+set);
        //此时输出:set= [null, tom, jack]
        /*结论1:
            1.1:由输出我们可以看出,Set接口的对象是无序的(添加和去除的顺序不一样)
            1.2:Set接口对象不能够重复,并且只能添加一个null
            1.3:输出的顺序是固定的,也就是你再次执行输出,顺序还是和现在一样,由固定的算法实现
        * */
        //证明1.3结论:五次输出都是一样的顺序,没有改变
        System.out.println("==验证输出顺序==");
        for (int i = 0; i < 5; i++) {
            System.out.println("sey= "+set);
        }
    }
}
输出 :
set= [null, tom, jack]
==验证输出顺序==
sey= [null, tom, jack]
sey= [null, tom, jack]
sey= [null, tom, jack]
sey= [null, tom, jack]
sey= [null, tom, jack]
14.5.3 Set接口遍历元素

因为Set接口是Collection的子接口,所以遍历方式和Collection一样,但是又有一点点区别

1、使用迭代器
2、增强for
3、普通的for循环不能用了,因为Set没有get( )方法,同时也没有索引

案例代码演示:

package com.xiaowang.set_;

import java.util.*;

/**
 * @Author 小王
 * @DATE: 2022/4/22
 */
@SuppressWarnings("all")
public class SetMethod {
    public static void main(String[] args) {
        //1.用Set接口的HashSet类来演示
        Set set = new HashSet();

        //2.添加几个对象元素
        set.add("tom");
        set.add("jack");
        set.add("tom");//重复添加试试,看看情况
        set.add(null);
        set.add(null);//再次添加null看看情况
        
        //遍历Set
        //方法1:迭代器
        System.out.println("==迭代器==");
        Iterator iterator =set.iterator();
        while (iterator.hasNext()) {
            Object obj =  iterator.next();
            System.out.println(obj);
        }
        //方法2:增强for
        System.out.println("==增强for==");
        for (Object o :set) {
            System.out.println("o= "+o);
        }
        /*
        //传统的for循环是有问题的,因为Set没有Get方法也没有索引
        for (int i = 0; i < set.size() ; i++) {
            System.out.println(set.get());//Set没有get方法
        }
        */
    }
}
输出:
==迭代器==
null
tom
jack
==增强for==
o= null
o= tom
o= jack

Process finished with exit code 0

14.5.4 HashSet 全面说明

1、HashSet实现了Set接口
在这里插入图片描述
2、HashSet实际上是HashMap
在这里插入图片描述
3、可以存放null,但是只能由一个null,即元素不能重复
在这里插入图片描述
4、HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即:不能保证输入的元素顺序和输出的元素顺序一样)
在这里插入图片描述
5、不能加入相同的元素/数据!!!!!

package com.xiaowang.set_;

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

/**
 * @Author 小王
 * @DATE: 2022/4/22
 */
@SuppressWarnings({"all"})
public class HashSet01 {
    public static void main(String[] args) {
        Set set = new HashSet();
        //不能添加重复元素/数据的理解:
        //分析下列语句能不能添加成功
        set.add("tom");//ok
        set.add("tom");//no

        //因为这里是两个不同的对象,所以可以添加成功,相当于有两个猫都叫jack而已,并不是同一个
        set.add(new Cat("jack"));//ok
        set.add(new Cat("jack"));//ok

        //以下是一个经典的面试题,在这里我们必须要去查看add底层,
        // 分析它对add中重复元素/数据的判断定义,
        set.add(new String("lulu"));//ok
        set.add(new String("lulu"));//no

        System.out.println("set= "+set);
    }
}
class Cat{
    private String name;

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

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}
输出:
set= [lulu, tom, Cat{name='jack'}, Cat{name='jack'}]
14.5.5 HashSet 底层分析

HashSet 底层说明:
因为HashSet 底层是HashMap,然后呢HashMap的底层是(数组+链表+红黑树)

14.5.5.1 模拟数组+链表

为了方便理解,我们先模拟数组+链表结构去分析这个底层

其实就是,在数组的某一个索引位置,去创建一个链表,通过列表的next去指向,一般情况,这种时候的数组是table(一个Node类型的数组),不将所有数据放在数组里面去,是因为效率太低了,
在这里插入图片描述

案例代码分析:

package com.xiaowang.set_;

/**
 * @Author 小王
 * @DATE: 2022/4/22
 */
@SuppressWarnings("all")
public class HashSetStructure {
    public static void main(String[] args) {
        //先常见一个Node类的数组table
        Node [] table =new Node[16];
        //创建一个结点对象插入table数组
        Node tom = new Node("tom", null);//下一个未指向所以先设置为null
        table[2] = tom;//将索引为2的位置存放tom这个结点
        //再创建一个Node对象去挂载到tom后面,这时使用next即可
        Node jack = new Node("jack", null);
        tom.next=jack;
        //然后同理操作,再在后面挂载对象,这样就能形成一个链表
        //然后也可以在其他索引位置上用同样的方法去创建链表
        //这样就形成了一个数组+链表
        //这样会提高效率
    }
}
class Node{
    Object item;//结点真正数据存放
    Node next;//指向下一个结点,为了演示们可以不用pre

    public Node(Object item, Node next) {
        this.item = item;
        this.next = next;
    }
}
14.5.5.2 HashSet底层机制说明

1、我们先看结论:
在这里插入图片描述
通俗理解:
//1、我们添加几个元素的时候,都依次按照先后顺序先获得这几个元素的hash值,然后通过方法去转成所对应的所要添加在table表上的索引值

//2、第一次加的时候,所要添加的索引值的table数组肯定是null的,所以直接将元素给添加到对应的索引处

//3、如果所要添加的元素索引值是一样的,并且第一个元素已经添加到对应哪个索引处了后,那么这时候就得调用equals方法去判断两个元素的内容相同不,要是相同,那么直接放弃添加,如果没有就放在索引值所在元素的后面,相当于通过前面那个元素的next去挂载

//4、然后再去看下一个元素所对应的索引值,如果table对应的索引位置没有元素,那么直接加入,要是有元素,那么就得用第3步去判断了

//5、要是某索引处的链表达到了8个长度的话,并且table表的长度超过64,那么这时就会自动变成红黑树

//6、要是某索引处的链表达到了8个长度的话,但是table表的长度没有超过64,那么这时就会自动去扩容table表,然后再去变成红黑树

2、HashMap底层具体分析:

(1)、第一次add( )

package com.xiaowang.set_;

import java.util.HashSet;

/**
 * @Author 小王
 * @DATE: 2022/4/22
 */
@SuppressWarnings({"all"})
public class HashSetSource {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet.add("xiao");
        hashSet.add("java");
        System.out.println("set= "+hashSet);

        //
        /*
        1.执行new HashSet();创建一个对象,
          public HashSet() {
               map = new HashMap<>();//其实HashSet实际还是一个HashMap对象
           }
        2.执行 hashSet.add("java");
           public boolean add(E e) { //此时e是我们所需要添加的数据“java”
               return map.put(e, PRESENT)==null;//PRESENT=> private static final Object PRESENT = new Object();
                                                //PRESENT是一个static的对象,主要就是用来占位的
            }
        3.执行 put(e, PRESENT)
           public V put(K key, V value) {//K key:是我们的具体要添加的数据,V value:就是(static) PRESENT = new Object();
               return putVal(hash(key), key, value, false, true);//注意这里有一个:hash(key)方法
           }
        4.执行 hash(key):
           static final int hash(Object key) {//这里就是计算hash值
               int h;  //用一个int去接收所要添加的元素的hash值
               return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
                  //(h = key.hashCode()) ^ (h >>> 16); 这个就是计算所要添加元素的hash值的方法,>>>无符号右移,^是异或
               此时显然key="java != null",所以就返回 key的hash值
           }

          5.执行 putVal()
          final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;//都是一些辅助变量,下面要用的
        //1.此时的table就是hashMap里面的table属性,这是一个Object[] 数组;tab是一个结点数组 Node<K,V>[] tab
        //2.这里的if就是判断第一次hashMap里面的table为不为null,长度是不是0,
        //3.第一次table肯定为null,所以就执行n = (tab = resize()).length;
        //4.首先执行resize()方法:(后面查看)
            最终返回的是扩容后的Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            也就是一个扩容后的数组对象,第一次扩容大小为16,有一个临界值:0.75*长度,第一次是12,也就是当添加到12个的时候,就得再扩容了
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
            
        //5.这里的if:就是去匹配hash值和索引值
        if ((p = tab[i = (n - 1) & hash]) == null)// 用p去接收以下这个索引元素,然后去判断下此时的索引有没有元素对象
            tab[i] = newNode(hash, key, value, null);//第一次嘛,肯定没有对象,所以就将resize()方法中结点元素 newNode给添加到对应的索引处
        else {//要是此时对应的索引处有元素,那么就进行下面操作:这就死第二次添加后了
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
          */
    }
}

这是resize()的具体方法:

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

2、第二次add()
① 如果元素不相等,那么就直接挂载到后面即可,方法相当于就是第一次的add()

① 如果元素相等,进行分析,前面的是一样的,只看关键部分

**if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        //5.这里的if:就是去匹配hash值和索引值
        if ((p = tab[i = (n - 1) & hash]) == null)// 用p去接收以下这个索引元素,然后去判断下此时的索引有没有元素对象
            tab[i] = newNode(hash, key, value, null);//第一次嘛,肯定没有对象,所以就将resize()方法中结点元素 newNode给添加到对应的索引处
        else {//要是此时对应的索引处有元素,那么就进行下面操作:这就是第二次添加后了,此时要添加的是“java”
        //1.和前面一样的方法。获得这次要添加的“java”的hsah值后,去进行比较上面的if,显然不为null,所以执行这里的else,有三种情况
            Node<K,V> e; K k;//定义一些辅助变量
             //2.p是即将要添加索引位置对象,此时去对比此时已经存在的索引位置的hash值和即将要添加的“java”的索引值做比较
             //  (1)当现在索引位置的hash值和即将要添加的hash相等的时候,
             //  (2)并且满足下面其中一种情况:
             //     (2.1)现在索引位置的对象和即将添加的对象是同一个对象的时候,key相当嘛
             //     (2.2)即将添加对象不为null,并且,将添加对象的内容和现在索引位置对象的内容进行equals比较相等
             //  (3)就将p赋给e,然后返回给上一步,这时不是null的话,那就证明添加失败,
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
               // 注意这里的equals方法是由程序员决定的,因为equals在不同的对象里面是不一样的方法,都被重写了的 
                e = p;
            //3. 如果不是同一个对象的时候,去判断现在索引位置的对象里的链表是不是一颗红黑树
            //   如果是,那就去进行putTreeVal()方法去进行红黑树操作
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //4.如果不是同一个对象,内容也不相等,并且也不是红黑树,那就用for循环进行下面判断
            //  (1)去判断索引位置对象的下一个对象是否存在,如果存在就直接将要添加的元素给添加到后面即可,然后退出
            //  (2)如果存在,就将那个对象的hash值和要添加的hash值进行比较,和上面2一样的,如果相同满足了,就放弃添加直接退出
            //  (3)如果以上都不满足,那就直接将这个对象赋值给e变量
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //3.如果现在有了e变量,也就是找到了,那么将其传给value即可
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }**

总结:
在table表中添加一个对象f

1.先判断table表中有没有这个对象元素,要是没有就直接添加到对应索引位置处即可

2.要是table表中有了一个对象(这里用t表示)的时候:(并且是一个链表形式)
2.1.先获取 t 所在索引处的第一个对象的hash值和 f 对象的hash值,进行比较
2.2.如果两个对象的hash值相等,是同一个对象并且内容相等或者不为null的话,那就直接放弃添加
2.3.如果不是同一个对象,并且也不是红黑树,那就去和这个hash值对应索引处的链表的下一个对象去比较
2.4.要是相等就放弃添加,要是不相等,并且链表下一个对象为null,就添加到其下一个对象后面

3.也就是,所要添加的对象元素,要和其对应索引处的链表里面的对象元素,依次进行比较,如果找到了相同的就放弃添加,没有找到相同的并且最后还有null位置,那就添加到后面即可

4.要特别注意的是,链表的长度达到8了后,就要去判断table表的长度够不够64了,要是不够就扩容到64,再进行红黑树化

注意:这里的equals方法是由程序员决定的,因为equals在不同的对象里面是不一样的方法,都被重写了的

14.5.5.3 HashSet的扩容和转成红黑树

结论:
在这里插入图片描述
简单看一下代码案例分析,红黑树就不细讲了,后面数据结构的时候讲

案例:

package com.xiaowang.set_;

import java.util.HashSet;
import java.util.Objects;

/**
 * @Author 小王
 * @DATE: 2022/4/23
 */
@SuppressWarnings({"all"})
public class HashSetKuoRong {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        /*
        1. HashSet 底层是 HashMap ,第一次添加时, table 数组扩容到16,
        临界值( threshold )是16*加载因子( loadFactor )是0.75=12
        //也就是说,当add到12的时候,table表就会开始*2的去扩容了
        通过debug去验证
        */

        /*
        for (int i = 0; i <100 ; i++) {
            hashSet.add(i);
        }
        */

        /*
        如果 table 数组使用到了临界值12,就会扩容到16*2=32,
        新的临界值就是32*0.75=24,依次类推
         */

        /*
        3.在Java8中,如果一条链表的元素个数到达 TREEIFY THRESHOLD (默认是8)
        并且 table 的大小>=MIN TREEIFY CAPACITY 默认64),就会进行树化(红黑树),
        否则仍然采用数组扩容机制
        */
        //为了演示要在table形成链表,就要确保所要添加的值的hash值相等
        for (int i = 1; i <=12 ; i++) {
            hashSet.add(new A(i));
            //这可以保证添加的值的hash值一样,但是内容不一样。debug查看
            //通过debug可以看出,当我们所要添加的对象,将链表存满八个过后,这时tbale表的长度是16
            //所以就要对table进行一次一次的扩容,扩容到64后,然后链表就会变成红黑树。
        }
        System.out.println("hashSet ="+hashSet);
    }
}
class A{//写一个类来确定输入的值的信息
    private int n;
    public A(int n) {
        this.n = n;
    }
    //重写一下类的hashCode方法,不去重写equals,这样的化去确保,被一个对象的hashCode值相等
    @Override
    public int hashCode() {
        return 100;
    }
}

在这里插入图片描述

在这里插入图片描述
注:
在达到临界值扩容的时候情况是;只要添加到了临界值个数的对象就会触发扩容机制,也就是说,无论是在table表上还是链表上的对象,只要个数达到了临界值,都会扩容

14.5.5.4 HashSet课堂练习

A01:
/*创建一个Employee类,包含name和age属性要求:

  • 1.创建三个Employee对象放入hashSet里面
  • 2.如果name和age完全相同,那么就不能添加*/

分析代码:

package com.xiaowang.set_;

import java.util.HashSet;
import java.util.Objects;

/**
 * @Author 小王
 * @DATE: 2022/4/24
 */
/*创建一个Employee类,包含name和age属性要求:
* 1.创建三个Employee对象放入hashSet里面
* 2.如果name和age完全相同,那么就不能添加*/
@SuppressWarnings({"all"})
public class HashSetExercise {
    public static void main(String[] args) {
        //创建一个hashSet
        HashSet hashSet = new HashSet();
        //存放三个Employee对象
        hashSet.add(new Employee("tom",12));
        hashSet.add(new Employee("jack",15));
        hashSet.add(new Employee("tom",12));
        //此时因为是三个不同的对象,所以hash值肯定不同,所以都可以加进去
        System.out.println(hashSet);

        //为了实现同名同年龄的不能添加,那么就是要让这两个对象的hash值相同,这样就不会被添加了
    }
}
class Employee{
    private String name;
    private int age;
    public Employee(String name,int age){
        this.name=name;
        this.age=age;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "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;
    }
    //为了实现同名同年龄的不能添加,那么就是要让这两个对象的hash值相同,所以要重写hashCode方法
    //那肯定要重写他们的equals方法去判断相同不啦,这是程序员自己写的吖,很具不同情况写的equals


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
输出:
[Employee{name='jack', age=15}, Employee{name='tom', age=12}]

A02:
在这里插入图片描述
代码:

package com.xiaowang.set_;

import java.util.HashSet;
import java.util.Objects;

/**
 * @Author 小王
 * @DATE: 2022/4/25
 */
@SuppressWarnings({"all"})
public class HashSetExercise02 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add(new Employee1("xiaowang",5200.0,new MyDate(2000,11,8)));
        hashSet.add(new Employee1("jiangzai",5000.0,new MyDate(1999,11,11)));
        hashSet.add(new Employee1("xiaowang",5200.0,new MyDate(2000,11,8)));
        System.out.println(hashSet);
    }
}
class Employee1{
    private String name;
    private double salary;
    private MyDate birthday;

    public Employee1(String name, double salary, MyDate birthday) {
        this.name = name;
        this.salary = salary;
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Employee1{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                ", birthday=" + birthday +
                '}';
    }
//重写hashCode,使其去判断是否相同,这里因为要调用MyDate类对象,所以在MyDate类里面也应该去重写一下hashCode方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee1 employee1 = (Employee1) o;
        return Objects.equals(name, employee1.name) && Objects.equals(birthday, employee1.birthday);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, birthday);
    }
}
class MyDate{
    private int year;
    private int month;
    private int day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    @Override
    public String toString() {
        return year+"."+month+"."+day;
    }
//重写hashCode方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyDate myDate = (MyDate) o;
        return year == myDate.year && month == myDate.month && day == myDate.day;
    }

    @Override
    public int hashCode() {
        return Objects.hash(year, month, day);
    }
}

输出:
[Employee1{name=‘xiaowang’, salary=5200.0, birthday=2000.11.8}, Employee1{name=‘jiangzai’, salary=5000.0, birthday=1999.11.11}]

14.5.6 LinkedHashSet 全面说明

1.基本介绍:
在这里插入图片描述
底层机制:
在这里插入图片描述
通俗理解:
其实这里存放每一个添加的对象是放在table表里面的,但是呢顺序是这样理解的:每一个添加的对象元素都有一个before(pre)和after(next)属性,每添加一次,前一次的next就指向下一次的对象元素,下一次添加对象元素的pre就只想上一次添加的元素对象,这样就形成了双向链表。

LinkedHashSet底层机制说明:

其实所有的添加的过程底层机制都差不多,最大的区别呢就是以下:
在这里插入图片描述
在这里插入图片描述
添加分析:
现在使用以下三个添加去分析:

 set.add(new String("AA"));
        set.add(456);
        set.add(456);
        set.add(new Customer("王",200));

在这里插入图片描述
在这里插入图片描述

14.5.7 Map接口
14.5.7.1 Map接口实现类的特点

注:这里讲的是JDK8的Map接口特点哦:

1、Map与Collection是并列存在的,用于保存又映射关系的数据:Key—Value(在这里,Key存放的是数据,并且后面是去计算Key的hash值取进行操作,Value就是前面的Present,一个final类型的对象。)

2、Map 中的 Key 和Value 可以是任意类型的数据,到时候会封装到HashMap$Node对象中
在这里插入图片描述

3、Map 中的 Key 不能重复,原因HashSet一样

4、Map 中的Key 可以为null,value也可以是null,但是注意,Key为null只能有一个,value为null可以是多个

5、Key 和 value 存在一对一的关系,即通过指定Key 总能找到一个对应的value

案例分析:

package com.xiaowang.map_;

import java.util.*;

/**
 * @Author 小王
 * @DATE: 2022/4/26
 */
@SuppressWarnings({"all"})
public class Map_ {
    public static void main(String[] args) {
        //Map接口实现类的讲解:
        //1、Map与Collection是并列存在的,用于保存又映射关系的数据:Key—Value
        // (在这里,Key存放的是数据,并且后面是去计算Key的hash值取进行操作,
        // Value就是前面的Present,一个final类型的对象。)
        Map hashMap = new HashMap();
        //注意:Map实现类的添加元素使用put方法
        hashMap.put("No1","小王");//k-v

        // 2、Map 中的 Key 和Value 可以是任意类型的数据,到时候会封装到HashMap$Node对象中
        //    查看table表就能发现

        //3、Map 中的 Key 不能重复,原因HashSet一样
        hashMap.put("No1","江仔");//key不能重复,所以不能添加,
        //但是这里有一个替换操作,也就是将原来key对应的value值替换成现在的value值,所以现在的No1的值是江仔

        //4、Map 中的Key 可以为null,value也可以是null,但是注意,Key为null只能有一个,value为null可以是多个
        hashMap.put(null,null);
        hashMap.put(null,"小王");//不可以,也做了替换的操作

        //5、Key 和 value 存在一对一的关系,即通过指定Key 总能找到一个对应的value
        System.out.println(hashMap.get("No1"));//因为替换了,所以No1输出:江仔
        
        System.out.println("Map= "+hashMap);
    }
}
输出:
江仔
Map= {null=小王, No1=江仔}

在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值