面试的时候经常会被问道这样一个问题:Java集合中哪些是有序的,哪些不是?

我们通常的回答是List、LinkedHashMap、LinkedHashSet、TreeMap、TreeSet是有序的,ListLinkedHashMapLinkedHashSetLinkedHashSet在遍历时会保持添加的顺序TreeMapTreeSet在遍历时会以自然顺序(Comparable接口的compareTo)输出。下面分别测试了一下

准备工作

新建一个User类作为Map的键,还有四个填充Map、Set的方法

/**
 * 实现了Comparable接口,重写了compareTo
 */
class User implements Comparable<User> {
	private String name;

// 省略getter、setter、构造方法

	@Override
	public int compareTo(User o) {
		return this.getName().compareTo(o.getName());
	}
}

	public static void fillMapByString(Map<String, String> map) {
		map.put("33", "33");
		map.put("aa", "aa");
		map.put("gg", "gg");
		map.put("dd", "dd");
		map.put("11", "11");
		map.put("ee", "ee");
		map.put("aa", "aaaa");
	}
	
	public static void fillMapByUser(Map<User, String> map) {
		map.put(new User("33"), "33");
		map.put(new User("aa"), "aa");
		map.put(new User("gg"), "gg");
		map.put(new User("dd"), "dd");
		map.put(new User("11"), "11");
		map.put(new User("ee"), "ee");
		map.put(new User("aa"), "aaaa");
	}
	
	public static void fillSetByString(Set<String> set) {
		set.add("33");
		set.add("aa");
		set.add("gg");
		set.add("dd");
		set.add("11");
		set.add("ee");
		set.add("aa");
	}
	
	public static void fillSetByUser(Set<User> set) {
		set.add(new User("33"));
		set.add(new User("aa"));
		set.add(new User("gg"));
		set.add(new User("dd"));
		set.add(new User("11"));
		set.add(new User("ee"));
		set.add(new User("aa"));
	}

TreeMap

testTreeMap1的结果可以看出TreeMap在遍历时会以自然顺序输出。这个自然顺序指的是键的自然顺序,是由String类的compareTo方法决定的。

testTreeMap2的输出顺序与testTreeMap1的输出顺序一致,是因为User类实现了Comparable接口重写了compareTo,而compareTo中直接以String类型的name属性排序

这里还有另一个问题,testTreeMap1、testTreeMap2中的Map都填充了7个元素,但两个方法的输出都是6个,并且都是后面的 aa的值覆盖了前面的 aa的值。

我们知道HashMap判断两个键是否相同是依赖于键的hashCodeequals方法,但是我没有重写User的hashCode、equals方法,所以说两个new User("aa")是不同的对象,不应该被覆盖。那么现在真相就只剩一个了,TreeMap判断键是否相同不是依赖于键的hashCode、equals方法,而是键的compareTo方法,这个猜想也在TreeMap的put方法源码中得到了证实,有兴趣的同学可以去瞅一瞅。

public static void testTreeMap1() {
		TreeMap<String, String> map = new TreeMap<String, String>();
		fillMapByString(map);
		
		System.out.println("===============testTreeMap1===================");
		System.out.println(map.size() + "个元素:");
		for (Entry<String, String> entry : map.entrySet()) {
			System.out.println(entry.getKey() + " " + entry.getValue());
		}
	}
	
	public static void testTreeMap2() {
		TreeMap<User, String> map = new TreeMap<User, String>();
		fillMapByUser(map);
		
		System.out.println("===============testTreeMap2===================");
		System.out.println(map.size() + "个元素:");
		for (Entry<User, String> entry : map.entrySet()) {
			System.out.println(entry.getKey() + " " + entry.getValue());
		}
	}

Java containAll 集合包含 顺序 java集合有序_User


图片失效请点击

TreeSet

TreeSet的输出顺序、元素格式和TreeMap是一样的,因为TreeSet底层就是由TreeMap实现的。

Java containAll 集合包含 顺序 java集合有序_有序集合_02

public static void testTreeSet1() {
		TreeSet<String> set = new TreeSet<String>();
		fillSetByString(set);
		
		System.out.println("===============testTreeSet1===================");
		System.out.println(set.size() + "个元素:");
		for (String s : set) {
			System.out.println(s);
		}
	}
	
	public static void testTreeSet2() {
		TreeSet<User> set = new TreeSet<User>();
		fillSetByUser(set);
		
		System.out.println("===============testTreeSet2===================");
		System.out.println(set.size() + "个元素:");
		for (User user : set) {
			System.out.println(user);
		}
	}

Java containAll 集合包含 顺序 java集合有序_User_03


图片失效请点击t

LinkedHashMap

由结果可以看出LinkedHashMap可以保持元素的添加顺序。

从元素个数来看,testLinkedHashMap1保留了6个元素,是因为两个键"aa".equals("aa") == true,所以后面的覆盖了前面的;testLinkedHashMap2保留了7个元素,是因为new User("aa").equals(new User("aa")) == false,没有产生覆盖。

public static void testLinkedHashMap1() {
		LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
		fillMapByString(map);
		
		System.out.println("===============testLinkedHashMap1===================");
		System.out.println(map.size() + "个元素:");
		for (Entry<String, String> entry : map.entrySet()) {
			System.out.println(entry.getKey() + " " + entry.getValue());
		}
	}
	
	public static void testLinkedHashMap2() {
		LinkedHashMap<User, String> map = new LinkedHashMap<User, String>();
		fillMapByUser(map);
		
		System.out.println("===============testLinkedHashMap2===================");
		System.out.println(map.size() + "个元素:");
		for (Entry<User, String> entry : map.entrySet()) {
			System.out.println(entry.getKey() + " " + entry.getValue());
		}
	}

Java containAll 集合包含 顺序 java集合有序_User_04


图片失效请点击

LinkedHashSet

从下图可以看出LinkedHashSet可保留的元素的插入顺序,并且元素个数和LinkedHashMap的结果一样,这是因为LinkedHashSet内部是以LinkedHashMap实现的。

public static void testLinkedHashSet1() {
		LinkedHashSet<String> set = new LinkedHashSet<String>();
		fillSetByString(set);
		
		System.out.println("===============testLinkedHashSet1===================");
		System.out.println(set.size() + "个元素:");
		for (String s : set) {
			System.out.println(s);
		}
	}
	
	public static void testLinkedHashSet2() {
		LinkedHashSet<User> set = new LinkedHashSet<User>();
		fillSetByUser(set);
		
		System.out.println("===============testLinkedHashSet2===================");
		System.out.println(set.size() + "个元素:");
		for (User user : set) {
			System.out.println(user);
		}
	}

Java containAll 集合包含 顺序 java集合有序_List_05


图片失效请点击

完整代码

https://gitee.com/zhaobingshuang/codes/mpcjd01zfnxv42kasw98g99