Java Collection Details 8: Java Collection Class Details, Details Decide Success or Failure

Keywords: Java github network Programming

The Java Collection Detailed Series is a new series that I am going to start after I have completed a series of blogs that consolidate the Java Foundation Chapter.

These articles will be sorted out into my Java Interview Guide warehouse on GitHub, and more exciting content can be found in my warehouse.

https://github.com/h2pl/Java-Tutorial

If you like, please click Star, fork Ha

The article was first published on my personal blog:

www.how2playlife.com

Today, let's explore some technical details of Java collection classes. It mainly explains and supplements some knowledge points which are easy to be missed and misunderstood. It may not be comprehensive, please forgive me.

Initial capacity

Collection is very widely used in Java programming, it is like the sea, Hainabaichuan, like a universal container, containing everything, and this sea, the universal container can be infinitely larger (if conditions permit). When the volume of the sea and container becomes very large, its initial capacity will become very important, because digging and expanding the sea will require a lot of manpower, material and financial resources.

In the same way, Collection's initial capacity is extremely important. So: For known scenarios, specify the initial capacity for the collection.

public static void main(String[] args) {
    StudentVO student = null;
    long begin1 = System.currentTimeMillis();
    List<StudentVO> list1 = new ArrayList<>();
    for(int i = 0 ; i < 1000000; i++){
        student = new StudentVO(i,"chenssy_"+i,i);
        list1.add(student);
    }
    long end1 = System.currentTimeMillis();
    System.out.println("list1 time: " + (end1 - begin1));
    
    long begin2 = System.currentTimeMillis();
    List<StudentVO> list2 = new ArrayList<>(1000000);
    for(int i = 0 ; i < 1000000; i++){
        student = new StudentVO(i,"chenssy_"+i,i);
        list2.add(student);
    }
    long end2 = System.currentTimeMillis();
    System.out.println("list2 time: " + (end2 - begin2));
}

Both lists in the above code insert 1000000 pieces of data, except that list1 has no application initialization capacity, while list2 has 1000000 initialization capacity. The results are as follows:

list1 time: 1638
list2 time: 921

From the above results, we can see that the speed of list2 is about twice that of list1. As mentioned earlier in LZ, the expansion mechanism of ArrayList is resource-intensive. Let's first look at ArrayList's add method:

public boolean add(E e) {  
        ensureCapacity(size + 1);   
        elementData[size++] = e;  
        return true;  
    }  

public void ensureCapacity(int minCapacity) {  
    modCount++;         //Modification counter
    int oldCapacity = elementData.length;    
    //The current required length exceeds the length of the array for capacity expansion
    if (minCapacity > oldCapacity) {  
        Object oldData[] = elementData;  
        //New capacity = old capacity * 1.5 + 1
        int newCapacity = (oldCapacity * 3)/2 + 1;  
            if (newCapacity < minCapacity)  
                newCapacity = minCapacity;  
      //Array copy to generate new arrays 
      elementData = Arrays.copyOf(elementData, newCapacity);  
    }  
}

Each time an additional element is added to the ArrayList, it detects whether the current capacity of the ArrayList has reached the critical point, and if it reaches the critical point, it will expand by 1.5 times. However, the expansion of ArrayList and the generation of new arrays by copying arrays are quite resource-intensive. So if we know the usage scenario of the set beforehand and the approximate range of the set, we'd better specify the initialization capacity, so that we can make better use of the resources, especially under the premise of large amount of data, the improvement of efficiency and the utilization of resources will be more advantageous.

Defects of asList

In the actual development process, we often use asList to talk about the conversion of arrays to lists. This method is very convenient to use, but asList method has several shortcomings:

Avoid converting arrays of basic data types to lists

Converting an array of eight basic types to a list has an interesting flaw. First look at the following procedures:

public static void main(String[] args) {
        int[] ints = {1,2,3,4,5};
        List list = Arrays.asList(ints);
        System.out.println("list'size: " + list.size());
    }
------------------------------------
outPut: 
list'size: 1

The result of the program is not 5 as we expected, but 1 against the sky. What's the situation? First look at the source code:

public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

The parameters accepted by asList are variable length parameters of a generic type. We know that the basic data types can not be hairstyled, that is to say, eight basic types can not be used as parameters of asList. If we want to be used as generic parameters, we must use the corresponding packaging type. But why is there no error in this instance?

Because this instance takes an array of type int as its parameter, in Java, an array is an object, and it can be generic. So this example won't be wrong. Since the example treats an array of the entire int type as a generic parameter, there is only one list of ints after the asList transformation. As follows:

