JavaRush /Blog Java /Random-VI /Xóa một phần tử khỏi ArrayList trong Java

Xóa một phần tử khỏi ArrayList trong Java

Xuất bản trong nhóm
Xin chào! Trong bài giảng trước, chúng ta đã làm quen với lớp ArrayList và cũng đã học cách thực hiện các thao tác phổ biến nhất với nó. Ngoài ra, chúng tôi đã nêu bật khá nhiều điểm khác biệt giữa ArrayList và mảng thông thường. Bây giờ hãy xem việc xóa một phần tử khỏi ArrayList. Chúng tôi đã nói rằng việc xóa các phần tử trong một mảng thông thường không thuận tiện lắm. Xóa một phần tử khỏi ArrayList - 1Vì chúng ta không thể xóa chính ô đó nên chúng ta chỉ có thể “zero” giá trị của nó:
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("Hippopotamus");
       cats[2] = new Cat("Philip Markovich");

       cats[1] = null;

       System.out.println(Arrays.toString(cats));
   }


@Override
   public String toString() {
       return "Cat{" +
               "name='" + name + '\'' +
               '}';
   }
}
Phần kết luận:

[Cat{name='Томас'}, null, Cat{name='Фorпп Маркович'}]
Nhưng khi reset lại, vẫn còn một “lỗ hổng” trong mảng. Chúng tôi không xóa ô mà chỉ xóa nội dung của nó. Hãy tưởng tượng điều gì sẽ xảy ra nếu chúng ta có một mảng gồm 50 con mèo, trong đó chúng ta đã xóa 17 con theo cách này. Chúng ta sẽ có một dãy có 17 lỗ và hãy chăm sóc chúng! Việc ghi nhớ thuộc lòng số ô trống nơi bạn có thể viết giá trị mới là không thực tế. Nếu mắc lỗi một lần và bạn sẽ ghi đè lên ô có tham chiếu mong muốn đến đối tượng. Tất nhiên, có một cơ hội để làm điều đó cẩn thận hơn một chút: sau khi xóa, hãy di chuyển các phần tử của mảng về đầu, sao cho “lỗ” ở cuối:
public static void main(String[] args) {

   Cat[] cats = new Cat[4];
   cats[0] = new Cat("Thomas");
   cats[1] = new Cat("Hippopotamus");
   cats[2] = new Cat("Philip Markovich");
   cats[3] = new Cat("Fluff");

   cats[1] = null;

   for (int i = 2; i < cats.length-1; i++) {
       //move the elements to the beginning so that the empty cell is at the end
       cats[i-1] = cats[i];
       cats[i] = null;
   }

   System.out.println(Arrays.toString(cats));
}
Phần kết luận:

[Cat{name='Томас'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}, null]
Bây giờ trông có vẻ ổn hơn nhưng khó có thể gọi đây là giải pháp ổn định. Ở mức tối thiểu, vì chúng ta sẽ phải viết mã này bằng tay mỗi khi xóa một phần tử khỏi mảng! Lựa chọn tồi. Bạn có thể đi theo cách khác và tạo một phương thức riêng:
public void deleteCat(Cat[] cats, int indexToDelete) {
   //...remove the cat by index and shift the elements
}
Nhưng điều này cũng ít được sử dụng: phương pháp này chỉ có thể hoạt động với các đối tượng Cat, nhưng không thể hoạt động với các đối tượng khác. Nghĩa là, nếu có thêm 100 lớp trong chương trình mà chúng ta muốn sử dụng mảng, chúng ta sẽ phải viết cùng một phương thức với cùng một logic trong mỗi lớp. Đây hoàn toàn là một thất bại -_- Nhưng trong lớp ArrayList vấn đề này đã được giải quyết thành công! Nó thực hiện một phương thức đặc biệt để loại bỏ các phần tử - remove():
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Hippopotamus");
   Cat philipp = new Cat("Philip Markovich");
   Cat pushok = new Cat("Fluff");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);
   System.out.println(cats.toString());

   cats.remove(1);

   System.out.println(cats.toString());
}
Chúng tôi đã chuyển chỉ mục của đối tượng cho phương thức và nó đã bị xóa (giống như trong một mảng). Phương pháp này remove()có hai tính năng. Thứ nhất , nó không để lại “lỗ hổng”. Nó đã triển khai logic dịch chuyển các phần tử khi loại bỏ một phần tử ở giữa mà trước đây chúng tôi đã viết bằng tay. Nhìn vào đầu ra của mã trước đó trong bảng điều khiển:

[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}]
[Cat{name='Томас'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}]
Chúng tôi loại bỏ một con mèo ở giữa và những con khác được di chuyển xung quanh để không còn khoảng trống. Thứ hai , nó có thể xóa một đối tượng không chỉ theo chỉ mục (như một mảng thông thường), mà còn bằng cách tham chiếu đến đối tượng đó :
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Hippopotamus");
   Cat philipp = new Cat("Philip Markovich");
   Cat pushok = new Cat("Fluff");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);
   System.out.println(cats.toString());

   cats.remove(philipp);

   System.out.println(cats.toString());
}
Phần kết luận:

