持有对象(4):Set、Map

一、Set

    Set不保存重复的元素(至于如何判断元素相同则较为复杂,稍后便会看到)。如果你试图将相同对象的多个实例添加到Set中,那么它就会阻止这种重复现象。Set中最常被使用的是测试归属性,你可以很容易地询问某个对象是否在某个Set中。正因如此,查找就成为了Set中最重要的操作,因为你通常都会选择一个HashSet的实现,它专门对快速查找进行了优化。

    Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set是基于对象的值来确定归属性的,而更加复杂的问题我们在以后再讨论。

    下面是Set中提供的方法:

public interface Set<E> extends Collection<E> {
	// 查询操作

	// 获取Set大小
	int size();

	// 判断Set是否为空
	boolean isEmpty();

	// 比较Set中是否包含o
	boolean contains(Object o);

	// 返回Set的迭代器
	Iterator<E> iterator();

	// 把Set转成Object数组
	Object[] toArray();

	// 把Set转成指定类型的数组
	<T> T[] toArray(T[] a);

	// 修改操作

	// 添加元素
	boolean add(E e);

	// 移除元素
	boolean remove(Object o);

	// 批量操作

	// 比较Set中是否包含集合c
	boolean containsAll(Collection<?> c);

	// 把集合c添加到Set中
	boolean addAll(Collection<? extends E> c);

	// 保留与集合c相同的元素
	boolean retainAll(Collection<?> c);

	// 移除与集合c相同的元素
	boolean removeAll(Collection<?> c);

	// 清空Set
	void clear();

	// 比较与散列

	// 比较Set与o是否相同
	boolean equals(Object o);

	// 返回散列码
	int hashCode();

	@Override
	default Spliterator<E> spliterator() {
		return Spliterators.spliterator(this, Spliterator.DISTINCT);
	}
}

    下面是使用存放Integer对象的HashSet的示例:

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

public class SetOfInteger {
	public static void main(String[] args) {
		Random r = new Random();
		Set<Integer> intset = new HashSet<>();
		for (int i = 0; i < 10000; i++)
			intset.add(r.nextInt(30));
		System.out.println(intset);
	}
}

    在0到29之间的10000个随机数被添加到了Set中,因此你可以想象,每一个数都重复了许多次。但是你可以看到,每一个数字只有一个实例出现在结果中。

    你还可以注意到,输出的顺序没有任何规律可循,这是因为处于速度原因的考虑,HashSet使用了散列。HashSet所维护的顺序与TreeSet或LinkedHashSet都不同,因为它们的实现具有不同的元素存储方式。TreeSet将元素存储在红黑树数据结构中,而HashSet使用的是散列函数。LinkedHashSet因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。

    如果你想对结果排序,一种方式是使用了TreeSet来代替HashSet:

import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;

public class SortedSetOfInteger {
	public static void main(String[] args) {
		Random r = new Random();
		SortedSet<Integer> intset = new TreeSet<>();
		for (int i = 0; i < 10000; i++)
			intset.add(r.nextInt(30));
		System.out.println(intset);
	}
}

    你将会执行的最常见的操作之一,就是使用contains()测试Set的归属性,但是还有很多操作会让你想起在上学时所学的集合图(用圆表示集与集之间关系的图):

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

public class SetOperations {
	public static void main(String[] args) {
		Set<String> set1 = new HashSet<>();
		Collections.addAll(set1, "A B C D E F G H I J K L".split(" "));
		set1.add("M");
		System.out.println("H: " + set1.contains("H"));
		System.out.println("N: " + set1.contains("N"));
		Set<String> set2 = new HashSet<>();
		Collections.addAll(set2, "H I J K L".split(" "));
		System.out.println("set2 in set1: " + set1.containsAll(set2));
		set1.remove("H");
		System.out.println("set1: " + set1);
		System.out.println("set2 in set1: " + set1.containsAll(set2));
		set1.removeAll(set2);
		System.out.println("set2 removed from set1: " + set1);
		Collections.addAll(set1, "X Y Z".split(" "));
		System.out.println("'X Y Z' added to set1: " + set1);
	}
}

    能够产生每个元素都唯一的列表是相当有用的功能。例如,在你想要列出在上面的SetOperations.java文件中所有的单词的时候。

    可以使用Set构造器来把List转为Set并去除List中重复的元素:

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

