Java에서 array를 List로 바꾸려면 어떻게 해야될까?

for문을 쓰는것이 당연하지만 왠지 복잡해 보이고..

Arrays.asList가 List를 리턴한다. 간단해 보이므로 이걸쓰자.

 

하지만 잘 살펴보지 않으면 여기에 함정이 있다.

다음 코드를 살펴보자.

 

Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
List<Integer> list = Arrays.asList(arr);
list.add(6);
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractList.add(AbstractList.java:153)
	at java.base/java.util.AbstractList.add(AbstractList.java:111)
	at ArrayAsList.main(ArrayAsList.java:10)

흠..List인데 add가 안된다?

 

Arrays.asList 구현을 보자.

public static <T> List<T> asList(T... a) {
	return new ArrayList<>(a);
}

ArrayList리턴인데 머지..?

 

하지만 이건 java.util.ArrayList 가 아니다.(머여그럼)

바로 밑에 코드를 보면 Arrays 안에 private static 으로 구현되어있다. 생성자로 array를 받고 전역변수로 array로 관리한다.

 

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }
        ...
 }

 

그리고 add 메소드가 구현이 안되어있다. AbstractList 로 따라가다 보면 다음과 같이 UnsupportedOperationException을 발생시키는걸 볼 수 있다.

public void add(int index, E element) {
	throw new UnsupportedOperationException();
}

Arrays.asList 에서 나오는 결과로는 동적 List를 만들 수 없다. 

 

동적 List로 만들고 싶다면 오브젝트를 생성하여 반복문으로 새로 값을 할당해야 한다.

'Java' 카테고리의 다른 글

[Java] Wrapper 클래스 Cache  (0) 2020.06.18
[Java] Java에서 Map 관련 Iterate(반복문) 방법  (0) 2016.11.10

 

 

 

다음 코드를 보자.

Integer a = 1000;
Integer b = 1000;

System.out.println(a==b);

출력 : false

 

매우 당연하다. a와 b는 값이 같아도 다른 오브젝트이기 때문이다.

 

그럼 다음 코드를 보자.

Integer a = 127;    
Integer b = 127;

System.out.println(a==b);

출력 : true

 

잉? 당연히 출력이 false일 것이라 예상했지만 아니다.

Java의 일부 Wrapper 클래스들은 내부에서 캐시값을 가지고 있다.

 

Integer 클래스 내부 코드를 살펴보자.

private static class IntegerCache {
	static final int low = -128;
	static final int high;
	static final Integer cache[];

	static {
	// high value may be configured by property
		int h = 127;
		String integerCacheHighPropValue =
		VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
		if (integerCacheHighPropValue != null) {
			try {
				int i = parseInt(integerCacheHighPropValue);
				i = Math.max(i, 127);
				// Maximum array size is Integer.MAX_VALUE
				h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
			} catch( NumberFormatException nfe) {
				// If the property cannot be parsed into an int, ignore it.
			}
		}
		high = h;

		cache = new Integer[(high - low) + 1];
		int j = low;
		for(int k = 0; k < cache.length; k++)
			cache[k] = new Integer(j++);

		// range [-128, 127] must be interned (JLS7 5.1.7)
		assert IntegerCache.high >= 127;
	}

	private IntegerCache() {}
}

private statc으로 IntegerCache 클래스를 가지고 있다.

-128 ~ 127 까지의 오브젝트를 array로 생성해 놓고 있다.

 

high값은 "java.lang.Integer.IntegerCache.high" 세팅으로 수정이 가능하다.

 

그리고 오브젝트를 생성할 때 사용한다.

public static Integer valueOf(int i) {
	if (i >= IntegerCache.low && i <= IntegerCache.high)
	return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}

 

그러면 캐싱을 왜 사용할까?

 

Java Language Specification(JLS)의 Boxing Conversion 에서 다음과 같이 나와있다.

 

If the value p being boxed is an integer literal of type int between -128 and 127 inclusive (§3.10.1), or the boolean literal true or false (§3.10.3), or a character literal between '\u0000' and '\u007f' inclusive (§3.10.4), then let a and b be the results of any two boxing conversions of p. It is always the case that a == b.

위의 문구에서 숫자 -128~127 까지의 reference가 동일하다고 얘기하고 있다. (boolean 포함, character도 0~127)

 

Ideally, boxing a primitive value would always yield an identical reference. In practice, this may not be feasible using existing implementation techniques. The rule above is a pragmatic compromise, requiring that certain common values always be boxed into indistinguishable objects. The implementation may cache these, lazily or eagerly. For other values, the rule disallows any assumptions about the identity of the boxed values on the programmer's part. This allows (but does not require) sharing of some or all of these references. Notice that integer literals of type long are allowed, but not required, to be shared.
This ensures that in most common cases, the behavior will be the desired one, without imposing an undue performance penalty, especially on small devices. Less memory-limited implementations might, for example, cache all char and short values, as well as int and long values in the range of -32K to +32K.

 

간단히 요약하면 이상적으로는 모든 primitive 값이 boxing이 되면 같은 reference를 가져야 하지만 실제적으로 이것을 모든 부분에 구현하는것은 어렵기 때문에 그래서 일단 일정 범위에 대해서만 java에서 적용하고 실제 구현에 있어서 프로그래머가 알아서 하라고 하는것이다.

'Java' 카테고리의 다른 글

Arrays.asList는 동적 List가 아니다.  (0) 2020.06.19
[Java] Java에서 Map 관련 Iterate(반복문) 방법  (0) 2016.11.10





Java에서 Map 관련하여 반복문을 사용하는 경우가 여러가지가 있다. 가장 많이 쓰이는 몇가지를 살펴보고 장단점을 알아보도록한다. Java의 모든 map들은 Map interface를 사용하므로 다음 경우들은 모든 map에 대하여 사용 가능하다.

예 : HashMap, TreeMap, LinkedMap, Hashtable 등등.. 




1. Entry 에 For-Each Loop 사용

  가장 일반적이고 거의 모든 경우에 사용된다. 반복문 안에 key값과 value값이 전부 필요할때 사용한다.


1
2
3
4
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
cs


  참고 : For-Each Loop는 Java 5이상에서 사용가능하다. 또한 NullPointerExecption을 발생 시킬 수 있으므로 null 체크를 하도록 하자.




2. Key Value 에 For-Each Loop사용

  entry 대신 key값이나 value 값만 필요할 때 사용한다.


1
2
3
4
5
6
7
8
9
10
11
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
 
//iterating over keys only
for (Integer key : map.keySet()) {
    System.out.println("Key = " + key);
}
 
//iterating over values only
for (Integer value : map.values()) {
    System.out.println("Value = " + value);
}
cs


  entrySet을 사용할 때보다 약 10%정도 빠르다.




3. Iterator 사용


- Generic 사용


1
2
3
4
5
6
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Map.Entry<Integer, Integer> entry = entries.next();
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
cs



- Generic 미사용


1
2
3
4
5
6
7
8
Map map = new HashMap();
Iterator entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Map.Entry entry = (Map.Entry) entries.next();
    Integer key = (Integer)entry.getKey();
    Integer value = (Integer)entry.getValue();
    System.out.println("Key = " + key + ", Value = " + value);
}
cs

 

 keySet이나 values 에도 똑같이 적용가능하다. 이 방법은 오래된 방법이지만 Java 버전이 낮은 경우에 유일하게 사용가능한 방법이다. 또 한가지 중요한 장점은 반복문을 사용하는 도중에 iterator.remove()를 통해 항목들을 삭제할 수 있는 유일한 방법이다. 만약 삭제 작업을 For-Each 구문에서 사용한다면 "unpredictable results"를 얻게된다(javadoc, Map은 순서를 보장하지 않기 때문에). 성능 측면에서 For-Each와 비슷하다.





4. Key값으로 Value를 찾는 반복문


  코드상으로는 간단해 보이지만 1번 방법에 비해 비효율적이고 성능면에서 안좋은 방법이다. key값을 이용해서 value를 찾는 과정에서 시간이 많이 소모된다.(1번 방법보다 약 20%~200% 성능저하가 있음)


1
2
3
4
5
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (Integer key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println("Key = " + key + ", Value = " + value);
}
cs



'Java' 카테고리의 다른 글

Arrays.asList는 동적 List가 아니다.  (0) 2020.06.19
[Java] Wrapper 클래스 Cache  (0) 2020.06.18

+ Recent posts