Java基础知识-第19章--Java集合常用工具类介绍

186 阅读9分钟

1、概述

引言:Java集合工具类分为两种,一种是 java 自带的(无需下jar包,jdk自带),一种是其他公司提供的(如Apache)。在公司开发的过程中较为常用的是Apache提供的工具类,学习了解这些工具类是十分有必要的。

2、Collections工具类

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

2.1、排序操作(均为static方法)

Collections 类提供了多种排序方法,可以对集合进行反转、洗牌、自然排序和自定义排序等操作。

  • reverse(List list):反转集合中元素的顺序。
  • shuffle(List list):随机打乱集合中元素的顺序。
  • sort(List list):对集合进行自然升序排序。
  • sort(List list, Comparator c):根据自定义比较器对集合进行排序。
  • swap(List list, int i, int j):交换集合中指定位置的两个元素。

示例代码

List<String> list = new ArrayList<>();
list.add("AAA");
list.add("BBB");
list.add("CCC");
list.add("DDD");
list.add("EEE");

System.out.println("原始顺序:" + list);

// 反转
Collections.reverse(list);
System.out.println("反转后:" + list);

// 洗牌
Collections.shuffle(list);
System.out.println("洗牌后:" + list);

// 自然升序
Collections.sort(list);
System.out.println("自然升序后:" + list);

// 交换
Collections.swap(list, 2, 4);
System.out.println("交换后:" + list);

输出结果

原始顺序:[AAA, BBB, CCC, DDD, EEE]
反转后:[EEE, DDD, CCC, BBB, AAA]
洗牌后:[DDD, AAA, EEE, BBB, CCC]
自然升序后:[BBB, AAA, DDD, EEE, CCC]
交换后:[BBB, AAA, CCC, EEE, DDD]

2.2、查找、替换

Collections 类提供了多种查找方法,可以用于查找集合中的最大值、最小值、元素出现的次数等。

  • binarySearch(List list, Object key):二分查找法,前提是集合已经排序。
  • max(Collection coll):返回集合中的最大元素。
  • max(Collection coll, Comparator comp):根据自定义比较器返回集合中的最大元素。
  • min(Collection coll):返回集合中的最小元素。
  • min(Collection coll, Comparator comp):根据自定义比较器返回集合中的最小元素。
  • fill(List list, Object obj):使用指定对象填充集合。
  • frequency(Collection c, Object o):返回指定对象在集合中出现的次数。
  • void copy(List dest,List src):将src中的内容复制到dest中,注意src的长度必须小于dest的长度,否则报错
  • boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值

示例

System.out.println("最大元素:" + Collections.max(list));
System.out.println("最小元素:" + Collections.min(list));
System.out.println("出现的次数:" + Collections.frequency(list, "AAA"));

// 没有排序直接调用二分查找,结果是不确定的
System.out.println("排序前的二分查找结果:" + Collections.binarySearch(list, "AAA"));
Collections.sort(list);
// 排序后,查找结果和预期一致
System.out.println("排序后的二分查找结果:" + Collections.binarySearch(list, "AAA"));

Collections.fill(list, "GGG");
System.out.println("填充后的结果:" + list);

结果

最大元素:CCC
最小元素:BBB
出现的次数:1
排序前的二分查找结果:0
排序后的二分查找结果:1
填充后的结果:[GGG, GGG, GGG, GGG, GGG]

2.3、集合线程不安全--同步控制【废弃】

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

image.png

//返回的list即为线程安全的list 
List list = new ArrayList();  
List list = Collections.synchronizedList(list)

注意:

SynchronizedList 的源码实现非常简单,只是在方法内部使用 synchronized 关键字加了一层锁。

