java1.5 Iterator, Enumeration, Comparable, Comparator, Generic, WildCard!

Iterator (반복자)

2중 배열처럼 Iterator를 사용해서 ArrayList안에 ArrayList출력하기

int totalStdNum = 0;
Iterator<ArrayList<Student>> gir = gradList.iterator();
int classNum = 1;
while (gir.hasNext()) {
    System.out.println(classNum++ + "반");
    ArrayList<Student> arrayList = gir.next();
    Iterator<Student> cir = arrayList.iterator();
    while (cir.hasNext()) {
        Student student = cir.next();
        System.out.println(student);
    }
    totalStdNum += arrayList.size();
    System.out.println();
}        
System.out.println("총학생수:" + totalStdNum);

Iterator를 쓰면 다중 쓰레드환경에서 충돌 발생할때 예외를 발생하기 때문에 나중에 동기화처리하기 편하다.

ListIterator 반복자

Iterator 반복자보다 향상된 버전으로 뒤로가는것이 가능하다. ListIterator는 양방향으로 이동가능하다.

// ListIterator 원형
public interface ListIterator<E>
extends Iterator<E>

ListIterator는 hasNext()뿐 아니라 hasPrevious() 도 있다.
previous() 메서드로 객체를 얻어와 관리한다.

ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.add("F");
System.out.println(list.size()); // 6

ListIterator<String> lir = list.listIterator();
while (lir.hasNext()) {
    String s = lir.next();
    System.out.println(s);
} //ABCDEF출력

while (lir.hasPrevious()) {
    String s = lir.previous();
    System.out.println(s);
} //FEDCBA출력

Enumeration (열거자)

열거자가 먼저나오고 반복자가 후에 나왔다.

열거자(Enumeration)와 반복자(Iterator)의 차이는 참조의 복사본을 만드는지 차이.
열거자는 3개의 요소를 출력할때 3개 요소 모두 임시 데이터 공간에 참조데이터를 저장해놓고 하나씩 꺼내 출력한다.
따라서 실시간의 데이터가 변해도 열거자는 복사해놓은 참조 관리하기 때문에 변한지 모른다!

Vector<BoardDTO> list = new Vector<>();
list.addElement(new BoardDTO("KO", "조퇴", "몸이아파서"));
list.addElement(new BoardDTO("JO", "지각예정", "실업급여"));
list.addElement(new BoardDTO("MO", "지각", "차가막혀서"));
System.out.println(list);
Enumeration<BoardDTO> en = list.elements();
while (en.hasMoreElements()) { //하나씩 꺼내오는건 Iterator랑 똑같다.
    BoardDTO bto = en.nextElement();
    System.out.println(bto);
}

에로 A라는 스레드가 1번째 요소를 출력하고 2번째 요소를 출력하려 한다.
이때 B라는 스레드가 2번째 게시글을 삭제했을때 A는 문제없이 출력 가능하다. 왜냐면 열거자 생성시 모든 값을 복사해서 사용하기 때문.

반면 반복자는 실시간으로 데이터에 접근하기때문에 변화가 일어나면 예외가 발생한다.

제네릭(Generics)

jdk1.5 부터 추가되었다.

다양한 타입의 객체들을 다루는 메서드, 컬렉션 클레스에 컴파일 시에 타입 체크(Complie Time Type Check)를 해주는 기능.

객체의 타입 안정성, 형변환 번거로움을 줄인다.

제네릭이 없을때 클래스 안에 여러 타입의 멤버를 사용하려면 템플릿 클래스를 써야했다.

class Box {
    Object content
    public Box(Obejct something) {this.content = something}
}

Box 생성자에 어떤 타입의 변수가 와도 상관 없다.

하지만 항상 형변환과정을 거쳐 사용해야하고 안정성이 떨어진다.

제네릭을 사용하면 형변환을 생략할 수 있어 코드가 간결해지고 안정성이 높아진다.

이 템플릿 클래스를 지네릭 클래스로 변환해보자.

class Box <T> {
T content;
public Box(T something) {this.content = something}
}

제네릭 용어

Box<T> : 제네릭클래스, 혹은 T의 Box, T Box(클래스명)
T : 타입변수, 타입매개변수
BoX : 원시타입

와일드카드 <?>

다음과 같이 static메서드가 정의되어있다.

class Juicer {
    static Juice makeJuice(FruitBox<Fruit> box)    {
    ...
    }
}

해당 메서드는 제네릭 메서드가 아니다. 그저 Fruit을 타입변수로 받는 FruitBox 객체를 매개변수로 받는 정적 메서드이다.
class Apple extends Fruit {} 라는 Apple클래스를 만들고
FruitBox<Apple> abox 객체를 만들어 makeJuice 메서드의 매개변수로 넣을 수 있을것 같지만 안된다.

그리고 다음과 같은 static메서드는 사용할 수 없다.

static Juice makeJuice(FruitBox<T> box)

