当前位置: 首页 > article >正文

java集合(1)

引入

        我们我们保存多个数据时大多是使用数组,但数组有许多不足之处

  1. 数组的长度必须在开始时指定,而且长度一旦确定便不能修改
  2. 保存的必须为同一类型的元素
  3. 数组增加/删除元素较麻烦
//数组扩容
int[] num1 = new int[1];
num1[0]=1;//创建数组并赋值
int[] num2 = new int[num1.length+1];
for (;;){}//将原数组内容复制到新数组(省略)
num2[num2.length-1]=1;

        相比起来,集合则有很多优点:

  1. 可以动态的保存任意类型的多个元素
  2. 提供了一系列方便操作对象的方法:add、remove、set、get等
  3. 使用集合添加/删除元素的代码较简洁

集合框架的层次结构

        Java集合框架主要包括两大接口:CollectionMap,它们分别代表了两种不同的集合类型。

  • Collection接口:继承自单列集合,存放单个的对象,所以其子类 List 和 Set 也都是单列集合。它定义了集合的基本操作,如添加(add)、删除(remove)、遍历(iterator)等。Collection接口有两个主要的子接口:ListSet
    • List:有序集合,允许元素重复。主要实现类有ArrayListLinkedList等。
    • Set:无序集合,不允许元素重复。主要实现类有HashSetLinkedHashSetTreeSet等。
  • Map接口:双列集合,存放的数据往往两两一组。将键(Key)映射到值(Value)的对象,也就是K-V,一个键可以映射到最多一个值。主要实现类有HashMapHashtableTreeMap等。
ArrayList arrayList = new ArrayList();//单列集合
arrayList.add("Rookie");
arrayList.add("jackey");
HashMap hashMap = new HashMap();//双列集合
hashMap.put("Key1","小明");
hashMap.put("Key2","小黄");
java.util.Collection
├── java.util.List
│   ├── java.util.ArrayList  
│   ├── java.util.LinkedList 
│   ├── java.util.Vector 
├── java.util.Set  
│   ├── java.util.HashSet  
│   │   └── java.util.LinkedHashSet  
│   ├── java.util.LinkedHashSet<E>  
│   ├── java.util.TreeSet<E>  
│   │   └── java.util.concurrent.ConcurrentSkipListSet<E>  
│   └── java.util.SortedSet<E>  
│       ├── java.util.TreeSet<E>  
│       └── java.util.concurrent.ConcurrentSkipListSet<E>  
├── java.util.Queue<E>  
│   ├── java.util.Deque<E>  
│   │   ├── java.util.ArrayDeque<E>  
│   │   └── java.util.LinkedList<E>  
│   ├── java.util.LinkedList<E>  
│   ├── java.util.PriorityQueue<E>  
│   └── java.util.concurrent.BlockingQueue<E>  
│       ├── java.util.concurrent.ArrayBlockingQueue<E>  
│       ├── java.util.concurrent.LinkedBlockingQueue<E>  
│       └── 更多并发阻塞队列...  
└── java.util.Map<K,V>  
    ├── java.util.HashMap<K,V>  
    │   └── java.util.LinkedHashMap<K,V>  
    ├── java.util.TreeMap<K,V>  
    ├── java.util.Hashtable<K,V>  
    ├── java.util.Properties  
    ├── java.util.WeakHashMap<K,V>  
    ├── java.util.IdentityHashMap<K,V>  
    └── java.util.concurrent.ConcurrentHashMap<K,V>

Collection

特点

1、继承自 Iterable 接口

public interface Collection<E> extends Iterable<E> {

2、其子类可存放多种类型的元素,每个元素都可以是 Object类及其子类

3、有些 Collection 的实现类,可以存放重复的元素,有些则不能

4、有些 Collection 的实现类存放数据是有序的(如 List 存放顺序和取出数据是一致的),有些则为无序(如 Set 存放和取出的顺序并不完全一样)

5、Collcetion 接口没有直接的实现子类,都是通过他的子接口 Set 和 List 来实现的

常用方法

        因为接口不能被实例化,所以我们以其实现该接口的子类 ArrayList 来演示

1、add()添加单个元素

ArrayList eg = new ArrayList();
eg.add("木楠");
eg.add(10);//这里系统会自动装箱,将int转化为integer相当于eg.add(new Integer(10));
eg.add(true);
System.out.println("example"+eg);

执行结果:example:[木楠, 10, true] 

2、 remove()删除元素,该方法有两种重载版本:
        1、通过索引查找指定位置的元素,该方法返回 Object 也就是所查找的元素
        2、通过元素内容查找指定的元素,该方法返回 boolean 表是否查找成功

        如果元素内的某个 int 型变量和已存在的索引相冲突,且想通过查找内容来删除某个 int 型变量,需使用 Integer.valueOf()

ArrayList eg = new ArrayList();
eg.add("木楠");
eg.add(10);
eg.add(true);
eg.add(1);
eg.add(2);
eg.remove(0);//删除索引为0的元素
eg.remove(true);//删除元素true
eg.remove(Integer.valueOf(2));//索引2与元素2相冲突,删除内容为2的元素

3、contains()查找某个元素是否存在,并返回boolean值,存在为 true 不存在为 false

4、size()返回集合内元素个数

5、isEmpty()判断集合是否为空

6、clear()清空集合内所有元素

7、addAll()将一个集合(或集合的子集)的所有元素添加到另一个集合中。这个方法有两个不同的重载版本:
        1、num1.addAll(num2):直接将集合num2内的所有元素添加到num1中
        2、num1.addAll(2,num2):将集合 num2 中的所有元素从 num1 的指定索引“2”插入。如果index超出 num1 的当前大小,则会在 num1 的末尾添加 num2 的所有元素。如果index为负数,则报错。

8、containsAll()检查调用该方法的集合 (调用集合) 是否包含指定集合 (参数集合) 中的所有元素。

9、removeAll()从调用该方法的集合 (调用集合) 中移除所有包含在指定集合 (参数集合) 中的元素。它会遍历参数集合中的每一个元素,并从调用集合中移除所有与之相等的元素。如果调用集合由于此操作而发生了更改(即至少有一个元素被移除),则返回 true;否则返回 false

ArrayList list1 = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
ArrayList list2 = new ArrayList(Arrays.asList(3,4));

//从list1中移除所有在list2中的元素
boolean result = list1.removeAll(list2);
//此时list1:[1,2,5],result为true
-------------------------------------------------------------
ArrayList list1 = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
ArrayList list2 = new ArrayList(Arrays.asList(5,6));

//从list1中移除所有在list2中的元素
boolean result = list1.removeAll(list2);
//此时list1:[1,2,3,4],result为true
-------------------------------------------------------------
ArrayList list1 = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
ArrayList list2 = new ArrayList(Arrays.asList(7,6));

//从list1中移除所有在list2中的元素
boolean result = list1.removeAll(list2);
//此时list1:[1,2,3,4,5],result为false

遍历方法

一、使用 Iterable (迭代器)

上面我们提到 Collection 的特点之一就是继承自 Iterable 接口
        1、Iterable 对象俗称迭代器,主要用于遍历 Collection 集合中的元素。
        2、所有实现了 Collection 接口的集合类都有一个 Iterable() 方法,用以返回一个实现了 Iterator 接口的对象,即返回一个迭代器。
        3、
        4、Iterable 仅用于遍历集合,Iterable 本身并不存放对象。

使用方法

        首先使用 Iterator 自定义迭代器名称 = 集合名称.iterator() 得到该集合的迭代器,然后再使用 hasNext() 和 next() 两方法。
        刚开始 next() 方法会指向集合的第一个元素之前(类似于指针),每调用一次该方法就会将指针下移,并将该位置的元素返回(返回为 Object 类),
        而 hasNext() 则会判断下面是否存在元素,并返回 Boolean 值。还有 remove() 方法因为不常用,不再深入了解。
        因此我们通常使用while循环,使用 hasNext() 方法作为判断条件判断下面是否仍存在元素,如果存在则使用 next() 方法。

        Collection list1 = new ArrayList();
        list1.add(new book("枫原万叶",18));
        list1.add(new book("阿蕾奇诺",19));
        list1.add(new book("那维莱特",20));
        Iterator myit = list1.iterator();
        //使用while循环,输入itit可快捷生成
        while (myit.hasNext()) {
            Object next =  myit.next();
            System.out.println(next);
        }
        class book{......}

        此时遍历完成,而迭代器指向该集合最后一个元素,如果再想遍历并调用 next() 方法,其就会抛出异常 NoSuchElementException ,如果想要重新遍历,则需重置遍历器:

        Iterator myit = list1.iterator();

        iterator.remove() 方法用于在遍历过程中安全地删除元素。但是,这个调用必须在 next() 方法之后,否则将会抛出 IllegalStateException。这是因为 remove() 方法需要知道要删除的是哪个元素,而 next() 方法提供了这个信息。如果在调用 next() 之后没有立即调用 remove(),则迭代器会失去这个信息,因此不允许调用 remove()

二、使用for循环增强

        增强 for 循环,可以代替iteration迭代器。增强 for 循环就是简化版的 iteration,本质一样,只能用于遍历集合和数组。

        while (myit.hasNext()) {
            Object next =  myit.next();
            System.out.println(next);}
---------------------------------------
        for (Object book:list1){
            System.out.println(book);}

同时也有几点需要注意

        1、for循环是在 Collection 之中,因此集合、数组都可使用
        2、增强for循环底层调用的也是 Iterable (迭代器),可以理解为简化版本
        3、输大写字母“I”+enter可快捷生成增强for循环

List接口

介绍

        前面已有介绍,List 接口代表了一个有序集合,是Collection接口的一个子接口,上文所介绍的方法为Collection接口内的方法,Set和List接口都可使用,但该部分介绍的为子接口List,Set接口相关的类不可使用。
        List接口有以下几个特点:

1、List集合类中元素有序(即添加顺序与取出顺序一致),且可重复。
2、List集合中的每个元素都有其对应的顺序索引,且从0开始。

List arrayList = new ArrayList(Arrays.asList("一", "二", "三", "四", "五"));
System.out.println(arrayList.get(3));
//输出集合arrayList的第三个元素,即"四"

常用方法 

1、add()添加元素,有两种重载
        a、直接在集合尾部添加元素
        b、在指定索引处添加元素,后方的元素后移

List list1 = new ArrayList(Arrays.asList("一", "二", "三"));
list1.add("新元素");//直接在集合尾部添加元素
list1.add(2,"打断");//在指定索引处添加元素,后方的元素后移
//此时list1:[一, 二, 打断, 三, 新元素]

2、addAll() 使用同Collection接口内的addAll()方法,有两个重载版本。
3、get(int index)获取指定索引处的元素。
4、indexOf()获取指定元素在集合内首次出现的索引位置。lastIndexOf()获取指定元素在集合内最后一次出现的索引位置。
5、remove()删除指定索引位置的元素,并返回该元素。
6、set()将指定索引位置的元素设置(修改)为指定元素。

list1.set(2,"yi");//将索引为2的元素设置为"yi"

7、subList(int a,int b)返回索引[a,b)的子集合(包含a不包含b)

List list1 = new ArrayList(Arrays.asList("一", "二", "三", "四", "五"));
System.out.println(list1.subList(2,4));
//输出[三, 四]

遍历方法

一、使用iterator迭代器

        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.print(next);
        }

二、使用增强for循环

        System.out.println();
        for (Object o :list1) {
            System.out.print(o);
        }

三、使用普通for循环

        System.out.println();
        for (int i = 0; i < list1.size(); i++) {
            System.out.print(list1.get(i));
        }

 ArrayList

        ArrayList 实现了 List 接口,是一个可以动态调整大小的数组。与普通的数组相比,ArrayList 提供了更加灵活和强大的功能,比如动态扩容、自动管理内存等。

特点:

        一、可以放入任何元素,包括空值,且可以放入多个空值
        二、ArrayList是由数组来实现数据存储的
        三、ArrayList基本等同于Vector,但ArrayList执行效率高,非线程安全,多线程时不推荐使用
        四、扩容机制:
                1、ArrayList中维护了一个Object类型的数组elementData,因为Object为所有类的父类,所以ArrayList可以放入任何元素。
                2、当创建ArrayList对象时若使用的为无参构造器,则初始的elementData容量为0,首次添加元素,系统会将elementData扩容为10,之后再扩容,则会扩容至1.5倍大小。
                3、如果使用有参的构造器则初始elementData容量为指定的大小,如需扩容,也是扩容为原来的1.5倍大小。

 Vector

        与 ArrayList 类似,Vector 也允许存储重复的元素,并且元素是有序的,但 Vector 是线程安全的。这也意味着在多线程环境中,Vector 的性能可能不如非线程安全的 ArrayList,但可保证线程安全。
        Vector的底层也是一个对象数组,可放入任何元素,这一点与 ArrayList 相同。
        扩容机制则不同,如果使用无参构造器,则其默认长度也为10,但第二次扩容时则会直接扩容至两倍,使用有参构造时同理,容量为指定的大小,但扩容时也会扩容至两倍。

