Collection Empty Check

To check whether all elements inside a collection are empty, use the isEmpty() method instead of size()==0 method.

This is because the isEmpty() method provides better readability and has a time complexity of O(1).

While the time complexity of the size() method for most collections we use is also O(1), there are many collections, particularly those in the java.util.concurrent package (such as ConcurrentLinkedQueue, ConcurrentHashMap, etc.), whose complexity is not O(1).

Below is the source code for the size() and isEmpty() methods of ConcurrentHashMap.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
public boolean isEmpty() {
return sumCount() <= 0L; // ignore transient negative values
}

Collection to Map Conversion

When using the toMap() method of the java.util.stream.Collectors class to convert to a Map collection, it is essential to note that an NPE (NullPointerException) will be thrown if the value is null.

1
2
3
4
5
6
7
8
9
10
11
12
class Person {
private String name;
private String phoneNumber;
// getters and setters
}

List<Person> bookList = new ArrayList<>();
bookList.add(new Person("jack","18163138123"));
bookList.add(new Person("martin",null));
// NPE
bookList.stream().collect(Collectors.toMap(Person::getName, Person::getPhoneNumber));

Let’s first examine the toMap() method of the java.util.stream.Collectors class. Inside, it invokes the merge() method of the Map interface.

1
2
3
4
5
6
7
8
9
10
11
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}

The merge() method of the Map interface, shown below, is a default implementation within the interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

The merge() method first calls the Objects.requireNonNull() method to check if the value is null.

1
2
3
4
5
6
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}

Collection Traversal

Avoid performing element remove/add operations within a foreach loop. For removing elements, use the Iterator approach instead. If concurrent operations are involved, ensure to synchronize access to the Iterator object.

Upon decompilation, you will discover that the foreach syntax underlyingly depends on Iterator. However, remove/add operations directly invoke methods of the collection itself, rather than Iterator‘s remove/add methods.

This leads to the Iterator inexplicably discovering that elements have been remove/add-ed, resulting in a ConcurrentModificationException being thrown to alert users of a concurrent modification anomaly. This is known as the fail-fast mechanism in single-threaded scenarios.

Fail-fast mechanism: When multiple threads modify a fail-fast collection, a ConcurrentModificationException may be thrown.

Starting from Java 8, you can use the Collection.removeIf() method to remove elements that satisfy specific conditions.

1
2
3
4
5
6
7
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= 10; ++i) {
list.add(i);
}
list.removeIf(filter -> filter % 2 == 0); /* delete all even number in list */
System.out.println(list); /* [1, 3, 5, 7, 9] */

In addition to directly using Iterator for traversal operations, you can also:

  • Utilize ordinary for loops
  • Employ fail-safe collection classes. All classes under the java.util package are fail-fast, while those under the java.util.concurrent package are fail-safe.
  • …..

Collection Deduplication

You can leverage the unique property of Set elements to quickly deduplicate a collection, avoiding the use of List‘s contains() for traversal deduplication or containment checks.

Here, we illustrate using HashSet and ArrayList as examples.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Set 
public static <T> Set<T> removeDuplicateBySet(List<T> data) {

if (CollectionUtils.isEmpty(data)) {
return new HashSet<>();
}
return new HashSet<>(data);
}

// List
public static <T> List<T> removeDuplicateByList(List<T> data) {

if (CollectionUtils.isEmpty(data)) {
return new ArrayList<>();

}
List<T> result = new ArrayList<>(data.size());
for (T current : data) {
if (!result.contains(current)) {
result.add(current);
}
}
return result;
}


The core difference lies in the implementation of the contains() method.

The contains() method of HashSet relies on the containsKey() method of HashMap, with a time complexity close to O(1) (O(1) when no hash collisions occur).

If we insert N elements into the Set, the time complexity is close to O(n).

The contains() method of ArrayList involves traversing all elements, resulting in a time complexity close to O(n).

Collection to Array Conversion

When converting a collection to an array, it is essential to use the collection’s toArray(T[] array) method, passing in an empty array of exactly the same type and with a length of 0.

The toArray(T[] array) method takes a generic array as its parameter. If no parameter is passed to the toArray method, it returns an array of type Object.

1
2
3
4
5
6
7
8
String [] s= new String[]{
"dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
};
List<String> list = Arrays.asList(s);
Collections.reverse(list);

s=list.toArray(new String[0]);

Due to JVM optimization, using new String[0] as the parameter for the Collection.toArray() method is now preferred. new String[0] serves as a template, specifying the type of the returned array. The 0 is to save space, as it merely serves to indicate the type of the returned array.

