Java核心技术(二)

279 阅读22分钟

八、集合

1.概述

  • 集合、数组都是对多个数据进行存储操作的结构,简称java容器【此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储】。
  • 数组在内存存储方面的特点:
    • 数组初始化以后,长度就确定了。
    • 数组声明的类型,就决定了进行元素初始化时的类型。
  • 数组在内存存储方面的缺点:
    • 数组初始化以后,长度就不可变了,不便于扩展。
    • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数。
    • 数组存储的数据是有序的、可以重复的。存储数据的特点单一。
  • Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。

2.java集合框架

  • Java 集合可分为 Collection 和Map两种体系。
  • Collection接口:单列数据,定义了存取一组对象的方法的集合【存储一个一个的对象】。
    • List: 元素有序、可重复的集合。
      • ArrayList、LinkedList、Vector
    • Set: 元素无序、不可重复的集合。
      • HashSet、LinkedHashSet、TreeSet
    • ......
  • Map接口: 双列数据,保存具有映射关系“key-value对”的集合【存储一对一对的数据】。
    • HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

3.Collection

Collection接口

  • Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List和Queue集合。
  • JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现。
  • 在 Java5 之前,Java集合会丢失容器中所有对象的数据类型,把所有对象都当成Object类型处理;从JDK5.0增加了泛型以后, Java 集合可以记住容器中对象的数据类型。

Collection 接口方法

  • 1.添加
    • add(Object obj)
    • addAll(Collection coll)
  • 2.获取有效元素的个数
    • int size()
  • 3.清空集合
    • void clear()
  • 4.是否是空集合
    • boolean isEmpty()
package com.yao.practice2;

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

import org.junit.Test;

public class CollectionTest {

	@Test
	public void test1() {
		Collection coll = new ArrayList();

		// add(Object e):将元素e添加到集合coll中
		coll.add("a");
		coll.add(new String("b"));
		coll.add(1);// 自动装箱
		coll.add(new Date());
		System.out.println(coll);

		// size():获取添加的元素个数
		System.out.println(coll.size());

		// addAll():将一个集合中的元素加入到另一个集合中
		coll.addAll(coll);
		System.out.println(coll);

		// clear():清空集合元素
		coll.clear();

		// isEmpty():判断当前集合是否为空
		System.out.println(coll.isEmpty());
	}

}

程序运行结果:
[a, b, 1, Tue Dec 03 15:48:50 CST 2019]
4
[a, b, 1, Tue Dec 03 15:48:50 CST 2019, a, b, 1, Tue Dec 03 15:48:50 CST 2019]
true
  • 5.是否包含某个元素

    • boolean contains(Objectobj):是通过元素的equals方法来判断是否是同一个对象
    • boolean containsAll(Collectionc):也是调用元素的equals方法来比较的。 拿两个集合的元素挨个比较。
    package com.yao.practice2;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Date;
    
    import org.junit.Test;
    
    class User {
    	private String name;
    	private int age;
    
    	public User() {
    		super();
    	}
    
    	public User(String name, int age) {
    		super();
    		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 + "]";
    	}
    }
    
    public class CollectionTest {
    
    	@Test
    	public void test2() {
    		Collection coll2 = new ArrayList();
    		coll2.add("a");
    		coll2.add(123);
    		coll2.add(new String("b"));
    		coll2.add(true);
    		coll2.add(new User("haha", 18));
    
    		// contains():判断当前集合中是否包含obj
    		System.out.println(coll2.contains(123));// true
    		System.out.println(coll2.contains(new String("a")));// true,调用equals,String中重写了equals方法,比较内容
    		System.out.println(coll2.contains(new User("haha", 18)));// false,User类中没有重写equals,比较引用
    		
    		//containsAll(Collection coll2):判断coll2中的所有元素是否都存在于当前集合中
    		Collection coll = Arrays.asList(123,"a");//true
    		System.out.println(coll2.containsAll(coll));//true
    	}
    }
    
    package com.yao.practice2;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Date;
    
    import org.junit.Test;
    
    class User {
    	private String name;
    	private int age;
    
    	public User() {
    		super();
    	}
    
    	public User(String name, int age) {
    		super();
    		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 + "]";
    	}
    	
        //重写equals()
    	@Override
    	public boolean equals(Object obj) {
    		System.out.println("调用重写的equals方法");
    		if (this == obj)
    			return true;
    		if (obj == null)
    			return false;
    		if (getClass() != obj.getClass())
    			return false;
    		User other = (User) obj;
    		if (age != other.age)
    			return false;
    		if (name == null) {
    			if (other.name != null)
    				return false;
    		} else if (!name.equals(other.name))
    			return false;
    		return true;
    	}
    
    }
    
    public class CollectionTest {
    
    	@Test
    	public void test1() {
    		Collection coll = new ArrayList();
    
    		// add(Object e):将元素e添加到集合coll中
    		coll.add("a");
    		coll.add(new String("b"));
    		coll.add(1);// 自动装箱
    		coll.add(new Date());
    		System.out.println(coll);
    
    		// size():获取添加的元素个数
    		System.out.println(coll.size());
    
    		// addAll():将一个集合中的元素加入到另一个集合中
    		coll.addAll(coll);
    		System.out.println(coll);
    
    		// clear():清空集合元素
    		coll.clear();
    
    		// isEmpty():判断当前集合是否为空
    		System.out.println(coll.isEmpty());
    	}
    
    	@Test
    	public void test2() {
    		Collection coll2 = new ArrayList();
    
    		coll2.add("a");
    		coll2.add(123);
    		coll2.add(new String("b"));
    		coll2.add(true);
    		coll2.add(new User("haha", 18));
    
    		// contains():判断当前集合中是否包含obj
    		// 在判断时,会调用obj对象所在类的equals()。
    		System.out.println(coll2.contains(123));// true
    		System.out.println(coll2.contains(new String("a")));// true
    		System.out.println(coll2.contains(new User("haha", 18)));// true
    	}
    }
    
    运行结果:
    true
    true
    调用重写的equals方法
    调用重写的equals方法
    调用重写的equals方法
    调用重写的equals方法
    调用重写的equals方法
    true
    