static class SynchronizedList<E>
    extends SynchronizedCollection<E>
    implements List<E> {
    private static final long serialVersionUID = -7754090372962971524L;

    final List<E> list;

    SynchronizedList(List<E> list) {
        super(list); // 调用父类 SynchronizedCollection 的构造方法,传入 list
        this.list = list; // 初始化成员变量 list
    }

    // 获取指定索引处的元素
    public E get(int index) {
        synchronized (mutex) {return list.get(index);} // 加锁,调用 list 的 get 方法获取元素
    }
    
    // 在指定索引处插入指定元素
    public void add(int index, E element) {
        synchronized (mutex) {list.add(index, element);} // 加锁,调用 list 的 add 方法插入元素
    }
    
    // 移除指定索引处的元素
    public E remove(int index) {
        synchronized (mutex) {return list.remove(index);} // 加锁,调用 list 的 remove 方法移除元素
    }
}

那这样的话,其实效率和那些直接在方法上加 synchronized 关键字的 VectorHashtable 差不多(JDK 1.0 时期就有了),而这些集合类基本上已经废弃了,几乎不怎么用。

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

    // 获取指定索引处的元素
    public synchronized E get(int index) {
        if (index >= elementCount) // 如果索引超出了列表的大小,则抛出数组下标越界异常
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index); // 返回指定索引处的元素
    }

    // 移除指定索引处的元素
    public synchronized E remove(int index) {
        modCount++; // 修改计数器,标识列表已被修改
        if (index >= elementCount) // 如果索引超出了列表的大小,则抛出数组下标越界异常
            throw new ArrayIndexOutOfBoundsException(index);
        E oldValue = elementData(index); // 获取指定索引处的元素

        int numMoved = elementCount - index - 1; // 计算需要移动的元素个数
        if (numMoved > 0) // 如果需要移动元素
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved); // 将数组中的元素向左移动一位
        elementData[--elementCount] = null; // 将最后一个元素设置为 null,等待垃圾回收

        return oldValue; // 返回被移除的元素
    }
}

正确的做法是使用并发包下的 CopyOnWriteArrayListConcurrentHashMap

2.4、不可变集合

Collections 类提供了多种方法来创建不可变集合,这些集合一旦创建,就无法进行修改操作。

  • emptyXxx():创建一个空的不可变集合。
  • singletonXxx():创建一个只包含一个元素的不可变集合。
  • unmodifiableXxx():为指定集合创建一个不可变视图。

示例代码:

List emptyList = Collections.emptyList();
emptyList.add("非空");  // 抛出 UnsupportedOperationException
System.out.println(emptyList);

输出结果:

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)
	at com.itwanger.s64.Demo.main(Demo.java:61)

2.5、singletonList()方法

首先,singletonList() 方法返回的列表只有一个元素。与Arrays.asList() 方法不同,singletonList() 方法返回一个不可变的列表。若集合只有一个元素,建议使用该方法。

换句话说,不允许在singletonList() 方法返回的列表上进行结构性和非结构性更改。测试可以快速说明这一点:

List<String> singletonList = Collections.singletonList("ONE");
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(
    () -> singletonList.add("TWO")
);
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(
    () -> singletonList.set(0, "A brand new string")
);

如果我们运行测试,它就会通过。因此,无论我们是向列表中添加元素还是更改列表中的元素,它都会抛出UnsupportedOperationException。

值得一提的是,如果我们看一下返回的列表类的源代码,与其他List实现不同,返回列表中的单个元素不存储在数组或任何其他复杂的数据结构中。相反,列表直接保存元素对象:

private static class SingletonList<E> extends AbstractList<E> implements RandomAccess, Serializable {
    ...
    private final E element;

    SingletonList(E obj) {element = obj;}
    ...
}

因此,它将占用更少的内存

2.6、unmodifiableMap()方法

相关方法:

  • unmodifiableMap()方法的功能:返回一个不可变的Map类型视图
  • unmodifiableSortedMap()方法的语法:public static SortedMap unmodifiableMap(Map m);

注意事项:

  • unmodifiableMap()方法在java.util包中可用
  • unmodifiableMap()方法用于获取给定Set(集合)的不可修改视图
  • unmodifiableMap()方法是一个静态方法,可以使用类名进行访问,如果尝试使用类对象访问该方法,那么也不会出现任何错误。
  • 返回给定集合的不可修改视图时,unmodifiableMap()方法不会引发异常

