[Java] Collections Framework와 Generics

김성은·2022년 11월 21일
0

Java

목록 보기
3/3
post-thumbnail

1. Collections Framework

다수의 데이터를 쉽고 효과적으로 처리할 수 있는 표준화된 방법을 제공하는 클래스의 집합

  • 다수의 데이터를 다루는데 다양하고 풍부한 클래스들을 제공
  • 인터페이스와 다형성을 이용한 객체지향적 설계를 통해 표준화되어 있어 편리하고 재사용성이 높은 코드를 작성할 수 있음

1.1 핵심 인터페이스

참고: Vector, Stack, Hashtable, Properties와 같은 클래스들은 컬렉션 프레임워크가 만들어지기 이전부터 존재하던 것이기 때문에 컬렉션 프레임워크의 명명법을 따르지 않는다. 기존의 컬렉션 클래스들은 호환을 위해 설계를 변경해 남겨두었지만 가능하면 사용하지 않는 것이 좋다.

2. Generics

데이터 타입(date type)을 일반화한다(generalize)는 것을 의미

  • 제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법
  • 컴파일 시에 미리 타입 검사(type check)를 수행 시 다음과 같은 장점을 지님
    • 반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일 수 있음
    • 클래스나 메소드 내부에서 사용되는 객체의 타입 안정성을 높일 수 있음

-> 즉, 의도하지 않은 타입의 객체가 저장되는 것을 막음으로써 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생하는 오류를 줄임

2.1 제네릭 클래스 선언

MyGenerics 클래스

public class MyGenerics {
    Object item;

    public void setItem(Object item) { this.item = item; }
    public Object getItem() { return item; }
}

MyGenerics 클래스을 제네릭 클래스로 변경

public class MyGenerics<T> {
    T item;

    public void setItem(T item) { this.item = item; }
    public T getItem() { return item; }
}
  • myGenerics<T>에서 T를 '타입 변수(type variable)'라고 하며 임의의 참조형 타입을 의미
  • 타입 변수는 T가 아닌 다른 것을 사용할 수 있으며 통상적으로 다음과 같이 사용
    • T : Type
    • E : Element
    • K : Key
    • V : Value
    • N : Number
MyGenerics<String> g = new MyGenerics<String>();  // 실제 타입 지정 
g.setItem(new Object());						  // error 
g.setItem("Hello World");						  // ok
String item = g.getItem();						  // 형변환 필요x	
  • 제네릭 클래스를 생성 시 타입 변수 자리에 사용할 실제 타입을 명시해야 함

참고: 자바에서 타입 변수 자리에 사용할 실제 타입을 명시할 때 기본 타입을 바로 사용할 수 없다. 따라서 Integer, Charactor와 같이 Wrapper 클래스를 사용해야 한다.

참고: Java SE 7부터 인스턴스 생성 시 타입을 추정할 수 있는 경우에는 타입을 생락할 수 있다.

MyGenerics<String> g = new MyGenerics<>(); // Java SE 7부터 가능

2.2 제네릭 용어

Class MyGenerics<T>{}

  • MyGenerics<T>: 제네릭 클래스
  • T: 타입 변수 또는 타입 매개변수
  • MyGenerics: 원시 타입(raw type)

2.3 예제

import java.util.*;

class LandAnimal {
    public void crying() { System.out.println("육지동물"); }
}
class Cat extends LandAnimal {
    public void crying() { System.out.println("냐옹냐옹"); }
}

class Dog extends LandAnimal {
    public void crying() { System.out.println("멍멍"); }
}

class Sparrow {
    public void crying() { System.out.println("짹짹"); }
}

class AnimalList<T> {
    ArrayList<T> al = new ArrayList<>();

    void add(T animal) { al.add(animal); }
    T get(int index) { return al.get(index); }
    boolean remove(T animal) { return al.remove(animal); }
    int size() { return al.size(); }
}

public class MyGenerics {
    public static void main(String[] args) {
        AnimalList<LandAnimal> landAnimal = new AnimalList<>(); 

        landAnimal.add(new LandAnimal());
        landAnimal.add(new Cat());
        landAnimal.add(new Dog());
        // landAnimal.add(new Sparrow()); // 오류가 발생함.

        for (int i = 0; i < landAnimal.size() ; i++) {
            landAnimal.get(i).crying();
        }
    }
}

// 실행결과
// 육지동물
// 냐옹냐옹
// 멍멍
  • Cat, Dog 클래스는 LandAnimal 클래스를 상속받는 자식 클래스이므로 AnimalList<LandAnimal>에 추가 가능하나 Sparrow 클래스는 타입이 다르므로 불가능

2.4 타입 변수의 제한

타입 문자로 사용할 타입을 명시하면 한 종류의 타입만 저장할 수 있도록 제한할 수 있지만, 여전히 모든 종류의 타입을 지정할 수 있다는 것에는 변함이 없다
이처럼 타입 변수 T에 지정할 수 있는 타입의 종류를 제한할 수 있는 방법이 있다

class AnimalList<T extends LandAnimal> { ... }
  • extends 키워드를 사용하면 타입 변수에 특정 타입만을 사용하도록 제한할 수 있음
  • 클래스의 타입 변수에 제한을 걸어 놓으면 클래스 내부에서 사용된 모든 타입 변수에 제한이 걸림
  • 클래스가 아닌 인터페이스를 구현할 경우에도 implements 키워드가 아닌extends 키워드 사용
class AnimalList<T extends LandAnimal & MyInterface> { ... }
  • 클래스와 인터페이스를 동시에 상속받고 구현해야 한다면 &기호 사용

2.5 제네릭 제거 시기

  • 자바 코드에서 선언되고 사용된 제네릭 타입은 컴파일 시 컴파일러에 의해 자동으로 검사되어 타입 변환됨
  • 코드 내의 모든 제네릭 타입은 제거되어 컴파일된 class 파일에는 어떠한 제네릭 타입도 포함되지 않게 된다

-> 이러한 동작을 통해 제네릭을 사용하지 않는 코드와의 호환성을 유지

2.6 제네릭의 제한

2.6.1 static 변수

public class MyGenerics<T> {
    static T item;	// error

    public void setItem(T item) { this.item = item; }
    public T getItem() { return item; }
}
  • static변수에 타입 변수를 사용할 수 없는데 T는 인스턴스변수로 간주되기 때문
  • static변수는 인스턴스변수를 참조하지 못함

2.6.1 제네릭 타입의 배열

public class MyGenerics<T> {
    T[] items;	  					  	// ok
   	T[] tmpArr = new T[items.length]; 	// error
}
  • 제네릭 변수를 생성 불가능한 이유는 new 연산자 때문인데 이 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 함 근데 위의 클래스를 컴파일 하는 시점에 T가 어떤 타입이 될지 알 수 없음
  • 따라서 제네릭 배열이 필요한 경우 'Reflection API'의 newInstanceOf()와 같이 동적으로 객체를 생성하는 메서드를 사용하거나 Object 배열을 생성해 복사한 다음 'T[]'로 형변환을 통해 생성 가능

2.7 제네릭 메소드

public static <T> void sort( ... ) { ... }
  • 메소드의 선언부에 타입 변수를 사용한 메소드
  • 타입 변수의 선언은 메소드 선언부에서 반환 타입 바로 앞에 위치
class AnimalList<T> {
    ...
    public static <T> void sort(List<T> list, Comparator<? super T> comp) {
        ...
    }
    ...
}
  • 제네릭 클래스에서 정의된 타입 변수 T와 제네릭 메소드에서 사용된 타입 변수 T는 전혀 별개의 것임을 주의
profile
취업 준비생

0개의 댓글