staic메서드가 메모리에 올라갈때 T가 제너릭 타입인지, T라는 클래스인지 구분 못한다.
static <T> Juice makeJuice(FruitBox<T> box)
따라서 위처럼 T가 제너릭 타입임을 알리도록 static 키워드 바로 뒤에 <T>를 추가해야한다.

그리고 FruitBox 안의 타입변수만 바꿔서 오버로딩도 하지 못한다.(타입변수는 오버로딩 성립조건X)

이런 애매모호한 상황을 해결하기 위한게 와일드 카드이다.

타입변수를 모르는 제너릭클래스를 매개변수로 넣고싶을 때 ? 키워드를 사용하면 된다.

class Juicer {
    static Juice makeJuice(FruitBox<? extends Fruit> box)    {
    ...
    }
}
static int numElementsInCommon(Set<?> s1, Set<?> s2) { 
    ...
}

Set안의 요소가 어떤게 올지 모를때, 그래도 Set객체를 매개변수로 쓰고싶을때 와일드카드를 쓰면 된다.
매개변수 안의 타입변수가 어떤게 오던지 상관없다.

와일드 카드도 제너릭처럼 제한을 걸 수 있다.
<? extends T> - T의 하위클래스만 타입변수로 올 수 있다.
<? super T> - T의 상위 클래스만 타입변수로 올 수 있다.

그런데 왠만하면 와일드카드 쓰지 말고 제너릭과 멤버클래스로 선언하는게 안정성이 좋다고 한다. static 멤버로 올 경우만 와일드 카드를 쓰도록 하자.

아래와 같이 입력 타입에 대해 정확히 알수 없는 상황, if 조건에 의해 입력값의 타입이 여러타입으로 나뉘는 경우 와일드카드를 그대로 사용하는 방법도 있다.

RequestEntity<?> request = null;
if (...) {
    request = RequestEntity
        .post(URI.create("https://example.com"))
        .body(new TestType1("hello World"));
    TestType1 body = request.getBody();
} else if (...) {
    request = RequestEntity
        .post(URI.create("https://example.com"))
        .body(new TestType2("foo bar", 123));
    TestType2 body = request.getBody();
}

제너릭 메서드

메서드 선언부, 반환자료형 앞에 제네릭 타입이 위치하는 메서드

public class MyClass {
    public static <T> List<T> getSingleList(T param) {
        return List.of(param);
    }

    public static void main(String[] args) {
        List<String> list1 = MyClass.<String>getSingleList("hi");
        List<Integer> list2 = MyClass.<Integer>getSingleList(1);

        // 입력 매개변수로 제네릭의 특정이 되기때문에 타입지정을 생략해도 상관 없음.
        // List<String> list1 = etSingleList("hi");
        // List<Integer> list2 = getSingleList(1);
    }
}

대표적인 제너릭 메서드로 Collectionssort메서드가 있다.

public static <T> void sort(List<T> list, Comparator<? super T> c)

Collections.sort(properties,sorter) 이런형식으로 간단히 쓰는데, 입력된 list의 제너릭으로 타입을 판별할 수 있기 떄문에 타입지정을 생략해서 사용한다.

제네릭 제한

class Box <T extends Employee> {} - Employee의 하위 객체들만 타입변수로 올 수 있음.
class Box <T super SalesMan> {} - SalesMan의 상위 객체들만 타입변수로 올 수 있음.
class Box <T super SalesMan & 인터페이스명> {} - SalesMan의 상위 객체이고 해당 인터페이스를 구현한 객체만 타입변수로 올 수 있음.

반환타입 앞의 <T>는 제네릭 메서드라는걸 명시하는 것이고 여기에 배웠던 제네릭 제한을 걸 수 도 있다.

위에서 보았던 Juicer클래스의 와일드카드를 쓴 makeJuice 메서드를 제너릭으로 바꾸어 보자.

class Juicer {
    static Juice makeJuice(FruitBox<? extends Fruit> box)    {
    ...
    }
}
class Juicer {
    static <T extends Fruit> Juice makeJuice(FruitBox<T> box)    {
    ...
    }
}

해석하면 해당 메서드는 제네릭 메서드이고 인자로 FruitBox 매개변수가 오고 FruitBox의 타입변수는 제네릭에 의해 달라질 수 있다.
타입변수는 제너릭에 의해 Fruit를 상속하는 객체만 가능하다.

Collectionssort 메서드가 대표적인 제너릭 제한 메서드,

public static <T extends Comparable<? super T>> void sort(List<T> list)

제너릭 요소로 Comparable 를 상속하는 것만 사용 가능하도록 제한하였다.

ArrayList<Integer> list = new ArrayList<>();
....
Collections.sort(list);

이런식으로 많이 사용했는데 Integer레퍼클래스는 Comparable<Integer> 을 구현하고 있기 때문에 사용 가능한것.

public final class Integer extends Number implements Comparable<Integer> { ... }

카테고리:

업데이트: