03 - Java核心类库—集合

这篇文章主要分享下面这张图的内容,欢迎大家一起学习交流~
在这里插入图片描述
ps:这篇文章着重讲解如何灵活运用,好比“先学会用筷子吃饭,长大了再研究筷子怎么做的”,顾然之,相关底层原理的分析就只是了解有印象即可,后期备战面试时候认真理解研究,除非您现在看这篇文章是个大神

目录

一、集合

1、类集概述(类集设置的目的)

对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。

但是这样做毕竟太复杂了,所以在Java 中为了方便用户操作各个数据结构, 故引入了类集的概念,有时候就可以把类集称为 java对数据结构的实现。

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

  • 类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。
  • 所有的类集操作的接口或类都在 java.util 包中。
    在这里插入图片描述

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

2、Collection接口—单值存储集合

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

此接口定义如下:

public interface Collection<E> extends Iterable<E>

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

此接口的常用方法如下所示:
在这里插入图片描述
本接口中一共定义了 15 个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。

3、List接口

在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。

List 子接口的定义:

 public interface List<E> extends Collection<E>

此接口上依然使用了泛型技术。此接口对于 Collection 接口来讲有如下的扩充方法
在这里插入图片描述
在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。

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

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

  • ArrayList(95%)
  • Vector(4%)
  • LinkedList(1%)

3.1 ArrayList

java.util.ArrayList 集合数据存储的结构是数组结构。

特点:元素增删慢,查找快

由于日常开发中使用最多的功能为查询数据、遍历数据,所以 ArrayList 是最常用的集合。

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

public class ArrayList<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, Serializable

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

3.1.1 构造方法

1、API文档
在这里插入图片描述
tips:如果需要存储大量数据,建议使用一参构造方法,避免重复扩容;

2、源码解析(以add函数为例)

1,无参构造函数创建ArrayList集合
在这里插入图片描述
2,进入ArrayList无参构造方法
在这里插入图片描述
其中elementData为:
在这里插入图片描述
DEFAULTCAPACITY_EMPTY_ELEMENTDATA为:
在这里插入图片描述
可以看出,通过无参构造方法创建的是一个长度为0 的数组,并不是长度为10的空列表;(并不是API错误)
3,添加元素时:

size指有效数据长度;
在这里插入图片描述
4,再次进入add函数
在这里插入图片描述
这里是引用
5,进入grow扩容函数
在这里插入图片描述
6,再次进入重写的扩容函数grow
在这里插入图片描述
7,进入newCapacity函数
在这里插入图片描述
这里是引用
其中MAX_ARRAY_SIZE为:
在这里插入图片描述

3.1.2 范例
3.1.2.1 增加及取得元素
package com.java.collection;

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

public class ArrayList_2 {
    public static void main(String[] args) {
        List<String> all = new ArrayList<String>(); // 实例化List对象,并指定泛型类型
        all.add("hello "); // 增加内容,此方法从Collection接口继承而来
        all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义
        all.add("world"); // 增加内容,此方法从Collection接口继承而来
       System.out.println(all); // 打印all对象调用toString()方法
        }
    }
}

运行结果:
在这里插入图片描述
以上的操作向集合中增加了三个元素,其中在指定位置增加的操作是 List 接口单独定义的。随后进行输出的时候, 实际上调用的是 toString()方法完成输出的。

可以发现,此时的对象数组并没有长度的限制,长度可以任意长,只要是内存够大就行。

3.1.2.2 进一步操作
  • 使用 remove()方法删除若干个元素,并且使用循环的方式输出。
  • 根据指定位置取的内容的方法,只有 List 接口才有定义,其他的任何接口都没有任何的定义。
import java.util.List;