Array to Collection Conversion

When using the utility class Arrays.asList() to convert an array to a collection, avoid using its methods for modifying the collection. Its add/remove/clear methods will throw an UnsupportedOperationException exception.

Arrays.asList() is quite common in everyday development, allowing us to convert an array into a List collection.

The JDK source code explains this method as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Returns a fixed-size list backed by the specified array. (Changes to
* the returned list "write through" to the array.) This method acts
* as bridge between array-based and collection-based APIs, in
* combination with {@link Collection#toArray}. The returned list is
* serializable and implements {@link RandomAccess}.
*
* <p>This method also provides a convenient way to create a fixed-size
* list initialized to contain several elements:
* <pre>
* List&lt;String&gt; stooges = Arrays.asList("Larry", "Moe", "Curly");
* </pre>
*
* @param a the array by which the list will be backed
* @return a list view of the specified array
*/
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}

1. Arrays.asList() is a generic method, and the array passed must be an object array, not a primitive type array.

1
2
3
4
5
6
7
8
int[] myArray = {1, 2, 3};
List myList = Arrays.asList(myArray);
System.out.println(myList.size());//1
System.out.println(myList.get(0));//array address
System.out.println(myList.get(1));//ArrayIndexOutOfBoundsException
int[] array = (int[]) myList.get(0);
System.out.println(array[0]);//1

When passing a primitive data type array, the actual parameter received by Arrays.asList() is not the elements of the array, but the array object itself! In this case, the only element of the List is the array itself, which explains the code above.

2. Using the collection’s modification methods: add(), remove(), clear() will throw an exception.

1
2
3
4
5
List myList = Arrays.asList(1, 2, 3);
myList.add(4);//UnsupportedOperationException
myList.remove(1);//UnsupportedOperationException
myList.clear();//UnsupportedOperationException

Arrays.asList() does not return a java.util.ArrayList, but rather an inner class of java.util.Arrays, which does not implement collection modification methods or override these methods.

1
2
3
List myList = Arrays.asList(1, 2, 3);
System.out.println(myList.getClass());//class java.util.Arrays$ArrayList

This inner class inherits from AbstractList and implements the RandomAccess and Serializable interfaces.

If we take a look at the add/remove/clear methods of java.util.AbstractList, we’ll understand why UnsupportedOperationException is thrown.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public E remove(int index) {
throw new UnsupportedOperationException();
}
public boolean add(E e) {
add(size(), e);
return true;
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}

public void clear() {
removeRange(0, size());
}
protected void removeRange(int fromIndex, int toIndex) {
ListIterator<E> it = listIterator(fromIndex);
for (int i=0, n=toIndex-fromIndex; i<n; i++) {
it.next();
it.remove();
}
}

So, how do we correctly convert an array to an ArrayList?

1、Manually implement a utility class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//JDK1.5+
static <T> List<T> arrayToList(final T[] array) {
final List<T> l = new ArrayList<T>(array.length);

for (final T s : array) {
l.add(s);
}
return l;
}


Integer [] myArray = { 1, 2, 3 };
System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList

2、The simplest method.

1
List list = new ArrayList<>(Arrays.asList("a", "b", "c"))

3、Utilize Java 8’s Stream (recommended).

1
2
3
4
5
6
Integer [] myArray = { 1, 2, 3 };
List myList = Arrays.stream(myArray).collect(Collectors.toList());

int [] myArray2 = { 1, 2, 3 };
List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());

4、Use Guava.

For immutable collections, you can use the ImmutableList class and its of() and copyOf() factory methods (parameters cannot be empty).

1
2
List<String> il = ImmutableList.of("string", "elements");  // from varargs
List<String> il = ImmutableList.copyOf(aStringArray); // from array

For mutable collections, you can use the Lists class and its newArrayList() factory method.

1
2
3
List<String> l1 = Lists.newArrayList(anotherListOrCollection);    // from collection
List<String> l2 = Lists.newArrayList(aStringArray); // from array
List<String> l3 = Lists.newArrayList("or", "string", "elements"); // from varargs

5、Use Apache Commons Collections.

1
2
List<String> list = new ArrayList<String>();
CollectionUtils.addAll(list, str);

6、 Use Java 9’s List.of() method.

1
2
Integer[] array = {1, 2, 3};
List<Integer> list = List.of(array);

REFEERENCE

(1) https://javaguide.cn/java/collection/java-collection-precautions-for-use.html#%E6%95%B0%E7%BB%84%E8%BD%AC%E9%9B%86%E5%90%88