@向Collection接口的实现类的对象中添加数据obj时,要去obj所在类重写equals()。

  • 6.删除

    • boolean remove(Objectobj):通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素。
    • boolean removeAll(Collectioncoll):取当前集合的差集。
    @Test
    public void test3() {
    	Collection coll3 = new ArrayList();
    	coll3.add("a");
    	coll3.add(123);
    	coll3.add(new String("b"));
    	coll3.add(true);
    	coll3.add(new User("haha", 18));
    	
    	//remove(Object obj):从当前集合中移除obj元素
    	coll3.remove(123);
    	coll3.remove(new User("haha", 18));
    	
    	System.out.println(coll3);
    	
    	//removeAll(Collection coll):从当前集合中移除coll中所有的元素
    	Collection coll = Arrays.asList(123,"a");
    	coll3.removeAll(coll);
    }
    
    • 程序test3中System.out.println(coll3);的部分实现源码;

      //【1】打印集合coll3元素
      System.out.println(coll3);
      
      //【2】调用println方法
      public void println(Object x) {
          String s = String.valueOf(x);
          synchronized (this) {
              print(s);
              newLine();
          }
      }
      //【3】调用静态方法valueOf
          public static String valueOf(Object obj) {
          return (obj == null) ? "null" : obj.toString();
      }
      
      //调用Object类方法toString,但是toString方法重写过了
          public String toString() {
          return getClass().getName() + "@" + Integer.toHexString(hashCode());
      }
      
      //【4】调用重写方法toString
      @Override
      public String toString() {
      	return "User [name=" + name + ", age=" + age + "]";
      }
      
  • 7.取两个集合的交集

    • boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c。
    	@Test
    	public void test4() {
    		Collection coll3 = new ArrayList();
    		coll3.add("a");
    		coll3.add(123);
    		coll3.add(new String("b"));
    		coll3.add(true);
    		coll3.add(new User("haha", 18));
    
    
    		//retainAll(Collection coll):交集:获取当前集合和coll集合的交集
    		Collection coll = Arrays.asList(123,"a");
    		coll3.retainAll(coll);
    		System.out.println(coll3);
    	}
    
  • 8.集合是否相等【与obj类型有关,List有序,Set无序】

    • boolean equals(Object obj)
  • 9.Arrays.asList的用法

    • 使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportOperationException异常。

    • asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

    • 用asList方法产生的List是固定大小的,这也就意味着任何改变其大小的操作都是不允许的。asList方法返回的确实是一个 ArrayList,但这个 ArrayList并不是java.util.ArrayList,而是 java.util.Arrays的一个内部类。这个内部类用一个final 数组来保存元素,因此用asList方法产生的ArrayList是不可修改大小的

      //部分源码
      public class Arrays {
          【1】
          public static <T> List<T> asList(T... a) {
              return new ArrayList<>(a);
          }
      
          【2】ArrayList<E>是一个内部类
          private static class ArrayList<E> extends AbstractList<E>
              implements RandomAccess, java.io.Serializable{
              
              private static final long serialVersionUID = -2764017481108945198L;
              private final E[] a;
      
              ArrayList(E[] array) {
                  a = Objects.requireNonNull(array);
              }
          }
          【3】requireNonNull
          public static <T> T requireNonNull(T obj) {
              if (obj == null)
              throw new NullPointerException();
              return obj;
          }
      }
      
    • 创建一个真正的 ArrayList:List<String> list =new ArrayList<>(Arrays.asList(s));

    • 对list或原数组操作都会影响另一个

      public void test5() {
          
          String[] s = new String[] {"a","b","c"};
          for(String temp:s) {
          	System.out.print(temp);
          }
          System.out.println();
          //数组--->集合:调用Arrays类的静态方法asList() 
          List<String> list = Arrays.asList(s);
          //list.add("d");//抛出UnsupportOperationException异常。
          System.out.println(list);
          
          s[0]="w";
          System.out.println(list);
          list.set(2, "d");
          for(String temp:s) {
          	System.out.print(temp);
          }
      } 
      
      运行结果:
      abc
      [a, b, c]
      [w, b, c]
      wbd
      
    • 不支持基本数据类型数据的数组

      public static <T> List<T> asList(T... a) {
              return new ArrayList<>(a);
          }
      
      • 可以看到:参数类型是 T ,根据官方文档的描述,T 是数组元素的 class。如果你对反射技术比较了解的话,那么 class 的含义想必是不言自明。我们知道任何类型的对象都有一个class属性,这个属性代表了这个类型本身。基本数据类型,比如 int,short,long等,是没有这个属性的,具有class属性的是它们所对应的包装类Integer,Short,Long。参考:Arrays.asList使用指南
      • 因此,这个错误产生的原因可解释为:asList方法的参数必须是对象或者对象数组,而基本数据类型不是对象——这也正是包装类出现的一个主要原因。当传入一个基本数据类型数组时,asList的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List 的唯一元素就是这个数组。
      • 解决方案:使用包装类数组
        @Test
        public void test5() {
        
        	//数组--->集合:调用Arrays类的静态方法asList() 
        	List<String> list = Arrays.asList(new String[] {"a","b","c"});
        	System.out.println(list);
        
        	List<int[]> arr1 = Arrays.asList(new int[] {123,456});
        	//List arr1 = Arrays.asList(new int[] {123,456});
        	System.out.println(arr1);
        	
        	List<Integer> arr2 = Arrays.asList(new Integer[] {123,456});
        	//List arr2 = Arrays.asList(new Integer[] {123,456});
        	System.out.println(arr2);
        	
        	List arr3 = Arrays.asList(123,456);
        	System.out.println(arr3);
        }
        
        运行结果:
        [a, b, c]
        [[I@4501b7af]
        [123, 456]
        [123, 456]
        
  • 10.获取集合对象的哈希值

    • hashCode()
  • 11.集合元素的遍历,使用Iterator接口

    • iterator(): 返回迭代器对象,用于集合遍历

Iterator迭代器接口

  • Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
  • GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。 迭代器模式,就是为容器而生。
  • Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
  • Iterator仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合。
  • 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
  • code
package com.yao.practice2;

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

import org.junit.Test;

public class ItetatorTest {

	@Test
	public void test() {
		Collection coll = new ArrayList();

		coll.add("a");
		coll.add(123);
		coll.add(new String("b"));
		coll.add(true);
		coll.add(new User("haha", 18));

		Iterator iterator = coll.iterator();

		// 方式一:不推荐
//		System.out.println(iterator.next());
//		System.out.println(iterator.next());
//		System.out.println(iterator.next());
//		System.out.println(iterator.next());
//		System.out.println(iterator.next());
//		// 报异常:NoSuchElementException
//		System.out.println(iterator.next());

		// 方式二:不推荐
//		for(int i=0;i<coll.size();i++) {
//			System.out.println(iterator.next());
//		}

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

		// foreach遍历
		// for(集合中元素的类型 局部变量:集合对象)
		// 内部仍然调用了迭代器
		//【注意】修改obj不会更改coll的值
		for (Object obj : coll) {
			System.out.println(obj);
		}
		System.out.println("***************************");

		// Iterator接口remove():
		iterator = coll.iterator();
		while (iterator.hasNext()) {
			Object obj = iterator.next();
			if ("b".equals(obj)) {
				iterator.remove();
			}
		}

		iterator = coll.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
	}
}


运行结果:
a
123
b
true
User [name=haha, age=18]
***************************
a
123
b
true
User [name=haha, age=18]
***************************
a
123
true
User [name=haha, age=18]

4.Collection子接口之一:List接口

List接口概述

  • public interface List<E> extends Collection<E>{...}
  • 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组【理解为“动态数组”】。
  • List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
  • JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。
    • ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储。
    • LinkedList:对于频繁的插入、删除操作,使用此类效率比Arraylist高;底层使用双向链表存储。
    • Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用0bject[] elementData存储。

List接口方法

  • List除了从Collection集合继承的方法外,List集合里添加了一些根据索引来操作集合元素的方法。

    • void add(int index,Objectele):在index位置插入ele元素
    • boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
    • Object get(int index):获取指定index位置的元素
    • int indexOf(Object obj):返回obj在集合中首次出现的位置
    • int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
    • Object remove(int index):移除指定index位置的元素,并返回此元素
    • Object set(int index, Object ele):设置指定index位置的元素为ele
    • List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
  • 区分List中remove(int index) 和remove(0bject obj)

    public class ListExer {
    
    	public void testListRemove() {
    		List list = new ArrayList();
    		list.add(1);|
    		list.add(2);
    		list.add(3);
    		updateList(list); 
    		System.out.print1n(list);
    	}
    
    	private void updateList(List list) {
    
    	//list.remove(2);
    	list.remove(new Integer(2));
    	}
    }
    

List实现类之一: ArrayList

  • ArrayList是List接口的典型实现类、主要实现类
  • 本质上,ArrayList是对象引用的一个”变长”数组
  • ArrayList源码
    • JDK7
    ArrayList list = new ArrayList();//底层创建了长度是10的object[]数组eLementData           
    List.add(123);//eLementData[0] = new Integer(123);                       
    ...                              
    List.add(11);//如果此次的添加导致底层eLementData数组容量不够,则扩容。           
    默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
    结论:建议开发中使用带参的构造器: ArrayList list = new ArrayList(int capacity)
    
    • JDK8
    ArrayList List = new ArrayList();//底层object[] elementData初始化为{}.并没有创建长度为10的数组          
    list. add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到eLementData[0]
    ...
    后续的添加和扩容操作与jdk7无异。
    
    • 小结 jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

List实现类之二: LinkedList

  • 对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
  • 新增方法:
    • void addFirst(Object obj)
    • void addLast(Object obj)
    • Object getFirst()
    • Object getLast()
    • Object removeFirst()
    • Object removeLast()
  • 源码分析
    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.next = next;
        this.prev = prev;
        }
    }
    

List 实现类之三: Vector

Vector的源码分析: jdk7和jdk8 中通过Vector()构造器创建对象时,底层都创建了长度为10的数。在扩容方面,默认扩容为原来的数组长度的2倍。

5.Collection子接口之二:Set接口

Set 接口概述

  • Set接口是Collection的子接口:public interface Set<E> extends Collection<E>{...},Set接口没有提供额外的方法。
  • Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败。
  • Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法。
  • Set接口:存储无序的、不可重复的数据。-->数学中的“集合”。
    • HashSet:作为Set接口的主要实现类;线程不安全的;可以存储nulL值。
    • LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历。
    • TreeSet:可以按照添加对象的指定属性,进行排序。
  • Set的无序性与不可重复性的理解(HashSet为例)

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

Set实现类之一: HashSet

  • HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。
  • HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
  • HashSet具有以下特点:
    • 不能保证元素的排列顺序
    • HashSet不是线程安全的
    • 集合元素可以是null
  • HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
  • 对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码” 。
  • 向HashSet中添加元素的过程:【HashSet底层:数组+链表】
    • 当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值,通过某种散列函数决定该对象在HashSet底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)。
    • 如果此位置上没有其它元素,则添加成功。
    • 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。
    • JDK7中:新添加元素放到数组中,指向原来的元素
    • JDK8中:原来的元素指向新添加的元素【七上八下】
  • 为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?
    • 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
    • 并且31只占用5bits,相乘造成数据溢出的概率较小。
    • 31可以由i*31==(i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)
    • 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)
  • 重写hashCode()方法的基本原则
    • 在程序运行时,同一个对象多次调用hashCode()方法应该返回相同的值。
    • 当两个对象的equals()方法比较返回true时,这两个对象的hashCode()方法的返回值也应相等。
    • 对象中用作equals()方法比较的Field,都应该用来计算hashCode值。
  • 重写equals()方法的基本原则
    • 重写equals方法的时候一般都需要同时重写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。