public class ArrayList_2 {
    public static void main(String[] args) {
        List<String> all = new ArrayList<String>(); // 实例化List对象,并指定泛型类型
        all.add("hello "); // 增加内容,此方法从Collection接口继承而来
        all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义
        all.add("world"); // 增加内容,此方法从Collection接口继承而来
        all.remove(1); // 根据索引删除内容,此方法是List接口单独定义的
        all.remove("world");// 删除指定的对象
        System.out.print("集合中的内容是:");
        for (int x = 0; x < all.size(); x++) { // size()方法从Collection接口继承而来
            System.out.print(all.get(x) + "、"); // 此方法是List接口单独定义的
        }
    }
}

运行结果:
在这里插入图片描述
但是,这里需要注意的是,对于删除元素的操作,后面还会有更加清楚的讲解,此处只是简单的理解一下元素删除 的基本操作即可。具体的原理可以暂时不进行深入掌握。

3.2 Vector

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

public class Vector<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, Serializable

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

从Java 2平台v1.2开始,该类被改进以实现List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector是同步的如果不需要线程安全实现,建议使用ArrayList代替Vector

3.2.1 构造方法

’
当增量为0(增量:每次扩容,增加的容量)时,扩容是每次翻一番;
在这里插入图片描述

3.2.2 范例
package org.listdemo.vectordemo; 

import java.util.List; 
import java.util.Vector; 
public class VectorDemo01 { 
	public static void main(String[] args) { 
		List<String> all = new Vector<String>(); // 实例化List对象,并指定泛型类型 
		all.add("hello "); // 增加内容,此方法从Collection接口继承而来
		all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义
		all.add("world"); // 增加内容,此方法从Collection接口继承而来
		all.remove(1); // 根据索引删除内容,此方List接口单独定义的
		all.remove("world");// 删除指定的对象 
		System.out.print("集合中的内容是:"); 
		for (int x = 0; x < all.size(); x++) { // size()方法从Collection接口继承而来 
			System.out.print(all.get(x) + "、"); // 此方法是List接口单独定义的 
		} 
	} 
}

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

但是 Vector 属于 Java 元老级的操作类,是最早的提供了动态对象数组的操作类,在 JDK 1.0 的时候就已经推出了此 类的使用,只是后来在 JDK 1.2 之后引入了 Java 类集合框架。

但是为了照顾很多已经习惯于使用 Vector 的用户,所以在 JDK 1.2 之后将 Vector 类进行了升级了,让其多实现了一个 List 接口,这样才将这个类继续保留了下来。

3.2.3 Vector类与ArrayList类的区别

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

tips:什么是同步和异步呢?

  • 同步:好比有两个人吃一大碗饭,排队一个一个吃,这样就不会出现争议问题,所以线程安全;
  • 异步:两个人同一时间吃这碗饭,这样就很容易导致争议,所以线程不安全。

3.3 链表操作类:LinkedList(理解)

LinkedList是一个双向链表

java.util.LinkedList 集合数据存储的结构是链表结构。

特点:元素增删快,查找慢

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

public class LinkedList<E> extends AbstractSequentialList<E> 
implements List<E>, Deque<E>, Cloneable, Serializable

此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法:
在这里插入图片描述

3.3.1 范例
3.3.1.1 验证 LinkedList 子类
import java.util.LinkedList; 
import java.util.Queue; 
public class TestDemo { 
	public static void main(String[] args) { 
		Queue<String> queue = new LinkedList<String>(); 
		queue.add("A"); 
		queue.add("B"); 
		queue.add("C"); 
		int len=queue.size();//把queue的大小先取出来,否则每循环一次,移除一个元素,就少 一个元素,那么queue.size()在变小,就不能循环queue.size()次了。 
		for (int x = 0; x <len; x++) { 
			System.out.println(queue.poll());
		}
		System.out.println(queue); 
	} 
} 																																					
3.3.2 常用方法

在这里插入图片描述

4、Set 接口

4.1 概述

Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。

Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义 的 get(int index)方法,所以无法通过下标获取数据进行输出

tips:Set集合取出元素的方式可以采用:迭代器、增强for、数组(toArray)。

在此接口中有两个常用的子类:HashSet、TreeSet
在这里插入图片描述

4.2 散列存放:HashSet

4.2.1 概述

java.util.HashSet 是 Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。