public static void main(String[] args) {
    int[] ints = {1,2,3,4,5};
    List list = Arrays.asList(ints);
    System.out.println("list Types:" + list.get(0).getClass());
    System.out.println("list.get(0) == ints: " + list.get(0).equals(ints));
}

outPut:
Type of list: class [I]
list.get(0) == ints: true
From this running result, we can fully prove that the elements in the list are int arrays. With this in mind, it's clear how to modify it: change int to Integer.

public static void main(String[] args) {
        Integer[] ints = {1,2,3,4,5};
        List list = Arrays.asList(ints);
        System.out.println("list'size: " + list.size());
        System.out.println("list.get(0) Types:" + list.get(0).getClass());
        System.out.println("list.get(0) == ints[0]: " + list.get(0).equals(ints[0]));
    }
----------------------------------------
outPut:
list'size: 5
list.get(0) Types:class java.lang.Integer
list.get(0) == ints[0]: true

Lists generated by asList are not operable

For the above example, let's make a minor modification:

public static void main(String[] args) {
        Integer[] ints = {1,2,3,4,5};
        List list = Arrays.asList(ints);
        list.add(6);
    }

This example is to say that ints is converted to list category through asList, and then an element is added through add method. This example can not be simpler, but what about the results? Give us what we expect:

Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.AbstractList.add(Unknown Source)
    at java.util.AbstractList.add(Unknown Source)
    at com.chenssy.test.arrayList.AsListTest.main(AsListTest.java:10)

The result throws an Unsupported OperationException exception, which indicates that the list does not support the add method. This makes us depressed. How can list not support the add method? Is jdk's head blocked? Let's look at the source code for asList:

public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

After the asList accepts the parameters, it directly new s an ArrayList. Is there anything wrong with it? Don't worry. Look down again.

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) {
            if (array==null)
                throw new NullPointerException();
            a = array;
        }
        //.................
    }

This is the source code of ArrayList, from which we can see that this ArrayList is not java.util.ArrayList, it is the internal class of Arrays.

The inner class provides size, toArray, get, set, indexOf, contains methods, while methods such as add and remove that change the list result inherit from the AbstractList parent class. These methods are also quite wonderful. They throw an Unsupported OperationException exception directly:

public boolean add(E e) {
        add(size(), e);
        return true;
    }
    
    public E set(int index, E element) {
        throw new UnsupportedOperationException();
    }
    
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
    
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }

From these codes, it can be seen that the list returned by asList is just a list, and it does not have the basic features of list (getting longer). The list is an immutable list of length. How long is the array of parameters passed in, and how long is the list returned? So: don't try to change the list returned by the asList, or you'll suffer.

Defects of subList

We often use subString method to segment String objects, and we can also use subList, subMap, subSet to segment List, Map, Set, but there are some flaws in this segmentation.

subList returns just one view

First, let's look at the following examples:

public static void main(String[] args) {

    List<Integer> list1 = new ArrayList<Integer>();
    list1.add(1);
    list1.add(2);
    
    //Create a new list 2 containing list1 through the constructor
    List<Integer> list2 = new ArrayList<Integer>(list1);
    
    //Generate a list 3 like list1 through subList
    List<Integer> list3 = list1.subList(0, list1.size());
    
    //Modify list3
    list3.add(3);
    
    System.out.println("list1 == list2: " + list1.equals(list2));
    System.out.println("list1 == list3: " + list1.equals(list3));
}

This example is very simple, it is nothing more than through the constructor, subList to regenerate a list like list1, then modify list3, and finally compare list1 == list2?, list1 == list3?.

According to our usual way of thinking, because list3 adds a new element through add, it must be different from list1, and list2 is constructed through list1, so it should be equal, so the result should be:

list1 == list2: true
list1 == list3: false

First, whether the result is correct or not, let's look at the source code of subList:

public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
}

The subListRangeCheck method is to determine whether fromIndex and toIndex are legal. If they are legal, they return a subList object directly. It is important to note that a parameter this is passed when the new object is generated, because it represents the original list.

/**

 * inherit AbstractList Class implementation RandomAccess Interface
 */
private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;    //list
    private final int parentOffset;   
    private final int offset;
    int size;

    //Constructor
    SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }

    //set method
    public E set(int index, E e) {
        rangeCheck(index);
        checkForComodification();
        E oldValue = ArrayList.this.elementData(offset + index);
        ArrayList.this.elementData[offset + index] = e;
        return oldValue;
    }

    //get method
    public E get(int index) {
        rangeCheck(index);
        checkForComodification();
        return ArrayList.this.elementData(offset + index);
    }

    //add method
    public void add(int index, E e) {
        rangeCheckForAdd(index);
        checkForComodification();
        parent.add(parentOffset + index, e);
        this.modCount = parent.modCount;
        this.size++;
    }

    //remove method
    public E remove(int index) {
        rangeCheck(index);
        checkForComodification();
        E result = parent.remove(parentOffset + index);
        this.modCount = parent.modCount;
        this.size--;
        return result;
    }
}