Set实现类之二:LinkedHashSet

  • LinkedHashSet是HashSet的子类:public class LinkedHashSet<E> extends HashSet<E> implements Set<E>
  • LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
  • LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。
  • LinkedHashSet不允许集合元素重复。

Set实现类之三:TreeSet

  • TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
  • 向TreeSet中添加的数据,要求是相同类的对象
  • TreeSet底层使用红黑树结构存储数据。
  • TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。
  • 自然排序(实现Comparable接口)
    • 对于TreeSet集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj)方法比较返回值。
    • TreeSet的自然排序要求元素所属的类实现Comparable接口。
    • TreeSet会调用集合元素的compareTo(Object odj)方法方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
    • 向TreeSet中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。
    • 因为只有相同类的两个实例才会比较大小,所以向 TreeSet中添加的应该是同一个类的对象。
    • 当需要把一个对象放入TreeSet中,重写该对象对应的 equals()方法时,应保证该方法与compareTo(Object obj)方法有一致的结果:如果两个对象通过equals() 方法比较返回true,则通过compareTo(Object obj) 方法比较应返回 0。否则,让人难以理解。
  • 定制排序
    • 定制排序中,比较两个对象是否相同的标准为: compare()返回0。不再是equals()。
    • TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。
    • 要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
    • 此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。

4.Map接口

Map接口概述

  • Map与Collection并列存在。用于保存具有映射关系的数据:key-value。
  • Map中的key和value都可以是任何引用类型的数据。
  • Map中的key用Set来存放,不允许重复,即同一个Map对象所对应的类,须重写hashCode()和equals()方法。
  • key和value之间存在单向一对一关系,即通过指定的key总能找到唯一的、确定的value。
  • Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是Map接口使用频率最高的实现类。

Map实现类之一:HashMap

  • HashMap是Map接口使用频率最高的实现类。
  • 允许使用null键和null值,与HashSet一样,不保证映射的顺序。
  • 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()。
  • 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()。
  • 一个key-value构成一个entry。
  • 所有的entry构成的集合是Set:无序的、不可重复的。
  • HashMap判断两个key相等的标准是:两个key通过equals()方法返回true,hashCode值也相等。
  • HashMap判断两个value相等的标准是:两个value通过equals()方法返回true。

Map实现类之二: LinkedHashMap

  • LinkedHashMap是HashMap的子类:public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
  • 在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序。
  • 与LinkedHashSet类似,LinkedHashMap可以维护Map的迭代顺序:迭代顺序与Key-Value对的插入顺序一致。

Map实现类之三:TreeMap

  • TreeMap存储Key-Value对时,需要根据key-value对进行排序。TreeMap可以保证所有的Key-Value对处于有序状态。
  • TreeSet底层使用红黑树结构存储数据。
  • TreeMap的Key的排序:
    • 自然排序:TreeMap的所有的Key必须实现Comparable接口,而且所有的Key应该是同一个类的对象,否则将会抛出 ClasssCastException。
    • 定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。此时不需要Map的Key实现Comparable接口。
  • TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。

Map实现类之四: Hashtable

  • Hashtable是个古老的Map实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
  • Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
  • 与HashMap不同,Hashtable不允许使用null作为key和value。
  • 与HashMap一样,Hashtable也不能保证其中Key-Value对的顺序。
  • Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。

Map实现类之五: Properties

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

Collections工具类

概述

  • Collections是一个操作Set、List和Map等集合的工具类。
  • Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
  • 排序操作:(均为static方法)
    • reverse(List):反转List中元素的顺序
    • shuffle(List):对List集合元素进行随机排序
    • sort(List):根据元素的自然顺序对指定List集合元素按升序排序
    • sort(List,Comparator):根据指定的Comparator 产生的顺序对List集合元素进行排序
    • swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换

Collections常用方法

  • Object max(Collection): 根据元素的自然顺序,返回给定集合中的最大元素
  • Object max(Collection, Comparator): 根据 Comparator 指定的顺序,返回 给定集合中的最大元素
  • Object min(Collection)
  • Object min(Collection,Comparator)
  • int frequency(Collection,Object): 返回指定集合中指定元素的出现次数
  • void copy(Listdest,Listsrc):将src中的内容复制到dest中
  • boolean replaceAll(List list,Object oldVal, Object newVal):使用新值替换List 对象的所有旧值

Collections常用方法:同步控制

  • Collections类中提供了多个synchronizedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。