飞道的博客

2021年一起学习Java集合框架

443人阅读  评论(0)

1.Java集合框架的概述

为什么使用集合框架?

  • 集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
    说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)。
  • 数组在存储多个数据方面的特点:
    ①一旦初始化以后,其长度就确定了。
    ②数组一旦定义好,其元素的类型也就确定了。我们也就只有操作指定类型的数据了
    比如:String[] arr;int[] arr1;Object[] arr2;
  • 数组在存储多个数据方面的缺点:
    ①一旦初始化以后,其长度就不可修改。
    ②数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
    ③获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
  • 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。

一张图了解集合框架知识体系:

具体说明集合框架:

 /----Collection接口: 单列集合,用来存储一个一个的对象
          /----List接口  存储有序的、可重复的数据
                 /----实现类:ArrayList、LinkedList、Vector
          /----Set接口   存储无序的、不可重复的数据
                 /----实现类:HashSet、LinkedHashSet、TreeSet
 /----Map接口:双列集合,用来存储一对(key-value)一对的数据
          /----HashMap、LinkdedHashMap、TreeMap、Properties、HashTable
2.Collection接口方法

Collection接口中定义了很多抽象的方法,在开发中会用到。
参考API文档

注意:向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()方法.
说明:(Collection接口的某些常用方法会调用equals()判断元素是否在集合中,比如 boolean contains(Object obj)方法,默认调用的是Object类的equals()方法,比较的是两个对象是否指向同一个地址值。)