[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}]
[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Пушок'}]
Điều này có thể rất thuận tiện nếu bạn không muốn luôn giữ chỉ mục của đối tượng mong muốn trong đầu. Chúng tôi dường như đã sắp xếp việc xóa thông thường. Bây giờ hãy tưởng tượng tình huống này: chúng ta muốn lặp qua danh sách các phần tử và loại bỏ một con mèo có tên nhất định. Để làm điều này, chúng tôi sử dụng toán tử vòng lặp đặc biệt for- for each. Bạn có thể tìm hiểu thêm về nó trong bài giảng này .
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Hippopotamus");
   Cat philipp = new Cat("Philip Markovich");
   Cat pushok = new Cat("Fluff");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(philipp);
   cats.add(pushok);

   for (Cat cat: cats) {

       if (cat.name.equals("Hippopotamus")) {
           cats.remove(cat);
       }
   }

   System.out.println(cats);
}
Mã có vẻ trông khá logic. Tuy nhiên, kết quả có thể làm bạn ngạc nhiên:

Exception in thread "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)
Có một loại lỗi nào đó, không rõ vì sao đột nhiên xuất hiện. Có một số sắc thái trong quá trình này cần được giải quyết. Một quy tắc chung mà bạn cần nhớ: Bạn không thể lặp qua một bộ sưu tập và thay đổi các thành phần của nó cùng một lúc. Vâng, vâng, chính xác là một sự thay đổi chứ không chỉ là việc xóa bỏ. Nếu bạn thử mã của chúng tôi để thay thế việc loại bỏ những con mèo bằng việc chèn những con mèo mới, kết quả sẽ giống nhau:
for (Cat cat: cats) {

   cats.add(new Cat("Salem Saberhegen"));
}

System.out.println(cats);

Exception in thread "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)
Chúng tôi đã thay đổi thao tác này sang thao tác khác nhưng kết quả không thay đổi: cùng một lỗi ConcurrentModificationException. Nó xảy ra chính xác khi chúng ta cố gắng phá vỡ một quy tắc và thay đổi danh sách trong khi lặp lại nó. Trong Java, để loại bỏ các phần tử trong quá trình lặp, bạn cần sử dụng một đối tượng đặc biệt - một iterator (class Iterator). Lớp này Iteratorchịu trách nhiệm duyệt qua danh sách các phần tử một cách an toàn. Nó khá đơn giản vì chỉ có 3 phương pháp:
  • hasNext()- trả về truetùy falsethuộc vào việc có phần tử tiếp theo trong danh sách hay không hoặc liệu chúng ta đã đạt đến phần tử cuối cùng hay chưa.
  • next()- trả về phần tử tiếp theo của danh sách
  • remove()- xóa một phần tử khỏi danh sách
Như bạn có thể thấy, iterator được “điều chỉnh” theo đúng nghĩa đen theo nhu cầu của chúng ta và không có gì phức tạp về nó. Ví dụ: chúng tôi muốn kiểm tra xem danh sách của chúng tôi có chứa phần tử sau hay không và nếu có, hãy in nó ra bảng điều khiển:
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 next element
   System.out.println(nextCat);// print it to the console
}
Phần kết luận:

Cat{name='Томас'}
Cat{name='Бегемот'}
Cat{name='Фorпп Маркович'}
Cat{name='Пушок'}
Như bạn có thể thấy, lớp này ArrayListđã triển khai một phương thức đặc biệt để tạo một trình vòng lặp - iterator(). Ngoài ra, hãy lưu ý rằng khi tạo một trình vòng lặp, chúng ta chỉ định lớp đối tượng mà nó sẽ hoạt động ( <Cat>). Cuối cùng, chúng ta có thể dễ dàng giải quyết vấn đề ban đầu bằng cách sử dụng trình vòng lặp. Ví dụ: hãy xóa một con mèo có tên “Philip Markovich”:
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 next element
   if (nextCat.name.equals("Philip Markovich")) {
       catIterator.remove();//delete the cat with the desired name
   }
}

System.out.println(cats);
Phần kết luận:

[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Пушок'}]
Bạn có thể nhận thấy rằng chúng tôi không chỉ định chỉ mục phần tử hoặc tên biến tham chiếu trong phương thức iterator remove()! Trình vòng lặp thông minh hơn vẻ ngoài của nó: phương thức này remove()loại bỏ phần tử cuối cùng được trình vòng lặp trả về. Như bạn có thể thấy, nó hoạt động chính xác khi cần thiết :) Về cơ bản đó là mọi thứ bạn cần biết về cách xóa các phần tử khỏi ArrayList. Chính xác hơn - hầu hết mọi thứ. Trong bài giảng tiếp theo, chúng ta sẽ xem xét “bên trong” của lớp học này và xem điều gì xảy ra ở đó trong quá trình vận hành :) Hẹn gặp lại!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION