안녕! 지난 수업에서 우리는 클래스에 대해 알게 되었고
ArrayList
이 클래스로 가장 일반적인 작업을 수행하는 방법을 배웠습니다. 또한 an ArrayList
과 일반 배열의 몇 가지 차이점을 지적했습니다. 그러나 우리는 한 가지 주제 , 즉ArrayList
. 지금 논의하겠습니다. 일반 배열에서 요소를 삭제하는 것이 그다지 편리하지 않다고 이미 언급했습니다. 요소 자체를 삭제할 수 없기 때문에 값을 "제로아웃"(null로 설정)할 수만 있습니다.
public class Cat {
private String name;
public Cat(String name) {
this.name = name;
}
public static void main(String[] args) {
Cat[] cats = new Cat[3];
cats[0] = new Cat("Thomas");
cats[1] = new Cat("Behemoth");
cats[2] = new Cat("Lionel Messi");
cats[1] = null;
System.out.println(Arrays.toString(cats));
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
출력: [Cat{name='Thomas'}, null, Cat{name='Lionel Messi'}] 그러나 배열 요소를 null로 설정하면 "구멍"이 생깁니다. 배열에서 위치를 제거하지 않고 해당 내용만 제거했습니다. 50마리의 고양이 배열이 있고 그 중 17마리를 이런 식으로 제거하면 어떤 일이 일어날지 상상해 보십시오. 우리는 17개의 구멍을 가진 배열을 갖게 될 것입니다. 계속 추적해 보세요! 새 값을 쓸 수 있는 빈 셀의 수를 기억하는 것은 비현실적입니다. 한 가지 실수를 하면 원하는 개체 참조를 덮어쓰게 됩니다. 물론 이를 좀 더 신중하게 수행하는 방법이 있습니다. 요소를 제거한 후 요소를 배열의 맨 앞으로 이동하여 끝에 "구멍"을 넣습니다.
public static void main(String[] args) {
Cat[] cats = new Cat[4];
cats[0] = new Cat("Thomas");
cats[1] = new Cat("Behemoth");
cats[2] = new Cat("Lionel Messi");
cats[2] = new Cat("Fluffy");
cats[1] = null;
for (int i = 2; i < cats.length-1; i++) {
cats [i-1] = cats [i];// Move the elements to the front of the array, so the empty position is at the end
}
System.out.println(Arrays.toString(cats));
}
출력: [Cat{name='Thomas'}, Cat{name='Fluffy'}, Cat{name='Fluffy'}, null] 이것은 더 좋아 보이지만 강력한 솔루션이라고 할 수는 없습니다. 배열에서 요소를 삭제할 때마다 이 코드를 작성해야 한다는 사실 외에 다른 이유가 없다면! 이것은 나쁜 선택입니다. 다른 방법으로 별도의 메서드를 만들 수 있습니다.
public void deleteCat(Cat[] cats, int indexToDelete) {
//...delete the cat corresponding to the index and move the elements
}
그러나 이것은 거의 사용되지 않습니다. 이 방법은 객체 에만 사용할 수 Cat
있고 다른 유형에는 사용할 수 없습니다. 즉, 프로그램에 배열과 함께 사용하려는 또 다른 100개의 클래스가 있는 경우 각 클래스에서 정확히 동일한 논리로 동일한 메서드를 작성해야 합니다. 이것은 완전히 재앙입니다 -_- 하지만 ArrayList
클래스는 이 문제를 해결합니다! 요소를 제거하기 위한 특수 메서드를 구현합니다.remove()
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<>();
Cat thomas = new Cat("Thomas");
Cat behemoth = new Cat("Behemoth");
Cat lionel = new Cat("Lionel Messi");
Cat fluffy = new Cat ("Fluffy");
cats.add(thomas);
cats.add(behemoth);
cats.add(lionel);
cats.add(fluffy);
System.out.println(cats.toString());
cats.remove(1);
System.out.println(cats.toString());
}
객체의 인덱스를 메서드에 전달하면 객체를 삭제합니다(배열에서처럼). 이 remove()
방법에는 두 가지 특별한 기능이 있습니다. 첫째, "구멍"을 남기지 않습니다. 이전에 직접 작성한 요소가 중간에서 제거될 때 요소를 이동하는 데 필요한 논리를 이미 구현합니다 . 이전 코드의 출력을 살펴보십시오.
[Cat{name='Thomas'}, Cat{name='Behemoth'}, Cat{name='Lionel Messi'}, Cat{name='Fluffy'}]
[Cat{name='Thomas'}, Cat{name='Lionel Messi'}, Cat{name='Fluffy'}]
가운데 고양이 한 마리를 빼고 나머지는 빈 공간이 없도록 옮겼습니다. 둘째 , 인덱스(일반 배열과 같음)뿐만 아니라 참조로 도 객체를 삭제할 수 있습니다 .
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<>();
Cat thomas = new Cat("Thomas");
Cat behemoth = new Cat("Behemoth");
Cat lionel = new Cat("Lionel Messi");
Cat fluffy = new Cat ("Fluffy");
cats.add(thomas);
cats.add(behemoth);
cats.add(lionel);
cats.add(fluffy);
System.out.println(cats.toString());
cats.remove(lionel);
System.out.println(cats.toString());
}
출력: [Cat{name='Thomas'}, Cat{name='Behemoth'}, Cat{name='Lionel Messi'}, Cat{name='Fluffy'}] [Cat{name='Thomas'}, Cat{name='Behemoth'}, Cat{name='Fluffy'}] 원하는 개체의 인덱스를 항상 추적하고 싶지 않은 경우 매우 편리할 수 있습니다. 일반적인 삭제 방법을 알아낸 것 같습니다. 이제 이 상황을 상상해 봅시다. 목록을 반복하고 특정 이름을 가진 고양이를 제거하려고 합니다 . for
이를 위해 Rishi의 수업에서 소개한 빠른 루프(for-each 루프라고도 함)를 사용합니다 .
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<>();
Cat thomas = new Cat("Thomas");
Cat behemoth = new Cat("Behemoth");
Cat lionel = new Cat("Lionel Messi");
Cat fluffy = new Cat ("Fluffy");
cats.add(thomas);
cats.add(behemoth);
cats.add(lionel);
cats.add(fluffy);
for (Cat cat: cats) {
if (cat.name.equals("Behemoth")) {
cats.remove(cat);
}
}
System.out.println(cats);
}
코드는 완벽하게 논리적으로 보입니다. 그러나 그 결과는 매우 놀랍습니다. java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)의 java.util.ArrayList$Itr.next(ArrayList. java:831) at Cat.main(Cat.java:25) 일종의 오류가 있으며 발생한 이유가 명확하지 않습니다. 이 프로세스에는 해결해야 하는 여러 가지 뉘앙스가 포함됩니다. 기억해야 할 일반적인 규칙은 다음과 같습니다. 컬렉션을 반복하면서 동시에 해당 요소를 변경할 수 없습니다. 그리고 우리는 단순히 제거가 아니라 모든 종류의 변화를 의미합니다. 고양이 제거를 새 고양이를 삽입하려는 시도로 바꾸면 결과는 동일합니다.
for (Cat cat: cats) {
cats.add(new Cat("Salem Saberhagen"));
}
System.out.println(cats);
"main" 스레드의 예외 java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayList$Itr.next(ArrayList.java:831) at Cat.main( Cat.java:25) 하나의 작업을 다른 작업으로 변경했지만 결과는 변경되지 않았습니다. 동일한 ConcurrentModificationException 이 발생합니다 . 목록을 반복하는 동안 목록을 변경하여 위의 규칙을 어기려고 할 때 정확하게 발생합니다. Java에서는컬렉션을 반복하는 동안 항목을 삭제하기 위해 iterator (Iterator
클래스Iterator
는 요소 목록을 안전하게 반복할 책임이 있습니다. 3가지 방법만 있기 때문에 매우 간단합니다.
hasNext()
- 목록에 다음 항목이 있는지 또는 이미 마지막 항목에 도달했는지 여부에 따라 true 또는 false를 반환합니다.next()
- 목록의 다음 항목을 반환합니다.remove()
- 목록에서 항목을 제거합니다.
Iterator<Cat> catIterator = cats.iterator();// Create an iterator
while(catIterator.hasNext()) {// As long as there are elements in the list
Cat nextCat = catIterator.next();// Get the next element
System.out.println(nextCat);// Display it
}
출력: Cat{name='Thomas'} Cat{name='Behemoth'} Cat{name='Lionel Messi'} Cat{name='Fluffy'} 보시다시피, 는 ArrayList
이미 반복자: iterator()
. 또한 반복자를 만들 때 작업할 개체의 클래스를 지정합니다( <Cat>
). 핵심은 반복자가 원래 작업을 쉽게 처리한다는 것입니다. 예를 들어 "Lionel Messi"라는 이름의 고양이를 제거합니다.
Iterator<Cat> catIterator = cats.iterator();// Create an iterator
while(catIterator.hasNext()) {// As long as there are elements in the list
Cat nextCat = catIterator.next();// Get the next element
if (nextCat.name.equals("Lionel Messi")) {
catIterator.remove();// Delete the cat with the specified name
}
}
System.out.println(cats);
출력: [Cat{name='Thomas'}, Cat{name='Behemoth'}, Cat{name='Fluffy'}]remove()
반복자의 메서드 에서 인덱스나 이름을 지정하지 않은 것을 눈치채셨을 것입니다. ! 반복자는 보이는 것보다 더 똑똑합니다. remove()
반복자가 반환한 마지막 요소를 제거합니다. 보시다시피, 우리가 원하는 대로 작동했습니다. :) 원칙적으로 에서 요소를 제거하는 방법에 대해 알아야 할 모든 것입니다 ArrayList
. 글쎄, 거의 모든 것. 다음 강의에서는 이 클래스 내부를 살펴보고 다양한 메서드 호출 중에 어떤 일이 발생하는지 살펴보겠습니다 :) 그때까지!