用于测试的自定义Person类:

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

    public Person() {
   
    }

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

    public String getName() {
   
        return name;
    }

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

    public int getAge() {
   
        return age;
    }

    public void setAge(int age) {
   
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
   
        System.out.println("Person类 equals()方法~~~");
        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 String toString() {
   
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. 添加
    ①boolean add(Object obj)
    ②boolean addAll(Collection coll1)
  2. 获取有效元素的个数
    int size()
  3. 清空集合
    void clear()
  4. 是否是空集合
    boolean isEmpty()

测试方法代码如下:

 @Test
    public void test1(){
   
        //1.    add(Object obj)向obj中添加数据
        Collection coll = new ArrayList();
        coll.add(789);//自动装箱 
        coll.add(Integer.valueOf(123));
        coll.add("美美哒");
        coll.add(new Person("张三",20));
        System.out.println(coll);
        //2.    addAll(Collection coll1) 并集:将coll1集合元素全部添加到当前集合中
        Collection coll1 = new ArrayList();
        coll1.add(456);
        coll1.add("小伙子");
        coll.addAll(coll1);
        System.out.println(coll);
        //3.    size()获得当前集合的大小
        int size = coll.size();
        System.out.println(size);
        System.out.println("--------------");
        //4.    clear()清空当前集合
        coll1.clear();
        System.out.println(coll1);
        //5.    isEmpty()当前集合是否为空
        boolean empty = coll1.isEmpty();
        System.out.println(empty);
        System.out.println("--------------");
        System.out.println(coll.isEmpty());
    }
  1. 是否包含某个元素
    ① boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
    ② boolean containsAll(Collection coll1): 也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
     @Test
   public void test2() {
   
       Collection coll = new ArrayList();
       coll.add(new Integer(123));
       coll.add(new String("美美哒"));
       coll.add(new Person("张三",20));
       coll.add(789);
       //1.    contains(Object obj) 当前集合中是否包含obj元素
       //注意:contains(Object obj)参数obj调用了equals()方法 去集合中一一比较
       //可以通过自定义类中的equals()方法得知
       Person p = new Person("张三", 20);
       boolean contains = coll.contains(p);
       System.out.println(contains);

       System.out.println("=================");
       System.out.println(coll.contains(new String("美美哒")));


       Collection coll1 = Arrays.asList(123,new Person("张三",20));
       //2.    containsAll(Collection coll1) 判断形参coll1中的所有元素是否都存在于当前集合中
       boolean b = coll.containsAll(coll1);
       System.out.println(b);
   } 
  1. 删除
    ① boolean remove(Object obj) 通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
    ② boolean removeAll(Collection coll1) 取当前集合的差集
 @Test
    public void test3() {
   
        Collection coll = new ArrayList();
        coll.add(new Integer(123));
        coll.add(new Person("张三",20));
        coll.add(new String("美美哒"));
        coll.add(789);
        //3.    remove(Object obj)将形参obj从当前集合中移除
        //coll第一个元素和 obj元素比较
        coll.remove(123);
        //查看移除数据后的集合
        System.out.println(coll);
        
        System.out.println("========*******=========");

        Collection coll2 = Arrays.asList(123, 789);
        //4.    removeAll(Collection coll1):差集:从当前集合中移除coll2中所有元素 coll2集合没有变化
        //removeAll()是当前集合的元素通过调用equals()跟作差集的集合每一个元素比较
        coll.removeAll(coll2);
        System.out.println(coll);
    }
  1. 取两个集合的交集
    boolean retainAll(Collection coll1) 把交集的结果存在当前集合中,不影响coll1
  2. 集合是否相等
    boolean equals(Object obj) 要想返回true,当前集合和形参集合的所有元素都相同
@Test
    public void test4() {
   
        Collection coll = new ArrayList();
        coll.add(new Integer(123));
        coll.add(new Person("张三",20));
        coll.add(new String("美美哒"));
        coll.add(789);
        Collection coll1 = Arrays.asList(123, 789);
	    //5.retainsAll(Collection coll1):交集 获取当前集合和coll1集合的交集,结果放在当前集合中,不影响coll1集合
        coll.retainAll(coll1);
        System.out.println(coll);

        Collection coll2 = new ArrayList();
        coll2.add(new Integer(123));
        coll2.add(new Person("张三2",20));
        coll2.add(new String("美美哒"));
        coll2.add(78910);
        //6.    equals(Object obj) 要想返回true,当前集合和形参集合的所有元素都相同
        boolean equals = coll.equals(coll2);
        System.out.println(equals);
    }
  1. 转成对象数组
    Object[] toArray()
  2. 获取集合对象的哈希值
    hashCode()
    @Test
    public void test5() {
   
        Collection coll = new ArrayList();
        coll.add(new Integer(123));
        coll.add(new Person("张三",20));
        coll.add(new String("美美哒"));
        coll.add(789);

        //7.    hashCode值:返回当前对象哈希值
        System.out.println(coll.hashCode());

        //8.    集合 转  数组
        Object[] array = coll.toArray();
        //遍历
        for (int i = 0; i < array.length; i++) {
   
            System.out.println(array[i]);
        }
        
        //9.    数组 转 集合 调用Arrays的静态方法asList()
        List<String> list = Arrays.asList(new String[]{
   "aa","bb","cc"});
        System.out.println(list);

        //常见问题: 如果是使用基本数据类型的数组  数组转换为集合的时候 会把数组中的元素当做一个元素
        List<int[]> arr1 = Arrays.asList(new int[]{
   1, 2, 3, 4});
        System.out.println(arr1.size());//1

        List<Integer> arr2 = Arrays.asList(new Integer[]{
   1, 2, 3, 4});
        System.out.println(arr2.size());//4

    }
}

总结:Collection接口中交集、差集、并集方法比较重要。
对于三者的概念不清楚可以查看一个视频 概念

3.Iterator迭代器接口
  1. 遍历
    iterator() 返回迭代器对象,用于集合遍历

集合元素的遍历操作,使用迭代器Iterator接口。
1.内部的方法:hashNext() 和 next()
2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
3.Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Colection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator 接口的对象。

public interface Collection<E> extends Iterable<E> {
   
Iterator<E> iterator();
}

4.Iterator 仅用于遍历集合。

测试迭代器遍历的方法

 @Test
    public void test() {
   
        Collection coll = new ArrayList();
        coll.add(new Integer(123));
        coll.add(new Person("张三",20));
        coll.add(new String("美美哒"));
        coll.add(789);
        //方式一:(不推荐)
        Iterator iterator = coll.iterator();
        for (int i = 0; i < coll.size(); i++) {
   
            System.out.println(iterator.next());
        }
        System.out.println("=============");
        //创建接口的实例,此时得到一个指针
        //如果要遍历两次就需要创建两个接口的实例 因为遍历的第一次后指针在最下面了  创建新的实例后指针在最开始的上面
		//方式二:(推荐)
        Iterator iterator1 = coll.iterator();
        //hasNext() 判断是否有下一个元素
        while (iterator1.hasNext()) {
   
            //next() ①指针下移  ②下移以后集合位置上的元素返回
            System.out.println(iterator1.next());
        }
    }
3.1迭代器执行原理

3.2两种迭代器错误写法

测试代码如下:

    @Test
    public void test2() {
   
        Collection coll = new ArrayList();
        coll.add(new Integer(123));
        coll.add(new Person("张三",20));
        coll.add(new String("美美哒"));
        coll.add(789);

       //遍历:错误方式一
       //直接迭代器对象直接调用next()方法 指针下移,当前元素不等于null,为true
       //接着又调用next(),指针下移,返回当前元素,当输出完789后,又执行next()此时指针下移,没有元素会报异常java.util.NoSuchElementException
       //Iterator iterator = coll.iterator();
       //while (iterator.next() != null) {
   
       //    System.out.println(iterator.next());
       //}

        //错误方式二:
        while (coll.iterator().hasNext()) {
   
            System.out.println(coll.iterator().next());
        }
    }
3.3迭代器中remove方法的使用

remove方法 Iterator里面的默认remove()方法 而不是collection接口中的

public interface Iterator<E> {
   
	/**
         * @throws IllegalStateException if the {@code next} method has not
         *         yet been called, or the {@code remove} method has already
         *         been called after the last call to the {@code next}
         *         method
         */
    default void remove() {
   
        throw new UnsupportedOperationException("remove");
    }
}

上面文档注释的意思如下:
如果还未调用next()或在上一次调用next()方法之后调用了remove方法再次调用 remove就会报IllegalStateException异常

测试代码如下:

    @Test
    public void test3() {
   
        Collection coll = new ArrayList();
        coll.add(new Integer(123));
        coll.add(new Person("张三",20));
        coll.add(new String("美美哒"));
        coll.add(789);
        Iterator iterator = coll.iterator();
        while (iterator.hasNext()) {
   
        //iterator.remove(); 错误写法
            if ("美美哒".equals(iterator.next())){
   
                iterator.remove();
                //iterator.remove(); 错误写法
            }
        }
        Iterator iterator1 = coll.iterator();
        while (iterator1.hasNext()) {
   
            System.out.println(iterator1.next());
        }
    }
3.4增强for循环遍历方式

遍历集合的底层调用Iterator完成操作。
语法:
for(集合元素的类型 局部变量 : 集合对象){
//输出局部变量
}
测试代码:

@Test
    public void test() {
   
        Collection coll = new ArrayList();
        coll.add(new Integer(123));
        coll.add(new Person("张三",20));
        coll.add(new String("美美哒"));
        coll.add(789);

        //增强for循环
        //for(集合元素的类型 局部变量 : 集合对象)
        //内部 还是使用了迭代器
        for (Object obj : coll) {
   
            System.out.println(obj);
        }
        
        int[] arr = new int[]{
   1,2,3,4,5,6};
        //for(数组元素的类型 局部变量 : 数组对象)
        for (int ele : arr) {
   
            System.out.println(ele);
        }
    }

小测试:

@Test
    public void test2() {
   
        String[] arr = new String[]{
   "MM","MM","MM"};
        for (int i = 0; i < arr.length; i++) {
   
            arr[i] = "GG";
        }
       
        for (int i = 0; i < arr.length; i++) {
   
            System.out.println(arr[i]);
        }
    }

问上面打印结果?

GG
GG
GG

如果将循环赋值改为增强for循环,结果又该如何?

        for (String s : arr) {
   
            s = "GG";
        }

答曰:只是将"GG"赋值给了局部变量s,并没有改变arr数组中的值。因此结果

MM
MM
MM

4.Collection子接口一:List

List接口 存储有序的、可重复的数据

ArrayList 是List接口的主要实现类JDK1.2开始,线程不安全,效率高 底层使用 Object[] elementData存储数据。
LinkedListJDK1.2开始,对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储数据。
Vector 是一个古老的实现类,线程安全JDK1.0开始,效率低,底层使用Object[] elementData存储数据。

4.1ArrayList源码分析(JDK1.8)

1.创建ArrayList对象, Object类型的数组被初始化为默认的空数组。

  private static final int DEFAULT_CAPACITY = 10;
  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
   };
  transient Object[] elementData; 

  public ArrayList() {
   
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  }

2.当添加第一个元素时,确保内部容量,此时size大小为0 ,也就是说第一次调用add()方法是,底层才创建了一个长度为10的Object[] elementDate数组。

public boolean add(E e) {
   
    ensureCapacityInternal(size + 1);  // 这步操作除了确保内部容量,实际上还给数组初始化一个大小
    elementData[size++] = e;
    return true;
}

此时形参minCapacity的值为1 ,调用ensureExplicitCapacity()方法参数是方法calculateCapacity(elementData, minCapacity)的结果

private void ensureCapacityInternal(int minCapacity) {
   
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

判断Object数组默认容量是否为空 是就返回 最大值为DEFAULT_CAPACITY=10

private static int calculateCapacity(Object[] elementData, int minCapacity) {
   
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
   
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

判断形参minCapacity=10减去数组长度是否大于0 是就调用grow()

private void ensureExplicitCapacity(int minCapacity) {
   
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

声明了一个oldCapacity变量大小是数组的长度,接着又声明了一个newCapacity变量 变量值是oldCapacity+(oldCapacity>>1)的结果,然后判断newCapacity - minCapacity 是否小于0 如果小于0的 就将形参minCapacity=10 赋值给 变量newCapacity ,最后复制elementData数组,指定新的长度,此时elementData数组对象长度指定为10。

private void grow(int minCapacity) {
   
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

测试Arrays.copyOf(T[] original, int newLength)方法

       Object[] ele = {
   };
        int count = 10;
        //copyOf(T[] original, int newLength)
        //复制指定的数组,指定新的长度
        ele = Arrays.copyOf(ele,count);
        System.out.println(ele.length);//10

结论:建议开发中使用带参的构造器ArrayList list = new ArrayList(int initialCapacity) ;

4.2Vector源码分析
 protected Object[] elementData;
 protected int capacityIncrement;
public synchronized int size() {
   
        return elementCount;//记录数组长度的变量
 }

1.创建Vector对象,底层通过调用了重载的构造器方法创建了长度为10的数组。

简单来说:当创建Vector对象是,底层创建了长度是10的Object[] 数组elementData。
当调用add(), 此次添加导致底层elecmentData数组容量不够,则扩容,默认情况下,扩容为原来2倍,同时将原有数组中数据复制到新的数组中。

public Vector() {
   
    this(10);
}
public Vector(int initialCapacity) {
   
    this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
   
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

2.添加元素的源码:

public synchronized boolean add(E e) {
   
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}
private void ensureCapacityHelper(int minCapacity) {
   
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}


private void grow(int minCapacity) {
   
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
4.3LinkedList源码分析

LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素、同时,定义内部类Node,作为LinkedList保存数据的基本结构。Node除了保存数据,还定义了两个变量
pre 变量记录前一个元素的位置
next 变量记录下一个元素的位置

源码如下:


 transient Node<E> first;
    
 transient Node<E> last;


//1.创建LinkedList对象
public LinkedList() {
   
}
//2.集合尾部添加元素
public boolean add(E e) {
   
    linkLast(e);
    return true;
}

private static class Node<E> {
   
    E item;
    Node<E> next;
    Node<E> prev;
    Node(Node<E> prev, E element, Node<E> next) {
     
        this.next = next;
        this.prev = prev;
    }
}

void linkLast(E e) {
   
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
4.4List接口常用方法
  1. void add(int index,Object ele):在index位置插入ele元素
  2. boolean addAll(int index,Collection eles):从index位置开始将eles中的所有元素添加起来
  3. Object get(int index):获取指定index位置的元素
  4. int indexOf(Object obj):返回obj在集合中首次出现的位置
  5. int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
  6. Object remove(int index):移除指定index位置的元素,并返回此元素
  7. Object set(int index,Object ele):设置指定index位置的元素为ele
  8. List subList(int fromIndex,int toIndex):返回从fromIndex到toIndex位置的子集合

测试方法代码如下:

    @Test
    public void test() {
   
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(456);
        list.add(new Person("Tom",23));
        list.add(456);
        //void add(int index,Object ele):在index位置插入ele元素
        list.add(1,"小伙子");
        System.out.println(list.toString());
        //boolean addAll(int index,Collection eles):从index位置开始将eles中的所有元素添加起来
        List list1 = Arrays.asList(123, 789);
        list.addAll(list1);
        System.out.println(list);
        System.out.println(list.size());//7
        //Object get(int index):获取指定index位置的元素
        Object o = list.get(3);
        System.out.println(o);
        //int indexOf(Object obj):返回obj在集合中首次出现的位置 如果没有找到就返回-1
        System.out.println(list.indexOf(456));
        //System.out.println(list.indexOf(4564));
        //int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
        System.out.println(list.lastIndexOf(456));
        //Object remove(int index):移除指定index位置的元素,并返回此元素
        Object remove = list.remove(6);
        System.out.println(remove);
        System.out.println(list);
        //Object set(int index,Object ele):设置指定index位置的元素为ele
        list.set(1,"倍棒儿");
        System.out.println(list);
        //List subList(int fromIndex,int toIndex):返回从fromIndex到toIndex位置的子集合
        List subList = list.subList(2, 6);
        System.out.println(subList);
        System.out.println(list);
    }
4.5List接口遍历

方式一:迭代器
方式二: 增强for循环
方式三: 普通for循环

@Test
public void test2(){
   
    ArrayList list = new ArrayList();
    list.add(123);
    list.add("虎牙直播");
    list.add(456);
    list.add("飞哥大英雄");
    //遍历集合 方式一:迭代器
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
   
        System.out.println(iterator.next());
    }
    System.out.println("***********************");
    //方式二:增强for循环
    for (Object obj : list) {
   
        System.out.println(obj);
    }
    System.out.println("***********************");
    //方式三:普通for循环
    for (int i = 0; i < list.size(); i++) {
   
        System.out.println(list.get(i));
    }
}

必须掌握

ArrayList中:
增 add(Object obj)
删 remove(int index) / remove(Object obj)
改 set(int index, Object obj)
查 get(int index)
插 add(int index,Object obj)
长度 size()
遍历:    1.迭代器
        2.增强for循环
        3.普通for循环
5.Collection子接口二:Set

Set接口:存储无序的、不可重复的数据

HashSet:作为Set接口的主要实现类:线程不安全的:可以存储null值 底层是 数组+链表的结构。

LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历。
TreeSet:可以按照添加对象的指定属性,进行排序(所以添加的元素必须是相同类的对象,否则会报异常,下面演示)


一、 Set接口:存储无序的、不可重复的数据
  1. 无序的:不是随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
  2. 不可重复的:保证添加的元素按照equals()判断时,不能返回true,即:相同的元素只能添加一个

HashSet添加顺序解释:

二、添加元素的过程:以HashSet为例:
   我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
   此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组
   此位置上是否已经有元素:
      如果此位置上没有其他元素,则元素a添加成功 --->情况1
      如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
                如果hash值不相同,则元素a添加成功。---->情况2
                如果hash值相同,进而需要调用元素a所在类的equals()方法:
                         equals()返回true,元素a添加失败
                         equals()返回false,元素a添加成功。 ---->情况3
  对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
  jdk 7:元素a放到数组中,指向原来的元素、
  jdk 8:原来的元素在数组中,指向元素a
  总结:七上八下

说明:
1.Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法
2.要求:向Set中添加的数据、其所在的类一定要重写hashCode()和equals()
要求:重写的hashCode()和equals()尽可能保持一致性,相等的对象必须具有相等的散列码(哈希值)
重写hashCode()方法的基本原则:

  1. 在程序运行时,同一个对象多次调用hashCode()方法应该返回相同的值。
  2. 当两个对象的equals()方法比较返回true时,这两个对象的hashCode()方法的返回值也应该相等。
  3. 对象中用作equals()方法比较Field,都应该用来计算hashCode值。

User类:

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

    public User() {
   
    }

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

    public String getName() {
   
        return name;
    }

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

    public int getAge() {
   
        return age;
    }

    public void setAge(int age) {
   
        this.age = age;
    }

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

    @Override
    public boolean equals(Object o) {
   
        System.out.println("User  equals()....");
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }

    @Override
    public int hashCode() {
   
        System.out.println("User hashCode()....");
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

HashSet类测试:

 @Test
      @Test
    public void test() {
   
        Set set = new HashSet();
        set.add(123);
        set.add(456);
        set.add(456);
        set.add("联盟");
        set.add(new User("curry",33));
        set.add(new User("curry",33));
        set.add("BB");

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

测试结果:
set.add(new User(“curry”,33));
可以看到调用add()方法,会调用自定义类的hashCode()方法,当第下一次继续添加时,也会计算哈希值,此时哈希值一样,会调用equals()判断两个对象最终是否相等。

User hashCode()…
User hashCode()…
User equals()…
BB
456
123
联盟
User{name=‘curry’, age=33}

LinkedHashSet类测试:
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据的前一个数据和后一个数据
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet

 @Test
    public void test2(){
   
        Set set = new LinkedHashSet();
        set.add(123);
        set.add(456);
        set.add(456);
        set.add("联盟");
        set.add(new User("curry",33));
        set.add(new User("curry",33));
        set.add("BB");

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

测试结果:

User hashCode()…
User hashCode()…
User equals()…
123
456
联盟
User{name=‘curry’, age=33}
BB

TreeSet类测试:
1.向TreeSet中添加的数据,要求是相同类的对象。

如果添加的不是相同的类的对象,会报异常 java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

测试代码:

@Test
    public void test3() {
   
        TreeSet set = new TreeSet();
        set.add("Tom");
        set.add(123);
        set.add(456);
    }

如果添加的是自定义类的对象,此类如果不实现自然排序和定制排序会报异常java.lang.ClassCastException: com.li.demo2.User cannot be cast to java.lang.Comparable
测试代码:

  @Test
    public void test3() {
   
        TreeSet set = new TreeSet();
        set.add(new User("Tom",23));
        set.add(new User("Jack",42));
        set.add(new User("Curry",12));
        set.add(new User("Marry",5));
        set.add(new User("Jim",38));
    }

正确方式(String实现了Comparable):

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

测试代码:

@Test
    public void test(){
   
        Set set = new TreeSet();
        //对添加的对象进行排序 从小到大顺序
        set.add("sdf");
        set.add("df");
        set.add("zcv");
        set.add("wer");

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

自定义类的排序
2.两种排序方法:自然排序(实现Comparable接口)和定制排序(Comparator)
3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()
4.定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()

测试代码(自然排序):

User类的变动:

public class User implements Comparable {
   
//属性、无参构造、有参构造、set、get、toString方法省略
//按照姓名从小到大排列,并且年龄也从小到大排列
//按照姓名从大到小排列 return -this.name.compareTo(user.name);
@Override
public int compareTo(Object o) {
   
    if (o instanceof User) {
   
        User user = (User)o;
        int result = this.name.compareTo(user.name);
        if (result != 0) {
   
            return result;
        }else {
   
            return Integer.compare(this.age,user.age);
        }
    }else {
   
        throw new RuntimeException("输入的类型不匹配");
    }
}
}
 @Test
    public void test2(){
   
        TreeSet set = new TreeSet();
        set.add(new User("Tom",23));
        set.add(new User("Tom",10));
        set.add(new User("Jack",42));
        set.add(new User("Curry",12));
        set.add(new User("Marry",5));
        set.add(new User("Jim",38));

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

测试结果:

User{name=‘Curry’, age=12}
User{name=‘Jack’, age=42}
User{name=‘Jim’, age=38}
User{name=‘Marry’, age=5}
User{name=‘Tom’, age=10}
User{name=‘Tom’, age=23}

测试代码(定制排序):

  @Test
    public void test2(){
   
        //按照年龄从小到大排序
        Comparator com = new Comparator() {
   
            @Override
            public int compare(Object o1, Object o2) {
   
                if (o1 instanceof User && o2 instanceof User) {
   
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }else {
   
                    throw new RuntimeException("输入的类型不匹配");
                }
            }
        };
        TreeSet set = new TreeSet(com);
        set.add(new User("Tom",23));
        set.add(new User("Tom",10));
        set.add(new User("Jack",42));
        set.add(new User("Curry",12));
        set.add(new User("Marry",5));
        set.add(new User("Jim",38));

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

执行结果:

User{name=‘Marry’, age=5}
User{name=‘Tom’, age=10}
User{name=‘Curry’, age=12}
User{name=‘Tom’, age=23}
User{name=‘Jim’, age=38}
User{name=‘Jack’, age=42}

6.Map接口

Map实现类的说明:

/----Map:双列数据,存储key-value对的数据               
       /----HashMap:作为主要实现类,线程不安全,效率高,存储null的key和value。
	           /----LinkedHashMap:作为HashMap的子类,保证在遍历map元素时,可以按照添加的顺序 实现遍历。
			            原因:在原有的HashMao底层结构基础上,添加了一对指针,指向前一个和后一个元素。
			            对于频繁的遍历操作,此类执行效率高于HashMap。
       /----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序,底层使用红黑树。
       /----Hashtable:作为一个古老实现类,线程安全,效率低,不能存储null的key和value。
	           /----Properties:常用来处理配置文件。key和value都是String类型 。
HashMap的底层:数组+链表(jdk7之前)
   	          数组+链表+红黑树(jdk8)

Map结构(key-value)的理解:
Map中的key:无序的、不可重复的,使用Set存储所有的key (key所在类要重写equals()和hashCode() 以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value(value所在的类要重写equals())
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry。

HashMap的底层原理?以jdk7为例说明:
HashMap map = new HashMap();
在实例化以后,底层创建了长度是16的一维数组Entry[] table;
执行put操作
map.put(key1,value1);

首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在 Entry数组中的存放位置。
如果此位置 上的数据为空,此时key1-value1添加成功。----情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
				如果key1的哈希值和已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
				如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2):
							如果equals()返回false;此时key1-value1添加成功。----情况3
							如果equals()返回true;此时value1替换value2。

说明:关于情况2和情况3,此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量2倍,并将原有的数据复制过来。
jdk8相较于jdk7在底层实现方面的不同:
1.new HashMap():底层没有创建一个长度为16的数组
2.jdk8底层的数组是:Node[],而非Entry[]
3.首次调用put()方法时,底层创建长度为16的数组
4.jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树
当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所有数据改为使用红黑树存储。

Map常用方法:

  1. Object(Object key,Object value) 将指定key-value添加到(或修改)当前map对象中
  2. void putAll(Map m)将m中的所有key-value对存放到当前map中
  3. Object remove(Object key) 移除指定key的key-value对,并返回value
  4. void clear() 情况当前map中所有数据

测试代码:

 @Test
    public void test1() {
   
        HashMap map = new HashMap();
        //Object(Object key,Object value) 将指定key-value添加到(或修改)当前map对象中
        map.put("tom",123);
        map.put("curry",456);
        map.put("jack",789);
        map.put("tom",332);
        System.out.println(map);
        HashMap map1 = new HashMap();
        map.put("tom1",456);
        map.put("curry1",247);
        map.put("jack1",247);
        //void putAll(Map m)将m中的所有key-value对存放到当前map中
        map.putAll(map1);
        System.out.println(map);
        //Object remove(Object key) 移除指定key的key-value对,并返回value
        Object value = map.remove("tom");
        System.out.println(value);
        System.out.println(map);
        //void clear() 情况当前map中所有数据
        map.clear();
        System.out.println(map.size());
        System.out.println(map);
    }
  1. Object get(Object key) 获取指定key对应的value
  2. boolean containsKey(Object key) 是否包含指定的key 如果不存在该key,则返回null
  3. boolean containsValue(Object value) 是否包含指定的value
  4. int size() 返回map中key-value对的个数
  5. boolean isEmpty() 判断当前map是否为空
  6. boolean equals(Object obj) 判断当前map和参数对象obj是否相等

测试代码:

    @Test
    public void test2() {
   
        HashMap map = new HashMap();
        map.put("tom",123);
        map.put("curry",456);
        map.put("jack",789);
        map.put("tom",332);
        map.put("mack",332);
        System.out.println(map);
        //Object get(Object key) 获取指定key对应的value
        Object value = map.get("tom");
        System.out.println(value);
        //boolean containsKey(Object key) 是否包含指定的key 如果不存在该key,则返回null
        boolean key = map.containsKey("curry");
        System.out.println(key);
        //boolean containsValue(Object value) 是否包含指定的value
        boolean value1 = map.containsValue(332);
        System.out.println(value1);
        //int size() 返回map中key-value对的个数
        System.out.println(map.size());
//        map.clear();
        //boolean isEmpty() 判断当前map是否为空
        System.out.println(map.isEmpty());
        //boolean equals(Object obj) 判断当前map和参数对象obj是否相等
        //判断原理:判断map中每一个key-value对和obj的哈希值和equals是否相等
        HashMap map2 = new HashMap();
        map2.put("tom",123);
        map2.put("curry",456);
        map2.put("jack",789);
        map2.put("tom",332);
        map2.put("mack",332);
        System.out.println(map);
        System.out.println(map2);
        System.out.println(map.equals(map2));

    }
  1. Set keySet() 返回所有key构成的Set集合
  2. Collection Values() 返回所有value构成的Collection集合
  3. Set entrySet() 返回所有key-value对构成的Set集合

测试代码:

    @Test
    public void test4() {
   
        HashMap map = new HashMap();
        map.put("tom",123);
        map.put("curry",456);
        map.put("jack",789);
        map.put("tom",332);
        map.put("mack",332);
        //Set keySet() 返回所有key构成的Set集合
        Set keySet = map.keySet();
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()) {
   
            System.out.println(iterator.next());
        }

        System.out.println();

        //Collection Values() 返回所有value构成的Collection集合
        Collection value = map.values();
        for (Object obj : value) {
   
            System.out.println(obj);
        }

        System.out.println();

        //Set entrySet() 返回所有key-value对构成的Set集合
        //遍历方式一:
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()) {
   
            Object obj = iterator1.next();
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey()+ "=" + entry.getValue());
        }

        System.out.println();
        //遍历方式二:
        Set set = map.keySet();
        Iterator iterator2 = set.iterator();
        while (iterator2.hasNext()) {
   
            Object key = iterator2.next();
            Object val = map.get(key);
            System.out.println(key + "=" +val);
        }
    }

TreeMap两种添加方式:
向TreeMap中添加key-value,要求key必须是相同类创建的对象
因为要按照key进行排序:自然排序、定制排序
自然排序:

@Test
    public void test2(){
   
        TreeMap map = new TreeMap();
        map.put(new User("Tom",23),83);
        map.put(new User("Jim",35),90);
        map.put(new User("Marry",13),96);
        map.put(new User("Curry",52),73);
        //遍历方式一:
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()) {
   
            Object obj = iterator1.next();
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey()+ "--->" + entry.getValue());
        }
    }

定制排序:

 @Test
    public void test(){
   
        TreeMap map = new TreeMap(new Comparator() {
   
            @Override
            public int compare(Object o1, Object o2) {
   
                if (o1 instanceof User && o2 instanceof User) {
   
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }else {
   
                    throw  new RuntimeException("输入的类型不匹配!");
                }
            }
        });
        map.put(new User("Tom",23),83);
        map.put(new User("Jim",35),90);
        map.put(new User("Marry",13),96);
        map.put(new User("Curry",52),73);
        //遍历方式一:
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()) {
   
            Object obj = iterator1.next();
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey()+ "--->" + entry.getValue());
        }
    }

Properties处理属性文件:
Properties类是Hashtable的子类,该对象处理属性文件
由于属性文件里的key、value都是字符串类型,所以Properties里的key和value都是字符串类型。
存取数据时,建议使用setProperties(String key,String value)方法和getProperty(String key)方法

创建一个jdbc.properties文件存放用户名和密码

username=lisi
password=123456
public class PropertiesTest {
   
    public static void main(String[] args) {
   
        FileInputStream in = null;
        try {
   
            Properties pro = new Properties();
            in = new FileInputStream("jdbc.properties");
            pro.load(in);//读取配置文件
            String username = pro.getProperty("username");
            String password = pro.getProperty("password");
            System.out.println("username=" + username + " password=" + password);
        } catch (IOException e) {
   
            e.printStackTrace();
        } finally {
   
            try {
   
                if(null != in){
   
                    in.close();
                }
            } catch (IOException e) {
   
                e.printStackTrace();
            }
        }

    }
}

执行结果:

username=lisi password=123456

7.Collections工具类

Collections是一个操作Set、List和Map等集合的工具类
Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。

  1. void reverse(List list)反转List中元素的顺序
  2. void shuffle(List list)对List集合元素进行随机排序
  3. void sort(List list)根据元素的自然排序对指定List元素按升序排序
  4. void sort(List list,Comparator comp)根据指定的Comparator产生的顺序对List集合进行排序
  5. void swap(List list,int i,int j)将指定list集合中的i处元素和j处元素进行交换
//排序操作(均为static方法)
    //方法直接修改了集合
    @Test
    public void test1() {
   
        List list = new ArrayList();
        list.add("uiop");
        list.add("qwer");
        list.add("asdf");
        list.add("zxcv");
        System.out.println("原集合->"+list);
        //void reverse(List list)反转List中元素的顺序
        Collections.reverse(list);
        System.out.println("反转后的集合->"+list);
        //void shuffle(List list)对List集合元素进行随机排序
        Collections.shuffle(list);
        System.out.println("随机排序->"+list);
        //void sort(List list)根据元素的自然排序对指定List元素按升序排序
        Collections.sort(list);
        System.out.println("自然排序(升序)->"+list);
        //void sort(List list,Comparator comp)根据指定的Comparator产生的顺序对List集合进行排序
        Collections.sort(list, new Comparator<Object>() {
   
            @Override
            public int compare(Object o1, Object o2) {
   
                if (o1 instanceof String && o2 instanceof String) {
   
                    String str = (String)o1;
                    String str2 = (String)o2;
                    return str.compareTo(str2);
                }else {
   
                    throw new RuntimeException("输入类型不匹配");
                }
            }
        });
        System.out.println("定制排序->"+list);
        //void swap(List list,int i,int j)将指定list集合中的i处元素和j处元素进行交换
        Collections.swap(list,1,3);
        System.out.println(list);
    }
  1. Object max(Collection coll)根据元素的自然顺序,返回给定集合中的最大元素
  2. Object max(Collection coll,Comparator comp)根据Comparator指定的顺序,返回给定集合中的最大元素
  3. Object min(Collection coll)
  4. Object min(Collection coll,Comparator comp)
  5. int frequency(Collection coll,Object obj) 返回指定集合中指定元素的出现次数
  6. void copy(List dest,List src) 将src中的内容复制到dest中
  7. boolean replaceAll(List list,Object oldVal,Object newVal) 使用新值替换List对象的所有旧值
    //查找、替换
    @Test
    public void test2() {
   
        List list = new ArrayList();
        list.add("uiop");
        list.add("qwer");
        list.add("qwer");
        list.add("qwer");
        list.add("zzzw");
        list.add("asdf");
        list.add("zxcv");
        System.out.println(list);
        //Object max(Collection coll)根据元素的自然顺序,返回给定集合中的最大元素
        Comparable max = Collections.max(list);
        System.out.println(max);
        //Object max(Collection coll,Comparator comp)根据Comparator指定的顺序,返回给定集合中的最大元素
        Object max1 = Collections.max(list, new Comparator<Object>() {
   
            @Override
            public int compare(Object o1, Object o2) {
   
                if (o1 instanceof String && o1 instanceof String) {
   
                    String str = (String) o1;
                    String str2 = (String) o2;
                    return str.compareTo(str2);
                } else {
   
                    throw new RuntimeException("输入的类型不匹配");
                }
            }
        });
        System.out.println(max1);
        //Object min(Collection coll)
        //Object min(Collection coll,Comparator comp)
        //int frequency(Collection coll,Object obj) 返回指定集合中指定元素的出现次数
        int frequencey = Collections.frequency(list, "qwer");
        System.out.println(frequencey);
        List dest = Arrays.asList(new Object[list.size()]);
        System.out.println(dest);
        //void copy(List dest,List src) 将src中的内容复制到dest中
        //错误写法:抛出异常 Source does not fit in dest
//        List list1 = new ArrayList();
//        Collections.copy(list1,list);
        Collections.copy(dest,list);
        System.out.println("list2->"+dest);

        //boolean replaceAll(List list,Object oldVal,Object newVal) 使用新值替换List对象的所有旧值
        boolean result = Collections.replaceAll(list, "qwer", "1111");
        System.out.println(result);
        System.out.println(list);
        /*
        Collections类中提供了多个synchronizedXxx()方法,该方法可使将指定集合包装成多线程同步的集合,
        从而可以解决多线程并发访问集合时的线程安全问题
         */
        List list1 = Collections.synchronizedList(list);

    }

以上就是集合的所有内容,喜欢的小伙伴一键三连吧!


转载:https://blog.csdn.net/lirui1212/article/details/117338042
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场