示例

import java.util.Collections;
import java.util.Map; 
import java.util.TreeMap;
public class Test {
	public static void main(String[] args) {
		Map<Integer, String> tm = new TreeMap<Integer, String>();

		// 通过使用put()方法是添加
		// 树状图中的对象
		tm.put(11, "java");
		tm.put(88, "CSharp");
		tm.put(99, "Python");
		tm.put(120, "C");
		tm.put(130, "ErLang");

		// 显示树图
		System.out.println("TreeMap: " + tm);

		// 通过使用unmodifiableSortedMap()方法是
		Map um = Collections.unmodifiableMap(tm);

		um.put(880, "GO");//此处会产生异常
	}
}

2.7、其他操作

Collections 类还提供了一些其他常用的方法:

  • addAll(Collection<? super T> c, T... elements):将多个元素添加到集合中。
  • disjoint(Collection<?> c1, Collection<?> c2):判断两个集合是否没有交集。

示例代码:

List<String> allList = new ArrayList<>();
Collections.addAll(allList, "TTT", "KKK", "AAA");
System.out.println("addAll 后:" + allList);

System.out.println("是否没有交集:" + (Collections.disjoint(list, allList) ? "是" : "否"));

输出结果:

addAll 后:[TTT, KKK, AAA]
是否没有交集:否

3、Spring 和 Apache 的集合工具类

3.1、CollectionUtils工具类

除了 JDK 提供的 Collections 工具类,Spring 和 Apache 也提供了各自的集合工具类,如 org.springframework.util.CollectionUtilsorg.apache.commons.collections.CollectionUtils

Apache 的 CollectionUtils 提供了更多实用的方法,如集合判空、集合操作(交集、并集、差集等)。

  • 引入依赖
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

3.1.1、对两个集合进行交、并、补、差集操作

List<Integer> list = new ArrayList<>();
list.add(2);
list.add(1);
list.add(3);

List<Integer> list2 = new ArrayList<>();
list2.add(2);
list2.add(4);

// 获取并集
Collection<Integer> unionList = CollectionUtils.union(list, list2);
System.out.println(unionList);

// 获取交集
Collection<Integer> intersectionList = CollectionUtils.intersection(list, list2);
System.out.println(intersectionList);

// 获取交集的补集
Collection<Integer> disjunctionList = CollectionUtils.disjunction(list, list2);
System.out.println(disjunctionList);

// 获取差集
Collection<Integer> subtractList = CollectionUtils.subtract(list, list2);
System.out.println(subtractList);

输出结果:

[1, 2, 3, 4]
[2]
[1, 3, 4]
[1, 3]

3.1.2、判空操作

通过CollectionUtils工具类的isEmpty方法可以轻松判断集合是否为空,isNotEmpty方法判断集合不为空。

List<Integer> list = new ArrayList<>();
list.add(2);
list.add(1);
list.add(3);
if (CollectionUtils.isEmpty(list)) {
    System.out.println("集合为空");
}
if (CollectionUtils.isNotEmpty(list)) {
    System.out.println("集合不为空");
}

3.1.3、从一个对象的集合中,获取某一属性的集合

从一个对象的集合中,获取某一属性的集合。如,从人员信息集合中获取人员姓名的集合

List<Map<String, Object>> list = new ArrayList<>();
		Map<String, Object> map1 = new HashMap<>();
		map1.put("name", "张三");
		map1.put("age", 1);
		Map<String, Object> map2 = new HashMap<>();
		map2.put("name", "李四");
		map2.put("age", 2);
		Map<String, Object> map3 = new HashMap<>();
		map3.put("name", "王五");
		map3.put("age", 3);
		list.add(map1);
		list.add(map2);
		list.add(map3);
		List<String> nameList = (List<String>) CollectionUtils.collect(
				list, new Transformer() {
					public Object transform(Object arg0) {
						Map<String, Object> map = (Map<String, Object>) arg0;
						return map.get("name");
					}
				});
		System.out.println(nameList);

运行结果

[张三, 李四, 王五]

3.1.4、从一个对象集合中过滤出只符合要求的对象

List<Map<String, Object>> list = new ArrayList<>();
		List<Map<String, Object>> tarList = new ArrayList<>();
		Map<String, Object> map1 = new HashMap<>();
		map1.put("name", "张三");
		map1.put("age", 1);
		Map<String, Object> map2 = new HashMap<>();
		map2.put("name", "张三");
		map2.put("age", 2);
		Map<String, Object> map3 = new HashMap<>();
		map3.put("name", "王五");
		map3.put("age", 3);
		list.add(map1);
		list.add(map2);
		list.add(map3);
		CollectionUtils.filter(list, new Predicate(){
			public boolean evaluate(Object arg0) {
				Map<String, Object> map = (Map<String, Object>)arg0;
				return "张三".equals((String)map.get("name"))
				   && 2 == Integer.parseInt(String.valueOf(map.get("age")));
			}
		});
		System.out.println(list);

运行结果

[{name=张三, age=2}]

3.2、MapUtils工具类

导入依赖

<dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-collections4</artifactId>
      <version>4.2</version>
</dependency>

3.2.1、获取基础类型的值

可以从指定map中获取常用基础类型的值,都会判断map,map为null返回null,get结果为null返回null:

image.png

示例

Map<String, Object> nullMap = null;
Map<String, Object> map = new HashMap<>();
map.put("user", new User());

map.put("boolean", true);
Assert.assertTrue(MapUtils.getBoolean(map, "boolean"));
//转换失败,返回默认值false
Assert.assertFalse(MapUtils.getBoolean(map, "user",false));
//目标map为null,返回null
Assert.assertNull(MapUtils.getBoolean(nullMap, "boolean"));
//目标map为null,返回默认值false
Assert.assertFalse(MapUtils.getBoolean(nullMap, "boolean", false));

Assert.assertTrue(MapUtils.getBooleanValue(map, "boolean"));
//转换失败,返回默认值false
Assert.assertFalse(MapUtils.getBooleanValue(map, "user",false));
//目标map为null,返回false
Assert.assertFalse(MapUtils.getBooleanValue(nullMap, "boolean"));
//目标map为null,返回默认值false
Assert.assertFalse(MapUtils.getBooleanValue(nullMap, "boolean", false));

map.put("integer", 5);
Assert.assertEquals(Integer.valueOf(5), MapUtils.getInteger(map, "integer"));
//转换失败,返回默认值5
Assert.assertEquals(Integer.valueOf(5), MapUtils.getInteger(map, "integer",5));
//目标map为null,返回null
Assert.assertEquals(null, MapUtils.getInteger(nullMap, "integer"));
//目标map为null,返回默认值5
Assert.assertEquals(Integer.valueOf(5), MapUtils.getInteger(nullMap, "integer", 5));

Assert.assertEquals(5, MapUtils.getIntValue(map, "integer"));
//转换失败,返回默认值5
Assert.assertEquals(5, MapUtils.getIntValue(map, "user",5));
//目标map为null,返回0
Assert.assertEquals(0, MapUtils.getIntValue(nullMap, "integer"));
//目标map为null,返回默认值5
Assert.assertEquals(5, MapUtils.getIntValue(nullMap, "integer", 5));

3.2.2、Map判空

HashMap<String, Object> map = new HashMap<>();
map.put("name","zhangsan");
map.put("sex",true);
map.put("age",34);
map.put("money",null);
// 使用MapUtils.isEmpty方法和MapUtils.isNotEmpty方法对Map进行空判断。
boolean empty = MapUtils.isEmpty(map);
boolean notEmpty = MapUtils.isNotEmpty(map);

3.2.3、数组操作

HashMap<String, Object> map = new HashMap<>();
map.put("name","zhangsan");
map.put("sex",true);
map.put("age",34);
map.put("money",null);

将二维数组放入Map中,使用MapUtils.putAll方法。

String[][] user = {{"names","zhangfsan"},{"sexs","1f"}};
Map map1 = MapUtils.putAll(map, user);