LinkedList

底层操作机制

        一、LinkedList底层维护了一个双向链表,可添加任何元素,非线程安全,多线程时不推荐
        二、LinkedList中维护了两个属性 first 和 last 分别指向首节点和尾节点
        三、每个节点(Node对象)里面又维护了prev、next、item三个属性、其中通过 prev 指向前一个,通过 next 指向后一个,最终实现双向链表
        四、LinkList添加和删除元素,不是通过数组来实现的,只需改变前一个节点的和后一个节点的首尾,共四个属性即可,因此效率较高

         双向链表在后面会详细讲解,这里我们创建一个Node类来模拟双向链表来帮助理解。

public class Go {
    public static void main(String[] args) {
        //模拟双向链表,先创建节点
        Node no1 = new Node("1、半阙");
        Node no2 = new Node("2、新词");
        Node no3 = new Node("3、三篇");
        Node no4 = new Node("4、旧赋");
        //连接三个节点,形成双向链表
        no1.next=no2;
        no2.next=no3;
        no3.next=no4;
        no4.prev=no3;
        no3.prev=no2;
        no2.prev=no1;
        //创建首尾节点
        Node first=no1;
        Node last=no4;
        System.out.println("—————正序遍历—————");
        while (true){
            if (first==null)
                break;
            System.out.println(first);
            first=first.next;
        }first=no1;//重置first节点
        //倒序遍历
        System.out.println("—————倒序遍历—————");
        while (true){
            if (last==null)
                break;
            System.out.println(last);
            last=last.prev;
        }last=no4;//重置last节点
    }
}
class Node{
    public Object item;
    public Node next;
    public Node prev;

    public Node(Object item) {
        this.item = item;
    }

    @Override
    public String toString() {
        return "Node{" +
                "item=" + item +
                '}';
    }
}

执行结果:

—————正序遍历—————
Node{item=1、半阙}
Node{item=2、新词}
Node{item=3、三篇}
Node{item=4、旧赋}
—————倒序遍历—————
Node{item=4、旧赋}
Node{item=3、三篇}
Node{item=2、新词}
Node{item=1、半阙}

        理解 LinkedList的结构后,我们再来看 LinkedList 的增删改查所需操作

通常缩写CRUD代表增删改查:增C (Creat) 查R (Read)改U (Update) 删D (Delete)

增C (Creat)

        添加共有三种方法,分别是add(),addFirst(),addLast(),
        其中add()有两种重载方法:
1、add(E e) 等同于 addLast() 表示直接在集合的尾部添加元素。
2、add(int index, E element)表示在指定索引处添加元素
        addFirst()表示在集合首部添加元素;addLast()表示在集合尾部添加元素

        LinkedList list1 = new LinkedList();
        list1.add(1);//直接添加元素"1"
        list1.addLast(2);//在尾部添加元素"2"
        list1.addFirst(3);//在开头添加元素"3"
        list1.add(1,"new");//将元素"new"添加到序列1处

        此时list1:[3, new, 1, 2] 

删D (Delete)

        删除的方法与增加的方法大体相同,但也略有区别:删除共有三种方法,分别是remove(),removeFirst(),removeLast(),
        其中remove()有三种重载方法:
1、remove()无参表示删除首个元素,等同于removeFirst()
2、remove(int index)表示删除指定索引处的元素。
3、remove(Object o)表示删除集合内第一个出现的指定元素,并将该元素返回。
        removeFirst()表示删除集合的首个元素;addLast()表示删除集合的最后一个元素

        LinkedList list1 = new LinkedList(Arrays.asList("零","一","二","三","四","五","六","七"));
        list1.remove(2);//删除第2个元素"二"
        list1.remove("三");//删除元素"三"
        list1.removeFirst();//删除第一个元素,即"零"
        list1.removeLast();//删除最后一个元素,即"七"

此时list1:[一, 四, 五, 六] 

改U (Update)

        set(int index, E element) 表示将指定索引处的元素修改为所给值。

        LinkedList list1 = new LinkedList(Arrays.asList("零","一","二"));
        list1.set(1,"new");//将序列1处的元素修改为"new"

