Java

ArrayList 클래스의 remove() 메서드 파해치기

OptimizerStart 2025. 4. 15. 18:01

Intro

자바의 정석 기초편의 ArrayList 실습을 진행하다, ArrayList 클래스의 오버로딩된 remove(Object o) 메서드를 직접 까보던 중에 'remove의 Object o 인자로 new Integer(1)을 줬는데, 어떻게 ArrayList에 저장된 new Integer(1) 객체와 주소 비교를 해서 지우는 거지?' 라는 코드를 보고 시작된 것이다. 사실 내용(1)을 비교하는 거라면 이해가 갔을 것이다.

 

 

하지만, 실제 ArrayList.java에 존재하는 'public boolean remove(Object o)' 는 Object 클래스의 equals()로 ArrayList에 저장된 Integer 객체와 인자로 받은 Integer 객체의 주소를 비교하고 있다. 

ArrayList.java 의 remove(Object o) 메서드 내부

문제 

[28번 라인] new Integer()로 생성된 주소와 ArrayList내 요소간 equals 주소 비교인데, 어떻게 값을 찾고 제거하나?

 

실습 코드

package ch11;

import java.util.ArrayList;
import java.util.Collections;

public class Ex11_1 {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList(10);
        list1.add(new Integer(5)); // 객체 생성
        list1.add(100);
        list1.add("1");
        list1.add(new Integer(4));
        list1.add(new Integer(3));
        list1.add(new Integer(0));
        list1.add(new Integer(1));
        // [5, 100, 4, 3, 0, 1]

        ArrayList list2 = new ArrayList(10);
        list2.add(1); list2.add(3); list2.add(new Integer(10));

        // 삭제할 인덱스 제공. 기존 배열 수정
        list1.remove(0); // public E remove(int index)
        print(list1, list2);
        // TODO 문제: new Integer()로 생성된 주소와 ArrayList내 요소간 equals 주소 비교인데, 어떻게 값을 찾고 제거하나?
        // 답:  Integer 클래스에서 equals가 오버라이딩되어 내용비교로 바뀜

        // public boolean remove(Object o)
        list1.remove(new Integer(1)); // 타입 1이 제거됨. String "1"제거 안됨.
        print(list1, list2);


    }

    static void print(ArrayList l1, ArrayList l2) {
        System.out.println("list1 : " + l1);
        System.out.println("list2 : " + l2);
        System.out.println();
    }
}

 

실행결과

실행 결과 Integer타입 1의 요소가 제거됨

 

 

 

예제로 실행했을 때는 아무 문제 없이 잘 지워졌다. 이해가 안되어 커널아카데미의 조교님께 질문을 구했다. 

 

 

 

뭐가 문제인가? 

list1의 구조도

 

실제로 list1에 저장된 배열기반 자료구조에서는 [그림2]처럼 주소가 저장되어있다.  remove()메서드 내부에서는 Object 타입이기 때문에 o.equlas()를 호출하는 o는 객체의 주소를 비교해서 삭제한다고 생각을 했다. 

 

즉, 인자 new Integer(1) 로 생성된 객체를 0x400이라할 때, 0x32 (Integer(1))와 주소 비교하는데 어떻게 값이 같다고 확인하고 삭제한지 이해가 되지 않았다. 

[28번 라인] list1.remove(new Integer(1));

0x400.equals(0x32)  => 0x400 == 0x32 => false 

 

Object 클래스의 equals() 메서드 내부

 

 

 

ArrayList.java 의 remove(Object o) 메서드 내부

해결책

조교님이 찾아본 결과 Integer Wrapper 클래스에서 equals()를 오버라이딩 하여 Integer(1)의 내용 비교를 한 것이라 하셨다.  remove(new Integer(1))을 주면 ArrayList 의 remove(Object o)에서 Object 조상 타입인 o 참조변수가 자손 타입인 ArrayList 인스턴스를 참조하고 있다.

 

ArrayList 에서 equals()을 [사진]과 같이 내용 비교로 오버라이딩 했기 때문에 참조변수로 Integer(1) 래퍼 클래스의 객체 주소가 전달 되었음에도, 내용 비교로 삭제가 가능해진 것이다.

 

Integer.java 의 오버라이딩된 equals() 메서드

필자가 놓친 지점은 Integer의 equals()를 보지 않았다는 것이다.  단순히 ArrayList의 remove(Object o) 메서드 내부에 'o.equals(es[i])' 부분만 보고 남궁성 강사님이 항상 강조하시던 '참조변수가 가리키는 실제 인스턴스가 무엇'인지 확인해보지 않았던 것이다. 그래서 equals()메서드가 Integer Wrapper 클래스에서 오버라이딩 되어, 코드 상으로는 Object 의 equals() 가 호출되지 않았고, 객체 주소비교가 되지 않았던 것이다. 

 

실습하다 ArrayList의 boolean contains(Object o) 코드도 내용 비교를 할 것 같다는 추측을했다. 실제 코드 보니 remove(Object o)와 동일하게 오버라이딩된 equals()로 비교를 해서 ArrayList에 인자로 준 객체가 존재하는지를 판단했다.  

 

 

소감 

- 수업 시간에 말씀해주신 규칙 및 주의 사항을 반복해서 봐야겠다는 생각이 들었다. 그 규칙들을 계속 생각했다면 위의 문제는 좀 더 빨리 해결했었을 것이다. 

- 매개변수에 실제로 어떤 인자가 가는지에 집중하되, 어떤 타입이 전달되는지도 집중하기. 강사님의 타입이 매우 중요하다는 걸 또 한번 느끼는 계기가 되었다. 

 

Thanks To

조교님 정말 감사합니다. 너무 궁금해서 2시간 정도 고민을 한 문제였거든요.