集合

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2022-06-25

一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储,。另一方面,使用Array存储对象方面具有一些弊端,而Java集合就想一种容器,可以动态地把多个对象的引用放入容器中。

1、数组在内存存储方面的特点

  • 数组初始化之后,长度就确定了
  • 数组声明的类型,就决定了进行元素初始时的类型

2、数组在存储数据方面的弊端

  • 数据初始化之后,长度不可变,不利于扩展
  • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数。
  • 数组存储的数据是有序的,可以重复的。

Java集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。

Java集合可分为CollectionMap两种体系:

  • Collection接口:单列数据,用来存储一个一个的对象
    • List:有序的、可重复的集合
      • ArrayListLinkedListVector
    • Set:无序的、不可重复的集合
      • HashSetLinkedHashSetTreeSet
  • Map接口:双列数据,保存具有映射关系键值对的集合,用来存储一对一对的数据。
    • HashMapLinkedHashMapTreeMapHashtableProperties

Collection

Collection的常用方法

import org.junit.Test;

import java.util.*;

/**
 * Collection接口中方法的使用
 */
public class CollectionTest {
  @Test
  public void test() {
    Collection coll = new ArrayList();

    // 1、add(Object o); 添加元素
    coll.add("AA");
    coll.add(123); // int 会被自动装箱为 Integer类型
    coll.add(new Date());

    // 2、size(); 获取长度
    System.out.println(coll.size()); // 3

    Collection coll1 = new ArrayList();
    coll1.add(456);
    coll1.add("BB");

    // 3、addAll(Collection c); 将c集合中的元素添加到当前集合中
    coll.addAll(coll1);
    System.out.println(coll.size()); // 5
    System.out.println(coll); // "[AA, 123, Tue Jan 18 21:36:42 CST 2022, 456, BB]"

    // 4、isEmpty(); 判断集合是否为空
    System.out.println(coll.isEmpty()); // false 原理就是 return size == 0;

    // 5、clear(); 清空结合元素
    coll.clear();

    System.out.println(coll.isEmpty()); // true

    Collection coll3 = new ArrayList();
    coll3.add(new String("CC"));
    coll3.add(123);
    coll3.add("DD");

    // 6、contains(Object o); 判断集合是否存在o元素 原理是遍历使用equals进行比较
    // 所以向Collection接口的实现类的对象中添加数据o时,要求o所在类重写equals方法
    System.out.println(coll3.contains("DD")); // true
    System.out.println(coll3.contains(123)); // true
    System.out.println(coll3.contains(new String("CC"))); // true
    System.out.println(coll3.contains("CC")); // true 判断的是值

    Date d = new Date();
    coll3.add(d);

    System.out.println(coll3.contains(d)); // true
    System.out.println(coll3.contains(new Date())); // true,Date包装类重写了equals方式比较的也是值 而不是地址
    // 等同于 new Date().equals(new Date());
    System.out.println("Date equals =>" + new Date().equals(new Date())); // true

    Person1 p = new Person1("jack", 18);
    coll3.add(p);

    System.out.println(coll3.contains(p)); // true
    System.out.println(coll3.contains(new Person1("jack", 18))); // false
    // Person1类没有重写equals方法就会调用Object里面的equals 即进行引用的比较
    // Object equals的实现就是  return this == obj;

    // 7、containsAll(Collection c); 判断集合是否被当前集合所包含
    Collection c1 = new ArrayList();
    c1.add(123);
    c1.add(345);
    c1.add("AA");

    // Arrays.asList(); 将参数集合转换成集合
    Collection c2 = Arrays.asList(123, "AA");

    System.out.println(c2.containsAll(c1)); // false
    System.out.println(c1.containsAll(c2)); // true

    // 8、remove(Object o); 删除一个元素,也会进行equals比较,相等才代表存在,才能被删除
    boolean isSuccess = c1.remove(123);
    boolean isSuccess2 = c1.remove(1234);
    System.out.println(isSuccess); // true
    System.out.println(isSuccess2); // false

    // 9、removeAll(Collection c);
    // 从当前集合中移出c集合中所有的元素,需要当前元素有,即移除当前集合中与集合c的交集,
    // 即修改当前集合为 当前集合与集合c的差集

    // 10、retainAll(Collection c); 求两个集合的交集,并只保存当前集合中存在于当前集合的元素,
    // 即修改当前集合为 当前集合与集合c的交集。

    // 11、equals(Object o); 比较当前集合与参数o是否是同一个集合(顺序也要求一致),是遍历进行equals比较

    // 12、hashCode(); 返回当前集合的哈希值
    Collection c3 = new ArrayList();
    c3.add(123);
    System.out.println(c3.hashCode()); // 154

    c3.add("AA");
    System.out.println(c3.hashCode()); // 6854

    // 13、toArray(); 将集合转为数组
    Object[] objects = c3.toArray();
    for (int i = 0; i < objects.length; i++) {
      System.out.println(objects[i]); // 123 AA
    }

    // 数组转换集合就是使用asList
    List<String> strings = Arrays.asList(new String[]{"1", "2"});
    System.out.println(strings.contains("1")); // true
    System.out.println(strings.size()); // 2
    System.out.println(strings); // [1, 2]

    int[] arr = new int[]{1, 2, 3};
    List<int[]> ints = Arrays.asList(arr); // adList([1, 2, 3])
    System.out.println(ints.contains(1)); // false
    System.out.println(ints.size()); // 输出是1 => 将arr当成一个元素了
    System.out.println(ints); // [[I@1888ff2c] 打印的是地址值

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

    // new int[]的是一个整型数组,这个arr整个才算是一个对象,而new Integer[] 是一个对象数组,每个元素都一个Integer对象
    // 所有会被拆分成List的三个元素,从List<int[]> 和 List<Integer> 就可以看出来
    // 前者的元素需要是int型数组,后者元素需要是Integer对象
  }
}

Collection的遍历

import org.junit.Test;

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

public class CollectionIterator {

  @Test
  public void test1() {
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new String("hello"));
    coll.add(false);

    Iterator collIterator = coll.iterator();

    // next方法
    // System.out.println(collIterator.next()); // 123
    // System.out.println(collIterator.next()); // 456
    // System.out.println(collIterator.next()); // hello
    // System.out.println(collIterator.next()); // false
    // 遍历完成后在访问next就会报异常 NoSuchElementException异常
    // System.out.println(collIterator.next());

    // 使用for循环
    // for (int i = 0; i < coll.size(); i++) {
    //   System.out.println(collIterator.next());
    // }

    // 推荐方式,使用while
    // hasNext() 判断是否还有下一个元素
    while (collIterator.hasNext()) {
      // next(): 指针下移,将下移以后集合位置上的元素返回
      System.out.println(collIterator.next());
    }
  }

  @Test
  public void test2() {
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new String("hello"));
    coll.add(false);

    Iterator collIterator = coll.iterator();
    while (collIterator.hasNext()) {
      Object obj = collIterator.next();

      if ("hello".equals(obj)) {
        // remove(): 删除集合中的数据,删除当前指针位置中的元素
        // 调用的是迭代器对象的remove方法,而不是集合Collection的remove
        collIterator.remove();

        // 如果还未调用next() 指针还在初始位置 此时不指向任何元素 调用remove会报illegalStateException异常

        // 调用了next()方法之后调用了remove方法,如果再调用一次remove方法也会报illegalStateException异常
        // 因为此时被指针指向的元素已经被remove掉了,不能重复调用
      }
    }

    // 想要再次遍历,需要重新生产迭代器对象
    collIterator = coll.iterator();
    while (collIterator.hasNext()) {
      System.out.println(collIterator.next());
    }
  }
}

foreach循环

import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;

public class ForTest {
  @Test
  public void test() {
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add("hello");

    // for (集合元素的类型  局部变量: 集合对象) {}
    for(Object obj: coll) {
      System.out.println(obj);
    }
  }


  @Test
  public void test2() {
    int[] arr = new int[]{1,2,3,4,5};
    for(int n: arr) {
      System.out.println(n);
    }
  }
}

List

List接口是Collection的子接口之一,常用来存储有序的、可重复的数据,是动态的,实现该接口的常用类有ArrayList、LinkedList、Vector。

这三者的相同点是三个类都实现了List接口,存储数据的特点相同:存储有序的、可重复的数据。不同点如下:

  • ArrayList: 作为List接口的主要实现类;线程不安全的,效率高。底层使用Object[] elementData存储

  • LinkedList: 对于频繁的插入、删除操作,使用此类效率比ArrayList高,底层使用双向链表存储。

  • Vector: 作为List接口的古老实现类;线程安全的,效率低。底层使用Object[] elementData存储,与ArrayList不同的是,当需要扩容时,扩容至原来容量的两倍。

ArrayList源码分析

/* jdk7 */

ArrayList list = new ArrayList(); // 底层创建了长度是10的Object[]数组elementData
list.add(123); // elementData[0] = new Integer(123);
// ...
list.add(1); // 如果此次添加导致底层elementData数组容量不够,默认是10,则扩容
// 默认扩容为原来容量的1.5倍,同时需要将原有的数组中的数据复制到新的数组中
// 结论:建议开发中使用带参的构造器: ArrayList list = new ArrayList(int capacity);


/* jdk8 */

ArrayList list = new ArrayList(); // 底层Object[] elementData初始化为{},并没有创建长度为10的数组
list.add(123); // 第一次调用add()时,底层才创建了长度10的数组,并将数组123添加为elementData[0] = new Integer(123);
// 后续与jdk7一致

总结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省了内存。

LinkedList源码分析

LinkedList list = new LinkedList(); // 内部声明了Node尅性的first和last属性,默认值为null
list.add(123); // 将123封装到Node中,创建了Node对象

// 其中Node的定义如下, 即是一个双向链表
// private static class Node<E> {
//   E item;
//   Node<E> next;
//   Node<E> prev;

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

List的常用方法

  • void add(int index, Object ele): 在index位置插入ele元素
  • boolean addAll(int index, Collection eles): 从index位置开始将eles中所有的元素都添加进来
  • Object get(int index): 获取指定index位置的元素
  • int indexOf(Object obj): 返回obj在集合中首次出现的位置,不存在返回-1。
  • int LastIndexOf(Object obj): 返回obj在集合中末次出现的位置,不存在返回-1。
  • Object remove(int index) / Object remove(Object obj): 移出指定index位置的元素 或者 删除指定元素(eg. list.remove(new Integer(123))),并返回此元素。
  • Object set(int index, Object ele): 设置指定index位置的元素为ele
  • List subList(int fromIndex, int toIndex): 返回从fromIndex到toIndex位置的子集合

Set

Set接口也是Collection的子接口之一,用来存储无序的、不可重复的数据。Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。

要求:

  • 向Set中添加的数据,其所在的类一定要重写hashCode()equals()方法,与存储数据时比较异同的方式有关。

  • 重写的hashCode()equals()方法要尽量保持一致性,即相等的对象必须具有相等的散列码(即哈希值)。

其常用的实现类有三个HashSet、LinkedHashSet、TreeSet。

HashSet

HashSet: 作为Set接口的主要实现类;线程不安全的;可以存储null值。

import org.junit.Test;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SetTest {
  @Test
  public void test1() {
    Set s = new HashSet();
    s.add(123);
    s.add(456);
    s.add(false);
    s.add("AA");
    s.add(new String("CC"));
    s.add("BB");
    s.add(new Person2("jack"));
    s.add(123);
    s.add(789);
    s.add(new Person2("bob"));
    s.add(new Person2("jack"));

    Iterator iterator = s.iterator();
    while(iterator.hasNext()) {
      System.out.println(iterator.next());
      // AA
      // CC
      // BB
      // false
      // 789
      // 456
      // com.cz.java.Person2@61a52fbd
      // 123
      // com.cz.java.Person2@233c0b17
      // com.cz.java.Person2@63d4e2ba

      // 无序性不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加。
      // 而是根据数据的哈希值决定的

      // 不可重复性:保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个
    }
  }
}

class Person2 {
  String name;

  Person2(String _name) {
    this.name = _name;
  }
}

添加元素的过程(以HashSet为例):

  • 向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值。

  • 此哈希值接着通过某种算法计算出HashSet底层数组中的存放位置(即为索引位置,一般通过取模整个HashSet的容量来获得)

  • 判断底层数组此位置上是否有元素,如果没有,则元素a添加成功。如果有其他元素b(或以链表形式存在的多个元素),如果哈希值不相同(取模的结果可能一样,如果链表形式存在多个,则需要多次比较),则元素a添加到此位置的链表上,如果哈希值相同,进而需要调用元素a所在类的equals方法,返回true,则添加失败,反之将元素a添加到此位置的链表上。

LinkedHashSet

LinkedHashSet: 作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历。

存放数据的方式和HashSet一样,存放位置是无序性的,但是在存放的同时会额外添加两个引用,记录此数据的前一个数据和后一个数据,这样对于频繁的遍历操作,LinkedHashSet效率高于HashSet。

TreeSet

TreeSet: 要求放入的数据得是同一个类的实例对象,可以按照添加对象的指定属性,进行排序。

有两种排序方式:自然排序和定制排序。

自然排序中,比较两个对象是否有相同的标准为compareTo()返回0则相同,不再是上面提到的equals();被添加到TreeSet中的对象(除内置类外),其类必须实现Comparable接口。

public class SetTest {
  @Test
  public void test2() {
    TreeSet ts = new TreeSet();
    ts.add(12);
    ts.add(-12);
    ts.add(241);
    ts.add(24);
    ts.add(5);
    // ts.add("123"); // 会报错,必须是同一个类的实例对象

    Iterator tsIterator = ts.iterator();
    while(tsIterator.hasNext()) {
      System.out.println(tsIterator.next());
      // -12 5 12 24 241 // 添加数字会默认从小到大排序
    }
  }

  @Test
  public void test3() {
    TreeSet ts = new TreeSet();
    // java.lang.ClassCastException: class com.cz.java.Person2 cannot be cast to class java.lang.Comparable
    // 添加对象时,如果添加的实例的类没有实现Comparable接口就会报错
    ts.add(new Person2("jack"));
    ts.add(new Person2("bob"));
    ts.add(new Person2("andy"));
    ts.add(new Person2("mike"));
    ts.add(new Person2("jim"));

    Iterator tsIterator = ts.iterator();
    while(tsIterator.hasNext()) {
      System.out.println(tsIterator.next());
      // Person2{name='andy'}
      // Person2{name='bob'}
      // Person2{name='jack'}
      // Person2{name='jim'}
      // Person2{name='mike'}
    }
  }
}

class Person2 implements Comparable {
  String name;

  Person2(String _name) {
    this.name = _name;
  }

  // 按照姓名从小到大排列
  @Override
  public int compareTo(Object o) {
    if (o instanceof  Person2) {
      Person2 p2 = (Person2) o;
      int compare = this.name.compareTo(p2.name);
      if (compare != 0) {
        return compare;
      }
      // 因为compareTo的比较是比较值,为0时则表示两个Person2对象的name值一样
      // 此时应该换一种比较方式进行更深的比较
      //
      // 这里由于只有一个name属性 就return了一个 compare + 1,以保证两个相同name的Person2实例对象能当做不相等
      // 实际场景中 应根据需要改写
      return compare + 1;

    } else {
      throw new RuntimeException("输入的类型不匹配");
    }
  }

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

定制排序中,需要传入一个Comparator的实例对象给TreeSet,并在其中的compare方法中定制自己的排序规则。

public class SetTest {
  @Test
  public void test4() {
    Comparator comparator = new Comparator() {
      @Override
      public int compare(Object o1, Object o2) {
        return 0;
      }
    };

    TreeSet ts = new TreeSet(comparator);
    ts.add(new Person2("jack"));
    ts.add(new Person2("bob"));
    ts.add(new Person2("andy"));
    ts.add(new Person2("mike"));
    ts.add(new Person2("jim"));

    Iterator tsIterator = ts.iterator();
    while(tsIterator.hasNext()) {
      System.out.println(tsIterator.next());
      // 此时由于compare方法都返回的是0,所以就只有一个jack被添加并打印
    }
  }
}

HashSet面试题

import org.junit.Test;
import java.util.HashSet;

public class HashSetTest {
  @Test
  public void test1() {
    HashSet set = new HashSet();
    Person3 p1 = new Person3(1001, "AA");
    Person3 p2 = new Person3(1002, "BB");

    Object o = new Object();

    set.add(p1);
    set.add(p2);
    System.out.println(set);
    // [Person3{id=1002, name='BB'}, Person3{id=1001, name='AA'}]

    p1.name = "CC";
    set.remove(p1);
    // 此时并没有remove掉p1,因为remove是否需要查找该元素是否存在,而此时又是HashSet,所以查找时,会先根据哈希值去找
    // 而此时 name变为了CC,此时哈希值与存储时的name为AA的哈希值不一样了,所以不会找到,就remove了一个寂寞

    // 需要注意的是,如果Person3类没有重写equals方法,此时会可以remove掉的。
    // 因为默认会调用Object的equals方法比较的是引用地址 this == obj;
    System.out.println(set);
    // [Person3{id=1002, name='BB'}, Person3{id=1001, name='CC'}]

    set.add(new Person3(1001, "CC"));
    System.out.println(set);
    // 修改成CC之后,其位置依旧没有改变,依然是那个以name为AA时计算的哈希值所确定的位置
    // 此时add调用,又会根据name为CC计算一个哈希值,根据哈希值定位位置,显然该位置不会存在元素,然后add成功
    // [Person3{id=1002, name='BB'}, Person3{id=1001, name='CC'}, Person3{id=1001, name='CC'}]

    set.add(new Person3(1001, "AA"));
    // 此时根据name为AA计算出哈希值,确定位置,发现位置上存在一个元素,然后进行equals比较
    // 此时,由于p1存储时虽然name是AA,但是后来修改成了CC,此时进行equals比较则为false,则也添加成功,与原AA现CC的1001链在一起。
    System.out.println(set);
    // [Person3{id=1002, name='BB'}, Person3{id=1001, name='CC'}, Person3{id=1001, name='CC'}, Person3{id=1001, name='AA'}]
  }
}

class Person3 {
  int id;
  String name;

  public Person3() {
  }

  public Person3(int id, String name) {
    this.id = id;
    this.name = name;
  }

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

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

    Person3 person3 = (Person3) o;

    if (id != person3.id) return false;
    return name != null ? name.equals(person3.name) : person3.name == null;
  }

  @Override
  public int hashCode() {
    int result = id;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    return result;
  }
}

Map

Map是双列数据,存储key-value对的数据。它的实现类如下:

  • HashMap:作为Map的主要实现类;线程不安全,效率高;存储null的key和value。jdk7之前,底层是数组+链表,jdk8之后,底层是数组+链表+红黑树。

    • 子类LinkedHashMap: 保证在遍历Map元素时,可以按照添加的顺序实现遍历,因为在原有的HashMap底层结构基础上添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。
  • TreeMap:要求key必须是同一个类创建的对象,因为要按照key进行排序,包含自然排序和定制排序。底层使用的是红黑树。

  • Hashtable:“古老”的实现类。线程安全,效率低;不能存储null的key和value。

    • 子类Propertie:常用来处理配置文件,key和value都是String类型。

Map结构的理解:

  • Map中的key是无序的、不可重复的,使用Set存储所有的key。HashMap要求key所在的类要重写equals()hashCode()方法。
  • Map中的value是无序的,可重复的,使用Collection存储所有的Value。同样value所在的类也要重写equals()方法。
  • 一个键值对:key-value构成了一个Entry对象。
  • Map中的Entry:无序的、不可重复的,使用Set存储所有的Entry。

HashMap