此时list1:[零, new, 二] 

查R (Read)

        get(int index)查看指定索引处的元素。

        因为 LinkedList 实现了 List 接口,所以上文举例的三种遍历方式(iterator迭代器、增强for循环、普通for循环)都可使用。

ArrayList和LinkedList比较

底层结构增删效率改查效率
ArrayList可变数组较低(数组扩容)较高(通过索引)
LinkledList双向链表较高(链表追加)较低(逐个遍历)

1、如果改查操作较多,选择ArrayList
2、如果增删操作较多,选择LinkedList
3、一般来说在程序中,查询操作较多,因此大部分都选择ArrayList
4、在同一项目中,不同的模块可根据需求来选择不同的类

Set接口

        Set接口继承自Collection接口,是Java集合框架中的一个基础接口。Set接口的实现类主要包括HashSet、LinkedHashSet、TreeSet等。有以下几个特点:
        1、无序(添加和取出的顺序不一定一样,且只会按照该顺序开读取,下次读取时也不会改变),没有索引
        2、不允许有重复元素(系统会忽略添加的重复元素,并返回false),最多包含一个null
        3、因Set接口继承自Collection接口,所以遍历方式通用,但无法使用普通for循环,因为普通for循环需要通过索引或get方法来获取元素,但Set接口无法实现这两种方法

        我们以set接口的实现类HashSet来举例

        Set set = new HashSet();
        set.add("top");//首次添加top
        set.add("mid");
        set.add("top");//第二次添加top
        set.add("sup");
        set.add("baolan");
        set.add(null);//首次添加null
        set.add(null);//第二次添加null
        System.out.println("————————————迭代器———————————");
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.print(next+"、");
        }
        System.out.println();
        System.out.println("—————————增强for循环—————————");
        for (Object o :set) {
            System.out.print(o+"、");
        }

输出:

        ————————————迭代器———————————
        null、top、mid、baolan、sup、
        —————————增强for循环—————————
        null、top、mid、baolan、sup、

HashSet

1、HashSet实现了Set接口
2、HashSet的底层未HashMap,而HashMap的底层为数组+链表+红黑树

//HashSet的构造方法
public HashSet() {
        map = new HashMap<>();
    }

3、不能有重复元素,可以存放一个null值

public class Go {
    public static void main(String[] args) {
        Set set = new HashSet();
        set.add(null);
        set.add(null);//第二次存放null,忽略
        set.add("123");
        set.add("123");//第二次存放元素"123",忽略
        set.add(new dog("aaa"));
        set.add(new dog("aaa"));//成功存放
        set.add(new String("top"));
        set.add(new String("top"));//忽略
        System.out.println(set);
    }
}
class dog{...}

输出:[null, 123, top, dog{name='aaa'}, dog{name='aaa'}]
4、HashSet不能保证存取顺序一致,取出顺序取决于系统首次确定的取出顺序
5、可通过remove(Object o)来删除指定对象

实现方法

        上文我们说到HashSet的底层未HashMap,而HashMap的底层为数组+链表+红黑树,这里我们就来模拟以方便理解

public class Go {
    public static void main(String[] args) {
        //模拟一个HashSet的底层(HshMap的底层结构)
        //创建一个数组,类型为Node[]
        Node[] table = new Node[5];//创建数组
        Node t2 = new Node("t2", null);
        table[2]=t2;//创建节点并放到table[2]
        Node t21 = new Node("t2-1", null);
        table[2].next=t21;//将t21结点挂载到t2结点形成链表
        Node t22 = new Node("t2-2", null);
        t21.next=t22;//将t22结点挂载到t21结点,当挂载一定长度后形成红黑树(以后学习)
        table[3] = new Node("t3", null);//创建节点并放到table[3]
    }
}
class Node {//结点,存储数据,可以指向下一个结点,从而形成链表
    Object item;//存放数据
    Node next;//指向下一个结点

    public Node(Object item, Node next) {
        this.item = item;
        this.next = next;
    }
}

此时table表:

