Почему Alibaba требует осторожного использования метода subList в ArrayList

Java

GitHub 3.7k Star Путь Java-инженера к тому, чтобы стать богом, разве вы не хотите узнать об этом?

Путь 3,7k Star Java-инженера GitHub к тому, чтобы стать богом, вы действительно не хотите узнать об этом?

Путь Java-инженера с 3,7 тысячами звезд GitHub к тому, чтобы стать богом, вы действительно уверены, что не хотите прийти и учиться?

Коллекции часто используются в повседневной разработке Java-разработки. В некоторых предыдущих статьях мы рассказали о некоторых вопросах, на которые следует обратить внимание при использовании классов коллекций, таких как «Почему Alibaba запрещает операцию удаления/добавления элементов в цикле foreach», «Почему Alibaba рекомендует указывать коллекции при инициализации коллекций». вместимость" и др.

Что касается классов коллекций, на самом деле в «Руководстве по разработке Java для Alibaba» есть еще одно положение:

-w1379

В этой статье будет проанализировано, почему есть такое предложение? В чем причина этого?

subList

subList — это метод, определенный в интерфейсе List. Этот метод в основном используется для возврата сегмента в коллекции. Его можно понимать как перехват некоторых элементов в коллекции. Его возвращаемое значение также является списком.

Например, следующий код:

public static void main(String[] args) {
    List<String> names = new ArrayList<String>() {{
        add("Hollis");
        add("hollischuang");
        add("H");
    }};

    List subList = names.subList(0, 1);
    System.out.println(subList);
}

Вывод приведенного выше кода:

[Hollis]

Если мы изменим код и попытаемся принудительно вернуть значение subList в ArrayList:

public static void main(String[] args) {
    List<String> names = new ArrayList<String>() {{
        add("Hollis");
        add("hollischuang");
        add("H");
    }};

    ArrayList subList = names.subList(0, 1);
    System.out.println(subList);
}

Приведенный выше код вызовет исключение:

java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList

Он не только сообщит об ошибке, если будет преобразован в ArrayList, но и классы реализации List, такие как LinkedList и Vector, также сообщат об ошибке.

Итак, почему возникает эта ошибка? Далее рассмотрим глубже.

основной принцип

Прежде всего, давайте посмотрим, что такое List, возвращаемый методом subList, об этом говорится в комментарии к исходному коду JDK:

Returns a view of the portion of this list between the specifiedfromIndex, inclusive, and toIndex, exclusive.

Другими словами, возврат subList — это представление, так что же такое представление?

Давайте посмотрим на исходный код subList:

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

Этот метод возвращает SubList, который является внутренним классом в ArrayList.

Класс SubList определяет set, get, size, add, remove и другие методы отдельно.

Когда мы вызываем метод subList, мы создаем SubList, вызывая конструктор SubList, поэтому давайте посмотрим, что делает этот конструктор:

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;
}

Как видите, этот конструктор напрямую присваивает исходный список и некоторые свойства в списке некоторым своим собственным свойствам.

То есть SubList не воссоздает список, а напрямую ссылается на исходный список (возвращает представление родительского класса) и просто указывает диапазон элементов, которые он хочет использовать (от индекса (включено) до индекса (исключая )).

Итак, почему коллекцию, полученную методом subList, нельзя напрямую преобразовать в ArrayList? Поскольку SubList является только внутренним классом ArrayList, между ними нет интеграционной связи, поэтому прямое преобразование типов выполнить невозможно.

Что не так с видом

Изучив ранее исходный код, мы знаем, что метод subList() не воссоздает ArrayList, а возвращает внутренний класс ArrayList — SubList.

Этот SubList является представлением ArrayList.

Так в чем проблема с этим представлением? Нам нужно просто написать несколько фрагментов кода, чтобы посмотреть.

1. Неструктурные изменения в SubList

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : " + sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : " + subList);

    subList.set(1, "666");

    System.out.println("subList.set(3,666) 得到List :");
    System.out.println("subList : " + subList);
    System.out.println("sourceList : " + sourceList);

}

получил ответ:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.set(3,666) 得到List :
subList : [L, 666, I]
sourceList : [H, O, L, 666, I, S]

Когда мы попытались изменить значение элемента в подсписке с помощью метода set, мы обнаружили, что значение соответствующего элемента в исходном списке также изменилось.

Точно так же, если мы используем тот же метод для изменения элемента в исходном списке, соответствующее значение в подсписке также изменится. Читатели могут попробовать сами.

1. Структурные изменения в SubList

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : " + sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : " + subList);

    subList.add("666");

    System.out.println("subList.add(666) 得到List :");
    System.out.println("subList : " + subList);
    System.out.println("sourceList : " + sourceList);

}

получил ответ:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.add(666) 得到List :
subList : [L, L, I, 666]
sourceList : [H, O, L, L, I, 666, S]

Мы пытаемся изменить структуру subList, то есть добавить в него элементы, тогда получается, что структура sourceList тоже изменилась.

1. Структурно изменить исходный Список

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : " + sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : " + subList);

    sourceList.add("666");

    System.out.println("sourceList.add(666) 得到List :");
    System.out.println("sourceList : " + sourceList);
    System.out.println("subList : " + subList);

}

получил ответ:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
    at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)
    at java.util.AbstractList.listIterator(AbstractList.java:299)
    at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)
    at java.util.AbstractCollection.toString(AbstractCollection.java:454)
    at java.lang.String.valueOf(String.java:2994)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at com.hollis.SubListTest.main(SubListTest.java:28)

Мы попытались внести изменения в структуру sourceList, добавив к нему элементы, и обнаружили, что возникло исключение ConcurrentModificationException. По поводу этой аномалии мыКакой, к черту, отказоустойчивый, случайно наступивший на яму?", здесь принцип тот же, поэтому повторяться не буду.

резюме

Кратко подытожим, метод subList класса List не создает новый список, а использует представление исходного списка, которое представлено внутренним классом SubList.

Поэтому мы не можем привести List, возвращаемый методом subList, к такому классу, как ArrayList, потому что между ними нет отношения наследования.

Кроме того, при модификации представления и исходного списка также необходимо обратить внимание на несколько моментов, особенно на взаимное влияние между ними:

1. Неструктурные изменения, внесенные в родительский (исходный список) и дочерний (подсписок) список, будут влиять друг на друга.

2. Внесите структурные изменения в дочерний список, и операция также будет отражена в родительском списке.

3. Структурная модификация родительского списка вызовет исключение ConcurrentModificationException.

Поэтому в Руководстве по разработке Java для Alibaba есть еще одна оговорка:

Как создать новый список

Если вам нужно внести изменения в подсписок, вы не хотите трогать исходный список. Затем можно создать копию subList:

subList = Lists.newArrayList(subList);
list.stream().skip(strart).limit(end).collect(Collectors.toList());

PS: Недавно «Руководство по разработке Java для Alibaba» было официально переименовано в «Руководство по разработке Java», и была выпущена новая версия, в которую добавлено 21 новое правило и изменено 112 описаний.

Следуйте официальному справочному ответу учетной записи: руководство, вы можете получить последнюю версию руководства по разработке Java.

Использованная литература:Краткое описание.com/afraid/585485124… Блог Woohoo.cn на.com/connected blog/afraid/6…