GitHub 3.7k Star Путь Java-инженера к тому, чтобы стать богом, разве вы не хотите узнать об этом?
Коллекции часто используются в повседневной разработке Java-разработки. В некоторых предыдущих статьях мы рассказали о некоторых вопросах, на которые следует обратить внимание при использовании классов коллекций, таких как «Почему Alibaba запрещает операцию удаления/добавления элементов в цикле foreach», «Почему Alibaba рекомендует указывать коллекции при инициализации коллекций». вместимость" и др.
Что касается классов коллекций, на самом деле в «Руководстве по разработке Java для Alibaba» есть еще одно положение:
В этой статье будет проанализировано, почему есть такое предложение? В чем причина этого?
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…