table[0]=null
table[1]=null
table[2]=Node{item=t2, next=Node{item=t2-1, next=Node{item=t2-2, next=null}}}
table[3]=Node{item=t3, next=null}
table[4]=null

        添加元素的过程:

        1、添加一个元素t2时,先得到其hash值(不直接等于hashcode),再将其转化为索引值,因此索引是由hash值确定的,大概率不同(hash值是有限的,但要添加的元素是无限的,所以大概率不同,小概率相同,我们应重写hashcode方法以避免内容不同,但因hashcode相同而没有添加的情况),所以HashSet无序
        2、找到目标数据表table[2],并检查该位置是否已有元素,如果没有则直接添加,如果有则调用equals方法(判断取决于不同的类或者重载的equals)对t2、t2-1、t2-2...依次进行比较,相同则放弃添加,不相同则以链表的方式添加至末尾
        3、在java8中如果链表长度大于等于 TREEIFY_THRESHOLD(默认为8),且当前HashMap的容量(capacity)大于等于 MIN_TREEIFY_CAPACITY(默认为64)时,就会考虑树化。
        这个条件是为了避免在HashMap的容量还相对较小的情况下就进行树化,因为这样做可能会因为扩容而导致树化的努力白费(扩容会重新哈希并可能减少哈希冲突)。如果当前容量小于 MIN_TREEIFY_CAPACITY,HashMap会选择扩容而不是树化。
        4、首次创建HashSet类实例并添加元素时,数组扩容至16,但其临界值为threshold为16*0.75(LoadFactor)=12,一旦到达该临界值就会扩容至16*2=32(无论是否在同一个哈希桶,只要满足32就会扩容),同时新的临界值为32*0.75(LoadFactor)=24,依此类推

        我们以一个例题来加深理解:
        定义一个Employee类,该类包括private成员name和age,要求:1、创建三个Employee对象放入HashSet中,当name和age的值相同时,认为是相同的员工,不能添加到HashSet集合中

public class Go {//主方法

    public static void main(String[] args) {
        HashSet myhash = new HashSet();
        System.out.println(myhash.add(new Employee("李华",8)));
        System.out.println(myhash.add(new Employee("李一",8)));
        System.out.println(myhash.add(new Employee("李华",8)));
        //使用了new关键字所以hash值不一样,但内容一致
        System.out.println(myhash);
    }
}
class Employee{
    private String name;
    private int age;