java.util.HashSet 底层的实现其实是一个 java.util.HashMap 支持。

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

有插入和删除的方法,但是没有get方法,所以为了获取元素 需要通过迭代器增强for转换成数组 来进行;

TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的;

4.2.2 注意点

1, HashSet 底层的实现其实是一个HashMap 支持

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

2,插入操作有返回值

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

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

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

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

在这里插入图片描述
看到这张图就有人要问了,这个是怎么存储的呢?

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

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

4.2.4 范例
4.2.4.1 观察 HashSet
package com.java.collection;

import java.util.HashSet;

public class HashSet_3 {
    public static void main(String[] args) {
        HashSet<Double> data = new HashSet<>();
        data.add(10.0);
        data.add(20.0);//重复
        data.add(20.0);
        System.out.println(data);
    }
}

运行结果:
在这里插入图片描述
使用 HashSet 实例化的 Set 接口实例,本身属于无序的存放。

那么,现在思考一下?能不能通过循环的方式将 Set 接口中的内容输出呢?

是可以实现的,因为在 Collection 接口中定义了将集合变为对象数组进行输出。

import java.util.HashSet; 
import java.util.Set; 
public class HashSetDemo02 { 
	public static void main(String[] args) { 
		Set<String> all = new HashSet<String>(); // 实例化Set接口对象
		all.add("A"); 
		all.add("B"); 
		all.add("C"); 
		all.add("D"); 
		all.add("E"); 
		Object obj[] = all.toArray(); // 将集合变为对象数组 
		for (int x = 0; x < obj.length; x++) { 
			System.out.print(obj[x] + "、"); 
		} 
	}
}

运行结果:
在这里插入图片描述
但是,以上的操作不好,因为在操作的时候已经指定了操作的泛型类型,

那么现在最好的做法是由泛型所指定的类 型变为指定的数组。 所以只能使用以下的方法: T[] toArray(T[] a)

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

public class HashSet_4 {
    public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        /*Object obj[] = all.toArray(); // 将集合变为对象数组
        for (int x = 0; x < obj.length; x++) {
            System.out.print(obj[x] + "、");
        }*/
        //String[] a = new String[]{};
        //String[] str = all.toArray(a);//详细
        String[] str = all.toArray(new String[] {});// 变为指定的泛型类型数组【理解:那就是个没长度的数组,告诉集合想变数组可以,变跟这个数组一样的类型,长度按集合自己的实际长度来】
        for (int x = 0; x < str.length; x++) {
            System.out.print(str[x] + "、");
        }
    }
}

下面再进一步验证 Set 接口中是不能有重复的内容的。
(1)不是自定义对象

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

public class HashSet_1 {
    public static void main(String[] args) {
        HashSet<String> data = new HashSet<>();
        data.add("你好,");
        data.add(" 我是爱摸鱼的TT。");//重复
        data.add(" 我是爱摸鱼的TT。");
        //Iterator<String> iterator = data.iterator();
        for (String set:data) {
            System.out.print(set);
        }
    }
}

运行结果:
在这里插入图片描述
以上字符串“我是爱摸鱼的TT。”设置了多次,因为String类重写了hashCode和equals,所以其最终结果只能有一个“我是爱摸鱼的TT。”。

(2)自定义对象

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

public class HashSet_2 {
    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("李四", 10));//重复
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
    static 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;
        }
        public String toString() {
            return "姓名:" + this.name + ",年龄:" + this.age;
        }
    }
}

运行结果:
在这里插入图片描述
以上是自定义对象,没有重写hashCode和equals方法导致运行结果出现重复。

重写后的代码:(可自动生成,但注意选择对应的属性)

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

public class HashSet_2 {
    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("李四", 10));//重复
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
    static class Person{
        private String name;
        private int age;

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

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

        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;
        }
        public String toString() {
            return "姓名:" + this.name + ",年龄:" + this.age;
        }
    }
}

运行结果:
在这里插入图片描述

4.3 排序的子类:TreeSet 与 Comparable接口

4.3.1 概念

与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:

public class TreeSet<E> extends AbstractSet<E> 
implements NavigableSet<E>, Cloneable, Serializable

此类的iterator方法返回的迭代器是快速失败的 :如果在创建迭代器之后的任何时间修改集合,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。

补充:

快速失败:遍历的是集合本身,如果遍历过程中,通过其他操作使得集合本身发生改变,就会抛出异常;

安全失败:失败不会出错,在遍历的时候先将集合复制一份,即使原集合发生改变,也不会抛出异常;
4.3.2 范例

1,使用特定的数据类型作为填充元素

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

public class TreeSet_2 {
    public static void main(String[] args) {
        Set<String> all = new TreeSet<String>(); // 实例化Set接口对象\
        all.add("D");
        all.add("X");
        all.add("A");
        System.out.println(all); 
    }
}

运行结果:
在这里插入图片描述
虽然在增加元素的时候属于无序的操作,但是增加之后却可以为用户进行排序功能的实现。

4.3.3 排序的说明

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

举例:【接上面4.3.2 范例】

2,使用自定义对象作为填充元素

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

public class TreeSet_3 {
    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("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
    static 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;
        }
        public String toString() {
            return "姓名:" + this.name + ",年龄:" + this.age;
        }
    }
}

运行结果:
在这里插入图片描述
也就是说,如果你的类想要被识别,并用来判断大小(排序),就要实现Comparable接口中的方法。因此可以对Person对象进行修改:

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

public class TreeSet_3 {
    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("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
    static class Person implements  Comparable<Person>{
        private String name;
        private int age;

        @Override
        public int compareTo(Person per) {
            //即this与per比较
            //返回值:正数(this大) 负数(this小) 零(相等)
            if (this.age > per.age) {
                return 1;
            } else if (this.age < per.age) {
                return -1;
            } else {
                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;
        }
        public String toString() {
            return "姓名:" + this.name + ",年龄:" + this.age;
        }
    }
}

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

 @Override
        public int compareTo(Person per) {
            //即this与per比较
            //返回值:正数(this大) 负数(this小) 零(相等)
            if (this.age > per.age) {
                return 1;
            } else if (this.age < per.age) {
                return -1;
            } else {
                return this.name.compareTo(per.name);
            }
        }

运行结果:
在这里插入图片描述
此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。

4.3.4 关于重复元素的说明

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

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

public class TreeSet_4 {
    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("李四", 10));
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
}

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

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

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

  • 第一种判断两个对象的编码是否一致,这个方法需要通过 hashCode()完成,即:每个对象有唯一的编码
  • 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。

所以此时需要覆写 Object 类中的 hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。【hashCode与equals方法可以自动生成,但要注意选择对应的属性
在这里插入图片描述
发现,此时已经不存在重复元素了,所以如果要想去掉重复元素需要依靠 hashCode()和 equals()方法共同完成。

4.3.5 小结
  • 关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现 Comparable 接口,则不能实现TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。 换句话说要添加到 TreeSet集合中的对象的类型必须实现了 Comparable 接口。
  • 不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet集合中也是不能去掉重复值的。

5、Collection集合输出—Iterator与ListIterator

已经学习过了基本的集合操作,那么对于集合的输出本身也是有多种形式的,可以使用如下的几种方式:

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

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

5.1 问题引入

对于ArrayList来说,普通的遍历方法(如下图)效率差别不大,但对于LinkedList来说,并不是最优的。
在这里插入图片描述
因此可以使用迭代器实现

5.2 Iterator(通用)

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

此接口定义如下:

public interface Iterator<E>

要想使用此接口,则必须使用 Collection 接口【即:只适用于Collection接口的集合,Map接口不行】,在 Collection 接口中规定了一个 iterator()方法,可以用于为 Iterator 接口进行实例化操作。

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

