자바 프로그래머가 자주 실수 하는 10가지 - 1

Tags: Javamistake

August 31, 2015

원문: http://www.programcreek.com/2014/05/top-10-mistakes-java-developers-make/

다음글: 자바 프로그래머가 자주 실수하는 10가지 - 2

#1. 일반 배열을 ArrayList로 변환하기

보통 많은 개발자가 다음과 같이 일반 배열을 ArrayList로 변환한다:

List<String> list = Arrays.asList(arr);

Arrays.asList()Arrays의 private 정적 클래스인 ArrayList를 리턴한다. java.util.ArrayList 클래스와는 다른 클래스이다. java.util.Arrays.ArrayList 클래스는 set(), get(), contains() 매서드를 가지고 있지만 원소를 추가하는 매서드는 가지고 있지 않기 때문에 사이즈를 바꿀 수 없다. 진짜 ArrayList를 받기 위해서는 다음과 같이 변환하면 된다:

ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));

ArrayList의 생성자는 java.util.Arrays.ArrayList의 상위(super) 클래스인 Collection type도 받아들일 수 있다.

#2. 일반 배열에 특정 값이 들어있는지 확인하기

보통 이렇게 많이 확인한다:

Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);

이 코드는 동작하지만 list를 set으로 변환하는 것은 시간도 더 걸릴뿐더러 사실 할 필요가 없다. 대신에 다음과 같이 처리할 수 있다:

Arrays.asList(arr).contains(targetValue);

// OR

for(String s: arr){
	if(s.equals(targetValue))
		return true;
}
return false;

첫번째 솔루션이 훨씬 읽기 편하다.

#3. Loop에서 list의 원소를 제거하기

다음과 같이 Loop 안에서 원소를 제거한다고 하자:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
for (int i = 0; i < list.size(); i++) {
	list.remove(i);
}
System.out.println(list);

// output
// [b, d]

위의 코드에는 아주 심각한 문제가 있다. 원소가 삭제될 때, list의 사이즈가 줄어들면서 다른 원소들의 index도 바뀌어 버린다. 그래서 만약 loop 내에서 다수의 원소를 index를 사용해 삭제한다면 생각한대로 동작하지 않을 것이다.

아마 반복자(iterator)를 사용하는 것이 바른 방법이고, foreach loop가 내부적으로 반복자를 사용한다는 것을 알고 있을지도 모른다. 하지만 사실 다음의 foreach loop에서도 올바르게 동작하지 않는다:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
 
for (String s : list) {
	if (s.equals("a"))
		list.remove(s);
}

위의 코드는 ConcurrentModificationException을 발생시킬 것이다.

다음의 코드는 제대로 동작한다:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
	String s = iter.next();
 
	if (s.equals("a")) {
		iter.remove();
	}
}

반드시 .remove()전에 .next()가 호출되어야 한다. 만약 foreach loop안에서 원소가 삭제된 뒤에 .next()가 호출된다면 컴파일러는 ConcurrentModificationException을 발생시킬 것이다. ArrayList.iterator()의 코드가 깊이 이해하는 데 도움이 될 것이다.

#4. Hashtable vs HashMap

알고리즘적으로 봤을 때 Hashtable은 자료구조 이름이지만 Java에서의 이름은 사실 HashMap이다. HashtableHashMap과 가장 다른 점은 바로 동기화(synchronized)라는 것이다. 그래서 대부분 Hashtable보다는 HashMap을 사용하는 것이 좋다.

#5. Collection의 Raw Type 사용

Java에서는, raw typeunbounded wildcard type이 쉽게 섞여서 함께 사용된다. Set을 예로 들어보면, Set은 raw type이고 Set<?>은 unbounded wildcard type이다.

다음과 같은 raw type List를 파라미터로 사용하는 코드가 있다고 하자:

public static void add(List list, Object o){
	list.add(o);
}
public static void main(String[] args){
	List<String> list = new ArrayList<String>();
	add(list, 10);
	String s = list.get(0);
}

// Exception will be thrown..
// Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
// 	at ...

raw type collection을 사용하는 것은 타입 체크를 건너뛰기 때문에 안전하지 않다. Set, Set<?>, Set<Object> 사이에는 아주 큰 차이가 있다. 다음 글들을 읽는 걸 추천한다.