This SubLsit is an internal class of ArrayList, which, like ArrayList, inherits AbstractList and implements Random Access interfaces. At the same time, it also provides the commonly used methods of get, set, add, remove and so on. But its constructor is somewhat special, and there are two points to note in this constructor:

1. this.parent = parent; and parent is the list passed in front, that is to say, this.parent is the reference to the original list.

2. this.offset = offset + fromIndex;this.parentOffset = fromIndex;. At the same time, it even passes modCount (fail-fast mechanism) in the constructor.

Let's look at the get method again. In the get method, return ArrayList.this.elementData(offset + index);

This code clearly indicates that get returns the element at the offset + index position of the original list. The same goes for the add method:

parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
In the remove method

E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;

Admittedly, here we can judge that SubList returned by subList is also a subclass of AbstractList. At the same time, its methods such as get, set, add, remove and so on all operate on the original list. It does not generate a new object like subString.

So the subList returns only one view of the original list, and all its operations will eventually work on the original list.

So from the analysis here, we can conclude that the above results should be exactly the opposite of the above answers.

list1 == list2: false
list1 == list3: true

After subList generates sublists, do not attempt to manipulate the original list

From the above we know that the subList generated by the subList is only a view of the original list. If we operate on the subList, its effect will be shown on the original list. But what happens if we operate on the original list?

public static void main(String[] args) {

    List<Integer> list1 = new ArrayList<Integer>();
    list1.add(1);
    list1.add(2);
    
    //Generate a list 3 like list1 through subList
    List<Integer> list3 = list1.subList(0, list1.size());
    //Modify list1
    list1.add(3);
    
    System.out.println("list1'size: " + list1.size());
    System.out.println("list3'size: " + list3.size());
}

If this example is not unexpected, then the size of both list s should be 3, but it is contrary to our wishes. In fact, the result we get is as follows:

list1'size: 3
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$SubList.checkForComodification(Unknown Source)
    at java.util.ArrayList$SubList.size(Unknown Source)
    at com.chenssy.test.arrayList.SubListTest.main(SubListTest.java:17)

list1 normally outputs, but list3 throws the Concurrent ModificationException exception. My colleagues who have read another blog must be very impressed with this exception, fail-fast? Good is the fail-fast mechanism, in the fail-fast mechanism, LZ spent a lot of effort to describe this exception, so here LZ will not talk about this exception more. Let's look at the size method again:

public int size() {
            checkForComodification();
            return this.size;
        }

The size method first passes checkForComodification validation, and then returns to this.size.

private void checkForComodification() {
            if (ArrayList.this.modCount != this.modCount)
                throw new ConcurrentModificationException();
        }

This method shows that Concurrent ModificationException is thrown when the modCount of the original list is not equal to this.modCount.

At the same time, we know that modCount "inherits" the original list modCount in the process of new, and only when the list (sub-list) is modified will the value be changed (first expressed in the original list and then acted on the sub-list).

In this example, we operate on the original list. Of course, the modCount of the original list will not react on the modCount of the sublist, so the exception will be thrown.

For sub-list view, it is dynamically generated. After generation, do not operate on the original list, otherwise it will inevitably lead to the unstable view and throw an exception. The best way is to set the original list to read-only state, and to operate on the sub-list:

// Generate a list 3 like list1 through subList

List<Integer> list3 = list1.subList(0, list1.size());

// Set list1 to read-only

list1 = Collections.unmodifiableList(list1);

It is recommended to use subList to process local lists

In the process of development, we will encounter such a problem: after acquiring a pile of data, we need to delete a certain section of data. For example, there is a list with 1000 records. We need to delete data at 100-200 locations. Maybe we will do this:

for(int i = 0 ; i < list1.size() ; i++){
   if(i >= 100 && i <= 200){
       list1.remove(i);
       /*
        * Of course, there is a problem with this code. After list remove, the following elements will be filled in.
         * So i need to be dealt with simply. Of course, this is not the problem discussed here.
         */
   }
}

This should be the way most of us deal with it, in fact, there is a better way to use subList. As mentioned earlier in LZ, the operation of sublists is reflected in the original list. So the next line of code is all done:

list1.subList(100, 200).clear();

Simple but gorgeous!!!!

Keep compareTo and equals synchronized

In Java, we often use Comparable interface to achieve sorting, in which compareTo is the interface method. We know that compareTo returns 0 to denote the equality of two objects, returns positive to denote greater and returns negative to denote less than. At the same time, we also know that equals can also judge whether two objects are equal, so whether there is a relationship between them?

public class Student implements Comparable<Student>{
    private String id;
    private String name;
    private int age;
    
    public Student(String id,String name,int age){
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public boolean equals(Object obj){
        if(obj == null){
            return false;
        }
        
        if(this == obj){
            return true;
        }
        
        if(obj.getClass() != this.getClass()){
            return false;
        }
        
        Student student = (Student)obj;
        if(!student.getName().equals(getName())){
            return false;
        }
        
        return true;
    }
    
    public int compareTo(Student student) {
        return this.age - student.age;
    }

    /** Omitting getter and setter methods */
}

Student class implements Comparable interface and equals method, where compareTo is compared according to age and equals is compared according to name.

public static void main(String[] args){
        List<Student> list = new ArrayList<>();
        list.add(new Student("1", "chenssy1", 24));
        list.add(new Student("2", "chenssy1", 26));
        
        Collections.sort(list);   //sort
        
        Student student = new Student("2", "chenssy1", 26);
        
        //Retrieving the location of student in the list
        int index1 = list.indexOf(student);
        int index2 = Collections.binarySearch(list, student);
        
        System.out.println("index1 = " + index1);
        System.out.println("index2 = " + index2);
    }

According to the conventional way of thinking, the index of the two should be the same, because they retrieve the same object, but unfortunately, the results of their operation are as follows:

index1 = 0
index2 = 1

Why does it produce such different results? This is because indexOf and binary Search have different implementation mechanisms.

indexOf is based on equals. Once equals returns to TRUE, it is assumed that the same element has been found.

The binary Search is based on the compareTo method, and when compareTo returns to 0, it is assumed that the element has been found.

In the Student class we implemented, we override the compareTo and equals methods, but our compareTo and equals methods are based on different criteria, one is age-based and the other is name-based.

Comparisons based on different results are likely to be different. So knowing the reason, we have to revise it: we can keep the basis of comparison between the two in line.

For compareTo and equals, we can summarize as follows: compareTo is to judge whether the elements are equal in the ranking, equals is to judge whether the elements are equal. Since one decision determines the ranking position and one decision is equal, it is very necessary to ensure that when the ranking position is the same, its equals should also be equal.

The way to make them equal is that they should depend on the same conditions. Equals should also be equal when compareto is equal, and equals should not be equal when compareto is unequal, and compareto determines ranking based on certain attributes.

Reference articles

https://www.cnblogs.com/galibujianbusana/p/6600226.html

http://blog.itpub.net/69906029/viewspace-2641300/

https://www.cnblogs.com/itxiaok/p/10356553.html

Wechat Public Number

Java Technology

If you want to pay real-time attention to my updated articles and shared dried goods, you can pay attention to my public number (Java Technology Jianghu), a technology station of an Ali Java engineer, by Huang Xiaoxiao, focusing on Java related technologies: SSM, Spring Boot, MySQL, distributed, middleware, cluster, Linux, network, multi-threading, occasionally talking about Docker, ELK, and sharing at the same time. Technical dry goods and learning experience, dedicated to Java stack development!

Java Engineers Necessary Learning Resources: Some Java Engineers commonly use learning resources. After paying attention to the public number, the keyword "Java" can be answered in the background for free without routine access.

Personal Public No. Huang Xiaoxiao

Huang Xiaoxiao is a 985 master of cross-examination software engineering. He has taught himself Java for two years. He has been offer ed by nearly ten big factories, such as BAT, and has grown from a technical Xiaobai to an Ali engineer.

The author concentrates on JAVA backend technology stack, and is keen to share programmers'dry goods, learning experience, job hunting experience and programmer life. At present, Huang Xiaoxiao's CSDN blog has millions of + visits, and he knows that fans are 2+. There are 10W + readers on the whole network.

Huang Xiaoxiao is a slash youth, who insists on learning and writing. He believes in the power of lifelong learning. He hopes to make friends with more programmers and make progress and grow together. Pay attention to the public number [Huang Xiaoxiao], and then reply [original e-book]. You can get my original e-book `The Training Manual for Rookie Programmers: From Technical Xiaobai to Alibaba Java Engineer'.

Programmer 3T technology learning resources: Some programmers learn technology resources package, after paying attention to the public number, the background reply keyword "information" can be free of charge without routine access.

Posted by littleelmo on Sat, 12 Oct 2019 05:32:19 -0700