public class ListToSetTest {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("qwer");
		list.add("QWER");
		list.add("adsf");
		list.add("adsf");
		list.add("zxcv");
		list.add("ZXCV");
		list.add("1234");
		list.add("1234");
		list.add("4567");
		Set<String> set = new TreeSet<>(list);
		System.out.println(set);
	}
}

    由于它是TreeSet,因此结果是排序的。在本例中,排序是按字典序进行的,因此大写和小写字母被划分到了不同的组中。如果你想按照字母的顺序排序,那么可以向TreeSet的构造器传入String.CASE_INSENSITIVE_ORDER比较器(比较器就是建立排序顺序的对象):

port java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

public class ListToSetTest {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("A");
		list.add("a");
		list.add("b");
		list.add("b");
		list.add("c");
		list.add("C");
		list.add("d");
		list.add("d");
		list.add("E");
		Set<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
		set.addAll(list);
		System.out.println(set);
	}
}

    String.CASE_INSENSITIVE_ORDER比较器比较器会忽略大小写。当Set中有大写或小写时,只保留先出现的字母。

二、Map

    将对象映射到其他对象的能力是一种解决编程问题的杀手锏。Map接口提供的方法如下:

public interface Map<K, V> {
	// 查询操作

	// 查询键值对数量
	int size();

	// 查询Map是否为空
	boolean isEmpty();

	// 查询Map中是否包含某个key
	boolean containsKey(Object key);

	// 查询Map中是否包含某个value
	boolean containsValue(Object value);

	// 根据key查询对应的value值
	V get(Object key);

	// 修改操作

	// 向map中添加键值对
	V put(K key, V value);

	// 根据key移除对应的key和value
	V remove(Object key);

	// 批量操作

	// 把其他Map集合添加到此Map中
	void putAll(Map<? extends K, ? extends V> m);

	// 清空Map
	void clear();

	// 视图

	// 把Map中的Key值以Set集合的形式返回
	Set<K> keySet();

	// 把Map的的value以Collection的形式返回
	Collection<V> values();

	// 把Map中的Entry对象以Set的形式返回
	Set<Map.Entry<K, V>> entrySet();

	/* Entry对象 */
	interface Entry<K, V> {

		K getKey();

		V getValue();

		V setValue(V value);

		boolean equals(Object o);

		int hashCode();

		public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey() {
			return (Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> c1.getKey().compareTo(c2.getKey());
		}

		public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K, V>> comparingByValue() {
			return (Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> c1.getValue().compareTo(c2.getValue());
		}

		public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
			Objects.requireNonNull(cmp);
			return (Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
		}

		public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
			Objects.requireNonNull(cmp);
			return (Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
		}
	}

	/* Entry对象 */

	// 比较与散列

	// 比较Map是否与o相同
	boolean equals(Object o);

	// 返回Map散列码
	int hashCode();

	// 接口提供的默认方法

	// 根据key获取value,如果key不存在,则返回默认值
	default V getOrDefault(Object key, V defaultValue) {
		V v;
		return (((v = get(key)) != null) || containsKey(key)) ? v : defaultValue;
	}

	// 遍历Map
	default void forEach(BiConsumer<? super K, ? super V> action) {
		Objects.requireNonNull(action);
		for (Map.Entry<K, V> entry : entrySet()) {
			K k;
			V v;
			try {
				k = entry.getKey();
				v = entry.getValue();
			} catch (IllegalStateException ise) {
				// this usually means the entry is no longer in the map.
				throw new ConcurrentModificationException(ise);
			}
			action.accept(k, v);
		}
	}

	// 自定义key删选条件替换该key对应的value值
	default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
		Objects.requireNonNull(function);
		for (Map.Entry<K, V> entry : entrySet()) {
			K k;
			V v;
			try {
				k = entry.getKey();
				v = entry.getValue();
			} catch (IllegalStateException ise) {
				// this usually means the entry is no longer in the map.
				throw new ConcurrentModificationException(ise);
			}

			// ise thrown from function is not a cme.
			v = function.apply(k, v);

			try {
				entry.setValue(v);
			} catch (IllegalStateException ise) {
				// this usually means the entry is no longer in the map.
				throw new ConcurrentModificationException(ise);
			}
		}
	}

	// 如果指定的key,value没有关联,则关联
	default V putIfAbsent(K key, V value) {
		V v = get(key);
		if (v == null) {
			v = put(key, value);
		}

		return v;
	}

	// 当指定的key与value对应时,才删除该key,value
	default boolean remove(Object key, Object value) {
		Object curValue = get(key);
		if (!Objects.equals(curValue, value) || (curValue == null && !containsKey(key))) {
			return false;
		}
		remove(key);
		return true;
	}

	// 把key对应的value,替换成一个新的value
	default boolean replace(K key, V oldValue, V newValue) {
		Object curValue = get(key);
		if (!Objects.equals(curValue, oldValue) || (curValue == null && !containsKey(key))) {
			return false;
		}
		put(key, newValue);
		return true;
	}

	// 替换
	default V replace(K key, V value) {
		V curValue;
		if (((curValue = get(key)) != null) || containsKey(key)) {
			curValue = put(key, value);
		}
		return curValue;
	}

	// 如果指定的key不存在,则使用函数指定一个值
	default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
		Objects.requireNonNull(mappingFunction);
		V v;
		if ((v = get(key)) == null) {
			V newValue;
			if ((newValue = mappingFunction.apply(key)) != null) {
				put(key, newValue);
				return newValue;
			}
		}

		return v;
	}

	// 如果指定的键存在,则使用函数指定的key给予新的value
	default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
		Objects.requireNonNull(remappingFunction);
		V oldValue;
		if ((oldValue = get(key)) != null) {
			V newValue = remappingFunction.apply(key, oldValue);
			if (newValue != null) {
				put(key, newValue);
				return newValue;
			} else {
				remove(key);
				return null;
			}
		} else {
			return null;
		}
	}

	// 如果key存在,则使用函数指定一个新值,如果不存在则添加新的key,value
	default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
		Objects.requireNonNull(remappingFunction);
		V oldValue = get(key);

		V newValue = remappingFunction.apply(key, oldValue);
		if (newValue == null) {
			// delete mapping
			if (oldValue != null || containsKey(key)) {
				// something to remove
				remove(key);
				return null;
			} else {
				// nothing to do. Leave things as they were.
				return null;
			}
		} else {
			// add or replace old mapping
			put(key, newValue);
			return newValue;
		}
	}

	//如果指定的键尚未与值关联或与null关联,则将其与给定的非空值关联。
	default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
		Objects.requireNonNull(remappingFunction);
		Objects.requireNonNull(value);
		V oldValue = get(key);
		V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
		if (newValue == null) {
			remove(key);
		} else {
			put(key, newValue);
		}
		return newValue;
	}
}

    考虑一个程序,它将用来检查java的Random类的随机性。理想状态下,Random可以产生理想的数字分布,但要想测试它,则需要生成大量的随机数,并对落入各种不同范围的数字进行计数。Map可以很容易地解决该问题。在本例中,键是由Random产生的数字,而值是该数字出现的次数:

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

/**
 * 统计
 */
public class Statistics {
	public static void main(String[] args) {
		Random r = new Random();
		Map<Integer, Integer> map = new HashMap<>();
		for (int i = 0; i < 10000; i++) {
			int s = r.nextInt(20);
			Integer integer = map.get(s);
			map.put(s, integer == null ? 1 : integer + 1);
		}
		System.out.println(map);
	}
}

    在main()中,自动包装机制将随机生成的int转换为HashMap可以使用的Integer引用(不能使用基本类型的容器)。如果键不在容器中,get()方法将返回null(这表示该数字第一次被找到)。否则,get()方法将产生与该键相关联的Integer值,然后这个值被递增(自动包装机制再次简化了表达式,但是确实发生了对Integer的包装和拆包)。

 如果本文对您有很大的帮助,还请点赞关注一下。

发布了100 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_40298351/article/details/104388095
今日推荐