HashMap的底层实现原理(以jdk7为例说明):

HashMap map = new HashMap();
// 在实例化以后,底层创建了长度是16的一维数组Entry[] table。

// ...可能已经执行了多次put

Person key1 = new Person("jack");

map.put(key1, 18);
// 首先调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算后,得到在Entry数组中的存放位置。
// 如果此位置上的数据为空,此时的key1-value1添加成功,
// 如果不为空,意味此位置上存在一个或多个数据(以链表形式存在)
// 比较key1和已经存在的一个或多个数据的哈希值,
//    如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。
//    如果key1的哈希值与已经存在的某一个数据的哈希值相同,继续比较,调用key1所在类的equals方法,比较:
//       如果equals返回false,则添加成功。
//       如果equals返回true,使用value1替换相同key的value值,即修改相同key值的value,
//        即如果map.put(key1, 19),则key1与19组成key-value对。
// 在jdk7中,当key的哈希值相同时,以链表的方式存储。

// 在不断的添加过程中,会涉及到扩容问题(超出临界值时且要存放的位置非空值),默认的扩容方式是扩容为原来容量的2倍,并将原有的数据复制过来。

// jdk8相较于jdk7在底层实现方面的不同:
// 1. new HashMap()时底层没有创建一个长度为16的数组
// 2. jdk8底层的数组是 Node[] 而非 Entry[]
// 3. 首次调用put方法时,底层创建长度为16的数组
// 4. jdk7底层结构只有数组+链表,jdk8中底层结构为数组+链表+红黑树
//    当数组的某一个索引位置上的元素以链表形式存在的数据个数大于8 且 当前数组长度大于64时,
//    此时此索引位置上的所有数据改为使用红黑树存储,查找效率提高了。

// 源码中的常量
// DEFAULT_INITIAL_CAPACITY: HashMap的默认容量 16
// DEFAULT_LOAD_FACTOR: HashMap的默认加载因子 0.75
// threshold: 扩容的临界值,等于容量*加载因子,16 * 0.75 = 12
// TREEIFY_THRESHOLD: Bucket中里链表长度大于该默认值时,转化为红黑树,默认是8
// MIN_TREEIFY_CAPACITY: 桶中的Node被树化时最小的hash表容量 64

另外,在new HashSet()的时候实际上是 new HashMap(),add的时候的就是将数据当初map中的key去put,对应的value就是一个空对象new Object()

为什么重写equals()就一定要重写hashCode()方法?

在实际应用中,我们认为两个对象即使不是指向的同一块内存,只要这两个对象的各个字段属性值都相同,那么就认为这两个对象是同一个对象。所以就需要重写equals()方法,即如果两个对象指向内存地址相同或者两个对象各个字段值相同,那么就是同一个对象

对于对象集合的判重,如果一个集合含有100个对象实例,仅仅使用equals()方法的话,那么对于一个对象判重就需要比较4950次,随着集合规模的增大,时间开销是很大的。但是同时使用哈希表的话,就能快速定位到对象的大概存储位置,并且在定位到大概存储位置后,后续比较过程中,如果两个对象的hashCode不相同,也不再需要调用equals()方法,从而大大减少了equals()比较次数。所以从程序实现原理上来讲的话,既需要equals()方法,也需要hashCode()方法。那么既然重写了equals(),那么也要重写hashCode()方法,以保证两者之间的配合关系。

LinkedHashMap

源码中,使用Entry类before和after属性来记录元素的先后顺序,该Entry类继承与HashMap.Node。

常用方法

  • Object put(Object key, Object value):将指定key-value添加到(或修改)当前Map对象中。
  • void putAll(Map m): 将m中的所有key-value对存放到当前map中
  • Object remove(Object key): 移出指定key的key-value对,并返回value。
  • void clear():清空当前map中的所有数据。
  • Object get(Object key):获取指定key对应的value
  • boolean containsKey(Object key):是否包含指定的key
  • boolean containsValue(Object value):是否包含指定的value
  • int size():返回map中key-value对的个数
  • boolean isEmpty():判断当前map是否为空
  • boolean equals(Object obj): 判断当前map和参数对象obj是否相等
  • Set keySet():返回所有key构成的Set集合
  • Collection values():返回所有value构成的Collection集合
  • Set entrySet():返回所有key-value对构成的Set集合