    public Employee(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 boolean equals(Object object) {
        if (this == object) return true;
        if (object == null || getClass() != object.getClass()) return false;
        Employee employee = (Employee) object;
        return age == employee.age && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }//为防止出现hashcode相同但内容不同的情况,hashcode和equals方法都要重写

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

执行结果:

true//添加成功
true//添加成功
false//添加失败
[Employee{name='李华', age=8}, Employee{name='李一', age=8}]

        加深难度:修改Employee类使其内部包含private属性name,sal,birthday,其中birthday为自建类MyDate,属性包括year,month,day,要求:当name和birthday相同时拒绝录入:

package pack.chn.class1.pack;

import java.util.*;

public class Go {//主方法

    public static void main(String[] args) {
        HashSet myhash = new HashSet();
        System.out.println(myhash.add(new Employee("jack", 12, new MyDate(2022, 3, 5))));
        System.out.println(myhash.add(new Employee("jack", 13, new MyDate(2022, 3, 5))));
        System.out.println(myhash.add(new Employee("rose", 11, new MyDate(2012, 3, 5))));
        System.out.println(myhash.add(new Employee("rose", 11, new MyDate(2012, 5, 5))));
        for (Object o : myhash) {
            System.out.println(o);
        }

    }
}

class Employee {
    private String name;
    private int sal;
    private MyDate date;

    public Employee(String name, int sal, MyDate date) {
        this.name = name;
        this.sal = sal;
        this.date = date;
    }

    public String getName() {
        return name;
    }

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

    public int getSal() {
        return sal;
    }

    public void setSal(int sal) {
        this.sal = sal;
    }

    @Override//sal与判断无关,所以无需写入该属性
    public boolean equals(Object object) {
        if (this == object) return true;
        if (object == null || getClass() != object.getClass()) return false;
        Employee employee = (Employee) object;
        return Objects.equals(name, employee.name) && Objects.equals(date, employee.date);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, date);
    }

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


class MyDate {
    public int year;
    public int month;
    public int day;

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

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    @Override//因为MyDate也许判断是否相等,所以该类也需重写两方法
    public boolean equals(Object object) {
        if (this == object) return true;
        if (object == null || getClass() != object.getClass()) return false;
        MyDate myDate = (MyDate) object;
        return year == myDate.year && month == myDate.month && day == myDate.day;
    }

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

执行结果:

true//成功添加
false//name和MyDate相同,不允许添加,sal虽不同但无关判断
true//成功添加
true//name和sal相同,但MyDate不同,允许添加
Employee{name='rose', age=11, date=MyDate{year=2012, month=5, day=5}}
Employee{name='rose', age=11, date=MyDate{year=2012, month=3, day=5}}
Employee{name='jack', age=12, date=MyDate{year=2022, month=3, day=5}}

LinkedHashSet

        LinkedHashSet继承自 HashSet,并实现了 Set 接口。它保留了集合中元素的插入顺序,并提供了 HashSet 的所有功能,如不包含重复元素和快速的查找速度

        1、LInkedHashSet是HashSet的子类
        2、LinkedHashSet的底层是一个LinkedHashMap,底层维护了一个数组+双向链表
        3、LinkedHashSet根据元素的HashCode值来决定元素的存储位置,同时使用链表来维护元素的次序,这使元素看起来是以插入顺序保存的
        4、LinkedHashSet同样不允许重复元素 

实现方法

        1、LinkedHashSet的底层维护了一个数组+双向链表的LinkedHashMap(为HashMap的子类)
        2、首次添加时,数组先扩容到16,数组类型为HashMap$Node[],但实际存放的为LinkedHashMap$Entry类型(后者为前者的子类),该类型每个节点都有 before 和 after 属性以便实现双向链表
        3、在添加元素时,先求hash值,再求索引,以确定该元素在集合中的位置,添加规则和hashset相同
        4、虽然数组内顺序和添加顺序不一致,但因各个节点的before和after相连,所以可保证取出顺序与添加顺序一致

public class Go {//主方法

    public static void main(String[] args) {
        Set set = new LinkedHashSet();
        set.add("123");
        set.add("qqq");
    }
}

 

         在debug中可以看到第一个添加的元素存放在table[2]处,所以他的 before 为 null,而after指向下一个添加的元素,其中LinkedHashMap$Entry  在上文已有介绍,@528为对象的哈希码,它是调试器用来标识特定对象的唯一标识符。
        qqq=java.lang.Object@6ed3ef1: 这部分是 Entry 对象的内容。它表示键值对,其中键是 "qqq",值是 java.lang.Object 类型的对象。这里的 "qqq" 是添加到 LinkedHashSet 中的字符串,而值部分 java.lang.Object@6ed3ef1 表示实际存储的值对象,其类型为 java.lang.Object,并且有一个特定的哈希码 @6ed3ef1。


http://www.kler.cn/news/313698.html

相关文章:

  • 在 CentOS 中安装 MySQL(无坑版)
  • No operations allowed after statement closed
  • WPF TextBox 控件文本水平垂直居中
  • 写一个自动化记录鼠标/键盘的动作,然后可以重复执行的python程序
  • 华为云分布式缓存服务DCS 8月新特性发布
  • Android-UI设计
  • js 将二进制文件流,下载为excel文件
  • 2024“华为杯”中国研究生数学建模竞赛(E题)深度剖析|数学建模完整过程+详细思路+代码全解析
  • 【Linux取经之路】软件包管理器yum编辑器vim及其配置
  • 进程间关系与进程守护
  • 浅析OceanBase数据库的向量化执行引擎
  • CSS中如何实现鼠标悬停效果?
  • 数据结构:(牛客OR36)链表的回文结构
  • (笔记自用)LeetCode:快乐数
  • mysql时间戳格式化yyyy-mm-dd
  • kubeadm方式安装k8s+基础命令的使用
  • 二层、三层网络基本原理
  • 缓存技巧 · Spring Cache Caffeine 高性能缓存库
  • Github 2024-09-20 Java开源项目日报Top10
  • 【快手】前端校招一面
  • 深入理解Python中的时间表示:Unix时间点、毫秒和微秒,以及time模块
  • 9.7floodFill图像分割
  • 98-策略模式的理解
  • 蓝桥杯—STM32G431RBT6(ADC数模转换,从原理到应用)
  • C++第十一节课 new和delete
  • [Python可视化]数据可视化在医疗领域应用:提高诊断准确性和治疗效果
  • AI视觉算法盒是什么?如何智能化升级网络摄像机,守护全方位安全
  • 机器学习--AlexNet
  • 系统架构设计师:软件架构的演化和维护
  • 【AI视频】Runway:Gen-2 运镜详解