sort는 기본 정렬 조건이 오름차순으로 되어 있기 때문에 특별한 코드 없이 오름차순으로 정렬이 됩니다.
2. 내림차순 정렬을 알아보겠습니다.
Integer[] arr2 = {11, 5, 30, 40, 35, 80, 60};
// Collections 메소드 사용
Arrays.sort(arr2, Collections.reverseOrder());
// 직접 구현도 가능
Arrays.sort(arr2, new Comparator<Integer>() {
@Override
public int compare(Integer i1, Integer i2) {
return i2 - i1;
}
});
for(int i : arr2) {
System.out.println(i);
}
내림차순 정렬방법도 오름차순과 동일하게 진행됩니다. 하지만 인자로 내림차순이 될 수 있도록 구현된 Comparator를 넘겨 주셔야 됩니다. (Comparator가 무엇인지는 잠시 후에 살펴보겠습니다.) Comparator를 구할 때는 Collections메서드를 사용하시거나 직접 구현을 해주시면 되겠습니다.
3. Comparable 와 Comparator는 무엇일까?
위의 내용으로 배열의 정렬은 하실 수 있습니다. 이제는 정렬을 조금 더 잘 사용할 수 있도록 Comparable과 Comparator를 알아보도록 하겠습니다. 이 둘을 배우고 나면 객체도 정렬을 하실 수 있겠습니다.
Comparable과 Comparator는 정렬을 위한 인터페이스입니다. 각각의 인터페이스를 구현해서 "구현한 로직대로 정렬을 하겠다"입니다. 위에서 Arrays.sort() 함수를 통해서 정렬을 할 수 있었던 이유는 이미 Comparator가 내부적으로 구현이 되어 있기 때문입니다. 이렇게만 보면 이해가 잘 안 갑니다. 아래의 내용을 살펴보겠습니다.
4. Comparable
Comparable 인터페이스에는 compareTo() 추상 메소드 하나만 존재합니다.
주어진 객체(T o) 보다 작으면 음수, 같으면 0, 크면 양수를 리턴합니다.
이게 무슨말인지 소스 코드를 보겠습니다.
public class NameCard implements Comparable<NameCard> {
public String name;
public int age;
public NameCard(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(NameCard o) {
if(this.age < o.age) {
return -1;
} else if(this.age == o.age) {
return 0;
} else {
return 1;
}
}
}
NameCard라는 클래스에 Comparable를 구현하였습니다. NameCard를 컬랙션에 줄줄이 넣고 정렬을 한다면 어떻게 하시겠습니까? 정렬의 기준은 어떻게 될까요? 정렬의 기준을 이름으로 할까요? 아니면 나이가 될까요? 정렬의 기준이 없습니다. 그렇기 때문에 정렬의 기준을 정하기 위해서 Comparable를 구현하는겁니다. 위의 코드를 볼 때 정렬의 기준은 나이(age)가 되겠습니다. 테스트를 하도록 하겠습니다.
List와 같은 컬랙션에서 요소들을 순차적으로 처리하기 위해서 iterator 반복자가 존재합니다.
예제를 보고 간단히 살펴보겠습니다.
public class IteratorExample {
public static void main(String[] args) {
List<String> list = Arrays.asList("자바", "파이썬", "스프링", "장고");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
String name = iterator.next();
System.out.println(name);
}
for (String s : list) {
System.out.println(s);
}
}
}
자바 / 파이썬 / 스프링 / 장고 4개의 리스트 요소를 포함하고 있는 리스트를 생성했습니다.
그리고 iterator() 함수를 통해서 Iterator 반복자를 갖고 옵니다.
hasnext() 함수를 통해서 항목이 있는지 확인을 하고 next() 함수를 통해서 요소를 하나씩 하나씩 갖고 오면 되겠습니다.
아래의 for문은 iterator를 사용하지 않고도 for문을 통해서 요소들을 전체 출력을 할 수 있습니다.
다음으로는 iterator를 활용한 예제를 보도록 하겠습니다.
public class Iterators {
public static class ListIterator<T> implements Iterator<T> {
private final Iterator<Iterator<T>> listIterator;
private Iterator<T> currentIterator;
public ListIterator(List<Iterator<T>> iterators) {
this.listIterator = iterators.iterator();
this.currentIterator = listIterator.next();
}
@Override
public boolean hasNext() {
if(!currentIterator.hasNext()) {
if (!listIterator.hasNext()) {
return false;
}
currentIterator = listIterator.next();
hasNext();
}
return true;
}
@Override
public T next() {
hasNext();
return currentIterator.next();
}
@Override
public void remove() {
hasNext();
currentIterator.remove();
}
}
public static <T> Iterator<T> singleIterator(final List<Iterator<T>> iteratorList) {
return new ListIterator<>(iteratorList);
}
}
1. singleIterator부터 살펴보겠습니다.
singleIterator는 정적 메서드이며 Iterator<T> 타입을 리턴합니다.
위의 ListIterator 클래스는 Iterator를 구현하였으니 Iterator 타입이겠죠?
singleIterator를 호출하면 ListIterator 객체가 리턴됩니다.
파라미터로는 Iterator<T>으로 구성된 리스트를 인자로 받습니다. 즉, 여러 (리스트로 만들어지고 난 후에) Iterator형의 요소들이 존재합니다.
2. ListIterator 클래스를 살펴보겠습니다.
ListIterator 생성자를 보면 singleIterator 메서드로부터 받은 Iterator요소가 담겨 있는 리스트를 넘겨받아서, 리스트의 Iterator를 저장합니다.
아하, 그러니까 리스트 안에는 Iterator타입의 요소들이 담겨줘 있고, 요소가 아닌 이 리스트의 Iterator를 다시 저장하는 것입니다.
currentIterator에는 첫 번째 요소를 저장합니다.
3. hasNext()를 살펴보겠습니다.
currentIterator(현재 이터레이터)의 다음 데이터가 없다면 listIterator.next()를 통해서 다음 요소를 갖고 옵니다.
listIterator.hasNext()를 호출할 때 리스트의 다음 요소가 없다면, 더 이상 요소가 존재하지 않습니다.
currentIterator 다음 데이터가 존재한다면 True가 리턴됩니다.
테스트 코드를 살펴보겠습니다.
public class IteratorsTest {
@Test
public void multipleIterators() {
final Iterator<Integer> a = Arrays.asList(1, 2, 3, 4, 5).iterator();
final Iterator<Integer> b = Arrays.asList(6).iterator();
final Iterator<Integer> c = new ArrayList<Integer>().iterator();
final Iterator<Integer> d = new ArrayList<Integer>().iterator();
final Iterator<Integer> e = Arrays.asList(7, 8, 9).iterator();
final Iterator<Integer> singleIterator = Iterators.singleIterator(Arrays.asList(a, b, c, d, e));
assertTrue(singleIterator.hasNext());
for (Integer i = 1; i < 10; i++) {
assertEquals(i, singleIterator.next());
}
assertFalse(singleIterator.hasNext());
}
}
a, b, c, d, e 각각의 Iterator 타입의 변수를 만들었습니다. 그리고 이 각각의 변수들을 List 요소에 넣고 singleIterator 메서드를 호출했습니다.
singleIterator 변수에 Iterator가 리턴이 되고, 이를 활용하여 각각의 요소들 별로 데이터를 추출하는 예제입니다.
WeakReference는 클래스입니다 :) 그리고 GC(Garbage Collector)와 밀접한 연관성이 있습니다.
GC는 개발자의 영역이 아닌데 WeakReference을 사용함으로써 GC에게 어느정도 개입을 할 수가 있습니다.
GC는 불필요한 객체(참조하고 있지 않는)들을 힙 메모리에서 제거를 해줍니다. 특정 객체가 Weak Reference(약한 참조) 상태가 되면 GC는 이 객체를 제거를 합니다. 빠르게 객체를 제거함으로써 잠깐 사용하고 제거를 할 수 있는 것이죠.
new를 사용하여 객체를 만들때는 강한 Reference가 적용이 되는데, WeakReference를 사용하여 객체를 만들 때는 약한 Reference가 적용이 됩니다.
예제를 살펴보도록 하겠습니다.
@Test
public void weakReferenceStackManipulation() {
final WeakReferenceStack<ValueContainer> stack = new WeakReferenceStack<>();
final ValueContainer expected = new ValueContainer("Value for the stack");
stack.push(new ValueContainer("Value for the stack"));
ValueContainer peekedValue = stack.peek();
assertEquals(expected, peekedValue);
assertEquals(expected, stack.peek());
peekedValue = null;
System.gc();
assertNull("Fail", stack.peek());
}
위의 예제는 stack을 WeakReference형으로 선언하였습니다. WeakReference은 ValueContainer을 래퍼해줍니다.
ValueContainer 객체를 생성하고, 이 객체를 stack에 집어넣었습니다.
그리고 두 값을 비교하면 같은 값이 됩니다.
이때 peekedValue는 WeakReference 형으로 선언된 값을 참조하기 때문에, peekedValue 값에 null을 대입하면 WeakReference 형으로 선언된 객체는 GC가 동작될 때 제거됩니다. 그렇기 때문에 stack 값을 갖고 오면 stack에 값이 있을 것으로 생각이 되지만 GC에 의해서 이미 제거되어 null이 됩니다.
GC가 동작하지 않았더라면(System.gc() 주석 처리), GC가 동작하기 전까지는 null값이 아니기 때문에 마지막 테스트는 실패하게 됩니다.
LRU 캐시처럼 객체를 짧게 사용하는 곳에서는 WeakReference가 유용할 수 있겠습니다.