所以Map的遍历如下:

import org.junit.Test;
import java.util.*;

public class MapTest {
  @Test
  public void test1() {
    Map map = new HashMap();
    map.put("AA", 123);
    map.put(45, 123);
    map.put("BB", 456);

    // 遍历所有的key集
    Set s = map.keySet();
    Iterator iterator = s.iterator();
    while(iterator.hasNext()) {
      System.out.println(iterator.next());
    }

    // 遍历所有value集
    Collection values = map.values();
    for(Object obj: values) {
      System.out.println(obj);
    }

    // 遍历所有的key-value对
    Set s2 = map.entrySet();
    Iterator iterator2 = s2.iterator();
    while(iterator2.hasNext()) {
      Object obj = iterator2.next();
      // 本身是一个Entry,所以可以如下强转
      // Map.Entry entry = (Map.Entry) obj;
      // System.out.println(entry.getKey() + " ---> " + entry.getValue())
      System.out.println(obj);
    }
  }
}

Properties

import java.io.FileInputStream;
import java.util.Properties;

public class PropertiesTest {
  public static void main(String[] args) throws Exception {
    // 处理配置文件,key-value都是String类型
    Properties pros = new Properties();
    FileInputStream file = new FileInputStream("jdbc.properties");
    pros.load(file); // 加载流对应的文件
    String name = pros.getProperty("name");
    String password = pros.getProperty("password");

    System.out.println(name); // Tom
    System.out.println(password); // abc123
  }
}

对应的jdbc.properties就是一个配置文件,如下

name=Tom
password=abc123

Collections工具类

是一个操作Set、List和Map等集合工具类。常用方法如下:

  • reverse(List list):反转list中元素的顺序。

  • shuffle(List list):对list集合元素进行随机排序。

  • sort(List list):根据元素的自然顺序对list集合元素按升序排序

  • sort(List list, Comparator comparator):根据指定的comparator产生的顺序对list集合元素进行排序

  • swap(List list, int i, int j):将指定list集合中的i处元素和j处元素进行交换

  • Object max(Collection coll): 根据元素的自然顺序,返回给定集合中的最大元素。

  • Object max(Collection coll, Comparator comparator): 根据comparator指定的顺序自然顺序,返回给定集合中的最大元素。

  • Object min(Collection coll): 根据元素的自然顺序,返回给定集合中的最小元素。

  • Object min(Collection coll, Comparator comparator): 根据comparator指定的顺序自然顺序,返回给定集合中的最小元素。

  • int frequency(Collection coll, Object obj):返回指定集合中指定元素的出现次数

  • void copy(List dest, List src): 将src中的内容复制到dest中

  • boolean replaceAll(List list, Object oldVal, Object newVal): 使用新值替换Listzh中的所有对应的旧值。

部分方式代码演示如下:

import org.junit.Test;

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

public class CollectionsTest {

  @Test
  public void test1() {
    List list = new ArrayList();
    list.add(123);
    list.add(145);
    list.add(1);
    list.add(-23);
    list.add(-34);
    list.add(5);

    Collections.reverse(list);
    System.out.println(list); // [5, -34, -23, 1, 145, 123]

    Object[] objects = new Object[list.size()];
    List dest = Arrays.asList(objects); // 使用空对象数组站位,以免报异常
    Collections.copy(dest, list);
    System.out.println(dest); // [5, -34, -23, 1, 145, 123]

    Collections.replaceAll(list, 5, 3);
    System.out.println(list); // [3, -34, -23, 1, 145, 123]

    // Collections类中提供了多个 synchronizedXxx()方法
    // 如synchronizedList,synchronizedMap,synchronizedSet
    // 该方法可将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
    List list1 = Collections.synchronizedList(list);
    // 此时的list1就是线程安全的
  }
}