MyArrayList는 Object를 입력받기 때문에 아무 데이터나 입력할 수 있고, 또 결과로 Object를 반환함package collection.array;
public class MyArrayListV3BadMain {
public static void main(String[] args) {
MyArrayListV3 numberList = new MyArrayListV3();
// 숫자만 입력 하기를 기대
numberList.add(1);
numberList.add(2);
numberList.add("문자3"); //문자를 입력
System.out.println(numberList);
// Object를 반환하므로 다운 캐스팅 필요
Integer num1 = (Integer) numberList.get(0);
Integer num2 = (Integer) numberList.get(1);
// ClassCastException 발생, 문자를 Integer로 캐스팅
Integer num3 = (Integer) numberList.get(2);
}
}
numberList에는 숫자만 입력하기를 기대했지만, Object를 받아서 저장하기 때문에 자바의 모든 타입을 다 저장할 수 있음
값을 꺼낼 때 Object를 반환하기 때문에, 원래 입력한 타입으로 받으려면 다운 캐스팅을 해야 함
- 제네릭을 도입하면 타입 안전성을 확보하면서 이런 문제를 한 번에 해결할 수 있음
- 제네릭은 자료를 보관하는 자료 구조에 가장 어울림
package collection.array;
import java.util.Arrays;
public class MyArrayListV4<E> {
private static final int DEFAULT_CAPACITY = 5;
private Object[] elementData;
private int size = 0;
public MyArrayListV4() {
elementData = new Object[DEFAULT_CAPACITY];
}
public MyArrayListV4(int initialCapacity) {
elementData = new Object[initialCapacity];
}
public int size() {
return size;
}
public void add(E e) {
if (size == elementData.length) {
grow();
}
elementData[size] = e;
size++;
}
public void add(int index, E e) {
if (size == elementData.length) {
grow();
}
shiftRightFrom(index);
elementData[index] = e;
size++;
}
//요소의 마지막부터 index까지 오른쪽으로 밀기
private void shiftRightFrom(int index) {
for (int i = size; i > index; i--) {
elementData[i] = elementData[i - 1];
}
}
@SuppressWarnings("unchecked")
public E get(int index) {
return (E) elementData[index];
}
public E set(int index, E element) {
E oldValue = get(index);
elementData[index] = element;
return oldValue;
}
public E remove(int index) {
E oldValue = get(index);
shiftLeftFrom(index);
size--;
elementData[size] = null;
return oldValue;
}
//요소의 index부터 마지막까지 왼쪽으로 밀기
private void shiftLeftFrom(int index) {
for (int i = index; i < size - 1; i++) {
elementData[i] = elementData[i + 1];
}
}
public int indexOf(E o) {
for (int i = 0; i < size; i++) {
if (o.equals(elementData[i])) {
return i;
}
}
return -1;
}
private void grow() {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity * 2;
elementData = Arrays.copyOf(elementData, newCapacity);
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOf(elementData, size)) + " size=" +
size + ", capacity=" + elementData.length;
}
}
MyArrayListV4<E>로 제네릭 타입을 선언함Object로 입력받고 출력했던 곳에 타입 매개변수 E로 변경함Object[] elementData는 그대로 유지함package collection.array;
public class MyArrayListV4Main {
public static void main(String[] args) {
MyArrayListV4<String> stringList = new MyArrayListV4<>();
stringList.add("a");
stringList.add("b");
stringList.add("c");
String string = stringList.get(0);
System.out.println("string = " + string);
MyArrayListV4<Integer> intList = new MyArrayListV4<>();
intList.add(1);
intList.add(2);
intList.add(3);
Integer integer = intList.get(0);
System.out.println("integer = " + integer);
}
}
실행결과
string = a
integer = 1
이제 stringList에는 String 문자열만 보관하고 조회하고, intList에는 Integer 숫자만 보관하고 조회할 수 있음
다른 타입의 값을 저장하면 컴파일 오류가 발생함
추가로 값을 조회할 때도 위험한 다운 캐스팅 없이 지정한 타입으로 안전하게 조회할 수 있음
제네릭을 사용한 덕분에 타입 인자로 지정한 타입으로만 안전하게 데이털르 저장하고, 조회할 수 있게 됨
제네릭의 도움으로 타입 안전성이 높은 자료 구조를 만들 수 있었음
Object[] elementData을 그대로 사용하는 이유제네릭은 런타임에 이레이저에 의해 타입 정보가 사라짐
new E[DEFAULT_CAPACITY]대신에 다음과 같이 모든 데이터를 담을 수 있는 Object를 그대로 사용해야 함
new Object[DEFAULT_CAPACITY]Object[]을 생성해서 사용해도 문제가 없는지 확인new MyArrayListV4<String>을 사용한 경우 E가 다음과 같이 처리됨Object[] elementData;
void add(E e) {
elementData[size] = e;
...
}
E get(int index) {
return (E) elementData[index];
}
Object[] elementData;
void add(String e) {
elementData[size] = e;
...
}
String get(int index) {
return (String) elementData[index];
}
elementData[]에 데이터를 보관하는 add(E e) 메서드를 보면, E 타입으로 데이터를 입력함elementData[]에 데이터를 조회하는 get() 메서드를 보면, 보관할 때와 같은 E 타입으로 데이터를 다운 캐스팅해서 반환함E 타입으로 보관됨get()으로 배열에서 데이터를 꺼낼 때 (E)로 다운 캐스팅해도 보관한 E 타입으로 다운 캐스팅 하기 때문에 아무런 문제가 되지 않음MyArrayListV4를 생성할 때 타입 매개변수 E를 String으로 지정했다면 elementData에는 항상 String이 저장됨
add(String e)에서 배열의 모든 데이터는 String 타입으로 보관됨
get()에서 데이터를 꺼낼 때 항상 (String)으로 다운 캐스팅 함
저장한 String 타입으로 다운캐스팅 하기 때문에 아무런 문제가 되지 않음
Object는 모든 데이터를 담을 수 있기 때문에 데이터를 담는데는 아무런 문제가 없음
Object 타입을 지정한 타입 매개변수로 다운캐스팅 해줌public E get(int index) {
return (E) elementData[index];
}
elemetData[index]로 조회한 결과는 Object임(E) Object가 됨add(E e)를 통해 Object elementData[]에 보관한 모든 데이터는 E타입이라는 것이 확실하기 때문E가 String인 경우 (String) Object가 됨생성자에는 제네릭의 타입 매개변수를 사용할 수 없는 한계가 있음
따라서 배열을 생성할 때 대안으로 Object 배열을 사용해야 함
하지만 제네릭이 리스트의 데이터를 입력 받고 반환하는 곳의 타입을 고정해줌
따라서 고정된 타입으로 Object 배열에 데이터를 보관하고, 또 데이터를 꺼낼 때도 같은 고정된 타입으로 안전하게 다운 캐스팅 할 수 있음
MyArrayList의 단점O(n)으로 성능이 좋지 않음O(1)O(n)O(1)O(n)O(1)O(n)LinkedList에 대한 강의 진행할 예정