  • Iterator:迭代Collection下所有集合,包括List和Set;只能进行由前向后单向输出。
  • ListIterator:只用于遍历List;可以双向输出。
5.2.1 基本使用方法

在这里插入图片描述

5.2.2 常用方法详述

1,hasNext
在这里插入图片描述
2,next
在这里插入图片描述
3,remove
使用 Iterator 输出的时候有一点必须注意,在进行迭代输出的时候如果要想删除当前元素,则只能使用 Iterator 接口中的 remove()方法,而不能使用集合中的 remove()方法。否则将出现未知的错误。
在这里插入图片描述

例1:(直接删除)
声明迭代器后直接删除元素
在这里插入图片描述
原因:迭代器相当于一个指针,声明完毕后还没指向任何地方,必须通过next指向下一个元素,才能正常使用
在这里插入图片描述
在这里插入图片描述

例2:(使用集合中的 remove()方法)
在这里插入图片描述
使用Iterator中的remove方法
在这里插入图片描述

但是,从实际的开发角度看,元素的删除操作出现的几率是很小的,基本上可以忽略,即:集合中很少有删除元素 的操作。

Iterator 接口本身可以完成输出的功能,但是此接口只能进行由前向后的单向输出。如果要想进行双向输出,则必须使用其子接口 —— ListIterator。

5.3 双向输出—ListIterator(理解)

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

public interface ListIterator<E> extends Iterator<E>
5.3.1 常用方法

在这里插入图片描述
previous():

在这里插入图片描述
初始时指向其他地方,必须先用next指向下一个元素,才能使用previous指向上一个

5.3.2 范例

例1:

1,插入和修改元素,并重新遍历
在这里插入图片描述
2,发现新插入的元素未展示出来,原因是:迭代器指针刚声明完毕,并不是指向第一个元素,所以此时插入元素,是在第一个元素之前插入
在这里插入图片描述
在这里插入图片描述

例2:

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

public class ListIterator_2 {
    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() + "、");
        }
    }
}

运行结果:
在这里插入图片描述
但是,此处有一点需要注意的是,如果要想进行由后向前的输出,则首先必须先进行由前向后的输出。

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

5.4 废弃的接口:Enumeration(了解)

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

此接口定义如下:

public interface Enumeration<E>

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

但是,与 Iterator 不同的是,如果要想使用 Enumeration 输出的话,则还必须使用 Vector 类完成,在类中定义了如下 方法:public Enumeration elements()
在这里插入图片描述
需要注意的是,在大部分的情况下,此接口都不再使用了,但是对于一些古老的类库,本身依然要使用此接口进行 操作,所以此接口一定要掌握。

所有的代码最好可以用记事本独立写出。

5.5 新的支持:foreach(理解)

5.5.1 概述

增强for循环,最早出现在C#中;

用于迭代数组 或 集合(Collection下的)

5.5.2 实际应用
import java.util.HashSet;
import java.util.Iterator;

public class HashSet_1 {
    public static void main(String[] args) {
        HashSet<String> data = new HashSet<>();
        data.add("你好,");
        data.add(" 我是爱摸鱼的TT。");
        data.add(" 我是爱摸鱼的TT。");
        //Iterator<String> iterator = data.iterator();
        for (String set:data) {
            System.out.print(set);
        }
    }
}

运行结果:
在这里插入图片描述

在使用 foreach 输出的时候一定要注意的是,里面的操作泛型要指定具体的类型,这样在输出的时候才会更加有针对性。

5.6 小结

原则:只要是碰到了集合,则输出的时候想都不想就使用 Iterator 进行输出。

  • Iterator:迭代Collection下所有集合,包括List和Set;只能进行由前向后单向输出。
  • ListIterator:只用于遍历List;可以双向输出。
  • foreach:用于迭代数组 或 集合(Collection下的)

6、Map 接口—双值存储集合

6.1 概述

Map与Collection同一级别,而不是与List和Set同一级别;

Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用 Map 了;

取数据操作不同:ArrayList可以通过下标,Set可以通过迭代器,Map需要先取出键形成Set集合,再根据具体的键取出值

Map中的键(key)不可重复

所有内容都按照 key -> value 的形式保存,也称为二元偶对象。

HashSet使用的HashMap,TreeSet使用的TreeMap,LinkedHashSet使用的LinkedHashMap。即Set集合使用Map的键来存储数据

此接口定义如下:

public interface Map<K,V>

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

6.2 相关API

在这里插入图片描述
小结:添加数据都是put,删除数据都是remove,获取数据都是get;遍历都是使用keySet方法得到它键的Set集合,然后在迭代,最后再通过get方法取出所有。

6.3 哈希表的概述

Object中的hashCode可以提高哈希表的性能;

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

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
在这里插入图片描述
哈希桶(数组table中每个方格)中的数据量大于8时,从链表转换为红黑二叉树.当哈希桶中的数据量减少到6时,从红黑二叉树转换为链表;

初始桶的数量16,散列因子0.75。当使用桶的数量所占比例超过0.75后,需要扩容为原先的两倍;

6.4 新的子类:HashMap

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

public class HashMap<K,V> extends AbstractMap<K,V> 
implements Map<K,V>, Cloneable, Serializable

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

线程不安全(效率高)
在这里插入图片描述

6.4.1 HashMap源码分析(回头看)

1.构造方法

在这里插入图片描述
作为一般规则,默认加载因子(.75)在时间和空间成本之间提供了良好的折衷。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put )。在设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以便最小化重新散列操作的数量。如果初始容量大于最大条目数除以加载因子,则不会发生重新加载操作。

2.进入源代码
在这里插入图片描述
3.装载因子
在这里插入图片描述
4.初始容量
在这里插入图片描述

6.4.1.1 put方法

在这里插入图片描述
1,进入put函数
在这里插入图片描述
2,进入putVal方法 1)找到存储位置并判断是否满足存储条件
在这里插入图片描述
判断表是否为空以及长度是否为0,若是则进行扩容算法
在这里插入图片描述
2)根据hash值,计算求得位置对应下标(n-1 & hash 等价于 hash % n,但前一种方法更快),判断并进行插入操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3)table对应位置中已有元素,需判断覆盖原值还是插入新值,插入时也要区分链表插入还是红黑树插入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.4.2 范例
6.4.2.1 向集合中增加内容
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

//向集合内增内容
public class HashMap_1 {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"你好");
        map.put(1,"Hello");//以新换旧
        map.put(2,"爱摸鱼的TT");
        String str = map.get(6);
        System.out.println(str);
    }
}

运行结果:
在这里插入图片描述
以上的操作是 Map 接口在开发中最基本的操作过程,根据指定的 key 找到内容,如果没有找到,则返回 null,找到 了则返回具体的内容。

6.4.2.2 得到全部的 key 或 value
import java.util.*;

//得到全部的 key 或 value
public class HashMap_2 {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"你好");
        map.put(1,"Hello");//以新换旧
        map.put(2,"爱摸鱼的TT");
         //获取键值对的方法:
        //通过keySet()方法得到它的键(key)的Set集合,然后在迭代,最后通过get()方法取得所有value值。
        Set<Integer> set = map.keySet();// 得到全部的key
        Collection<String> value = map.values();// 得到全部的value
        Iterator<Integer> iter1 = set.iterator();
        Iterator<String> iter2 = value.iterator();
        System.out.print("全部的key:");
        while (iter1.hasNext()) {
            System.out.print(iter1.next() + "、");
        }
        System.out.print("\n全部的value:");
        while (iter2.hasNext()) {
            System.out.print(iter2.next() + "、");
        }
    }
}

运行结果:
在这里插入图片描述
既然可以取得全部的 key,那么下面就可以对以上的操作进行扩充,循环输出 Map 中的全部内容。

import java.util.*;

//得到全部的 key 或 value
public class HashMap_2 {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"你好");
        map.put(1,"Hello");//以新换旧
        map.put(2,"爱摸鱼的TT");
         //获取键值对的方法:
        //通过keySet()方法得到它的键(key)的Set集合,然后在迭代,最后通过get()方法取得所有value值。
        Set<Integer> set = map.keySet(); // 得到全部的key
        Iterator<Integer> iter = set.iterator();//实例化
        while (iter.hasNext()) {
            Integer i = iter.next(); // 得到key
            System.out.println(i + " --:> " + map.get(i));
        }
    }
}

运行结果:
在这里插入图片描述
HashMap 本身是属于无序存放的。

6.5 旧的子类:Hashtable

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

线程安全(效率低)
在这里插入图片描述

import java.util.HashMap;
import java.util.Map;

public class Hashtable {
    public static void main(String[] args) {
        Map<String,Integer> hashtable = new HashMap<>();
        hashtable.put("one",1);
        hashtable.put("two",2);
        hashtable.put("three",3);
        Integer integer = hashtable.get("two");
        if(integer != null){
            System.out.println("two" + "->" + integer);
        }
    }
}

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

6.6 HashMap 与 Hashtable 的区别

在整个集合中除了 ArrayList 和 Vector 的区别之外,另外一个最重要的区别就是 HashMap 与 Hashtable 的区别。
在这里插入图片描述

6.7 排序的子类:TreeMap(理解)

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

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class TreeMap_1 {
    public static void main(String[] args) {
        Map<String,String> map = new TreeMap<String,String>();
        map.put("你好","爱摸鱼的TT");
        map.put("Hello","爱摸鱼的TT");
        //获取键值对的方法:
        //通过keySet()方法得到它的键(key)的Set集合,然后在迭代,最后通过get()方法取得所有value值。
        Set<String> key = map.keySet();// 得到全部的key
        //迭代
        Iterator<String> iterator = key.iterator();//使用 iterator()方法为 Iterator 接口实例化
        while(iterator.hasNext()){
            String i = iterator.next();// 得到key
            System.out.println(i +"->" + map.get(i));
        }
    }
}

运行结果:
在这里插入图片描述
此时的结果已经排序成功了,但是从一般的开发角度来看,在使用 Map 接口的时候并不关心其是否排序,所以此类 只需要知道其特点即可。

7、关于 Map 集合的输出

7.1 描述

在 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 接口中以下的方法最为常用:
在这里插入图片描述

7.2 范例

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

//输出操作
public class HashMap_3 {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<String,String>();
        map.put("你好","爱摸鱼的TT");
        map.put("Hello","爱摸鱼的TT");
        //方法一:直接使用Iterator接口
        Set<Map.Entry<String,String>> set = map.entrySet();//Set集合
        Iterator<Map.Entry<String,String>> iterator = set.iterator();
        while(iterator.hasNext()){
            Map.Entry<String, String> next = iterator.next();
            System.out.println(next.getKey() + "->" + next.getValue());
        }
    }
}

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

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

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

//输出操作
public class HashMap_3 {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<String,String>();
        map.put("你好","爱摸鱼的TT");
        map.put("Hello","爱摸鱼的TT");
        //方法二:foreach
        for(Map.Entry<String,String> me : map.entrySet()){
            System.out.println(me.getKey() + "->" + me.getValue());
        }
    }
}

运行结果:
在这里插入图片描述

8、两种关系(理解)

ps:学到数据库回头看更好理解!!!

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

8.1 第一种关系:一对多关系

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

8.1.1 范例

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

public 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; 
    }
    public String toString() { 
        return "学生信息" + "\n" + "\t|- 姓名:" + this.name + "\n" + "\t|- 年龄:" + this.age; } 
}

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

import java.util.ArrayList; 
import java.util.List; 
public class School { 
    private String name; 
    private List<Student> allStudents = null; 
    public School() { 
        this.allStudents = new ArrayList<Student>(); 
    }
    public School(String name) { 
        this(); this.name = name; 
    }
    public List<Student> getAllStudents() { 
        return allStudents; 
    }
    public String toString() { 
        return "学校信息:" + "\n" + "\t|- 学校名称:" + this.name; } 
}

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

import java.util.Iterator; 
public class TestDemo01 { 
    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()); } 
}

此时,就已经完成了一个一对多的关系。

了解之后,下面继续研究。

8.2 第二种关系:多对多关系

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

8.2.1 范例

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

import java.util.ArrayList; 
import java.util.List; 
public class Student { 
    private String name; 
    private int age; 
    private List<Course> allCourses; 
    public Student() { 
        this.allCourses = new ArrayList<Course>(); 
    } 
    public Student(String name, int age) { 
        this(); this.name = name; this.age = age; 
    }
    public List<Course> getAllCourses() { 
        return allCourses; 
    }
    public String toString() { 
        return "学生信息" + "\n" + "\t|- 姓名:" + this.name + "\n" + "\t|- 年龄:" + this.age; 
    } 
}

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

import java.util.ArrayList; 
import java.util.List; 
public 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.credit = credit; 
        this.name = name; 
    }
    public List<Student> getAllStudents() { 
        return allStudents; 
    }
    public String toString() { 
        return "课程信息:" + "\n" + "\t|- 课程名称:" + this.name + "\n" + "\t|- 课程学分: " + this.credit; 
    } 
}

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

import java.util.Iterator;
public class TestDemo02 { 
    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()); 
        } 
    } 
}

9、Collections 类(理解)

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

public class Collections extends Object

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

9.1 范例

9.1.1返回空的 List 集合
import java.util.Collections; 
import java.util.List; 
public class CollectionsDemo01 { 
    public static void main(String[] args) { 
        List<String> all = Collections.emptyList() ;// 空的集合 
        all.add("A") ; 
    } 
}

使用 Collections 类返回的空的集合对象,本身是不支持任何的修改操作的,因为所有的方法都没实现。

9.1.2 使用 Collections 进行增加元素的操作
import java.util.ArrayList; 
import java.util.Collections; 
import java.util.List; 
public class CollectionsDemo02 { 
    public static void main(String[] args) { 
        List<String> all = new ArrayList<String>(); 
        Collections.addAll(all, "A", "B", "C");// 向集合增加元素 System.out.println(all); 
        } 
}

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

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

10.1 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()方法时,必须遵守的准则,如果违反会出现意想不到的结果,请大家一定要遵守。

10.2 hashcode() 方法

在 object 类中定义如下:

public native int hashCode();

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

10.3 java.lang.Object 中对 hashCode 的约定(很重要)

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

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

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

11 总结

  1. 类集就是一个动态的对象数组,可以向集合中加入任意多的内容。
  2. List 接口中是允许有重复元素的,Set 接口中是不允许有重复元素。
  3. 所有的重复元素依靠 hashCode()和 equals 进行区分
  4. List 接口的常用子类:ArrayList、Vector
  5. Set 接口的常用子类:HashSet、TreeSet
  6. TreeSet 是可以排序,一个类的对象依靠 Comparable 接口排序
  7. Map 接口中允许存放一对内容,key -> value
  8. Map 接口的子类:HashMap、Hashtable、TreeMap
  9. 用HashSet\HashMap就重写hashCode & equals;用TreeSet\TreeMap装就重写Comparable
  10. Map 使用 Iterator 输出的详细步骤
    a、通过keySet()方法得到它的键(key)的Set集合,然后在迭代,最后通过get()方法取得所有value值。在这里插入图片描述
    b、 使用 Map 接口中的 entrySet()方法将 Map 接口的全部内容变为 Set 集合;可以使用 Set 接口中定义的 iterator()方法为 Iterator 接口进行实例化;之后使用 Iterator 接口进行迭代输出,每一次的迭代都可以取得一个 Map.Entry 的实例;通过 Map.Entry 进行 key 和 value 的分离

在这里插入图片描述

二、学习建议

熟悉List Map Set 并且要能在里面数量存储各种对象(int String Object 或者自定义对象…)和添加、移除等等一些常用的方法

List Map 在公司是用的最多的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

涛涛同学debug

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

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

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

打赏作者

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

抵扣说明:

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

余额充值