본문 바로가기

프로그래밍/Java

Java_제네릭스·지네릭스(1)_타입 변수, 제네릭스 용어, 제네릭의 타입과 다형성, Iterator<E>, HashMap<K,V>, 제한된 제네릭 클래스

반응형

제네릭스(Generics)

: 제네릭스란 컴파일시 타입을 체크해주는 기능이다. -> 타입의 안정성을 높이고 형변환의 번거로움을 줄여준다.

 

예를들어 아래와 같이 ArrayList를 생성할 때, 저장할 객체의 타입을 저장해주면 지정한 타입 외에 다른 타입의 객체가 저장되면 에러가 발생한다.

//Tv객체만 저장할 수 있는 ArrayList를 생성.
ArrayList<Tv> tvList= new ArrayList<Tv>();

tvList.add(new Tv());	//Ok.
tvList.add(new Audio());	//컴파일 에러. Tv외에 다른 타입은 저장 불가.

→ 그리고 저장된 객체를 꺼낼 때는 형변환할 필요가 없어서 편리하다. 이미 어떤 타입의 객체들이 저장되어 있는지 알고 있기 때문이다.

 

 

 

- 타입 변수

: 클래스를  작성할 때, Object타입 대신 타입변수<E>를 선언해 사용한다.

 

ArrayList클래스의 선언에서 클래스 이름 옆의 '<>'안에 있는 E를 '타입 변수(type variable)'라고 하며, 일반적으로는 'Type'의 첫 글자를 따서 T를 사용한다. 타입변수로 반드시 T를 사용해야 하는 것은 아니며, T가 아닌 다른 것을 사용해도 된다. ArrayList<E>의 경우, 'Elemant(요소)'의  첫 글자를 따서 타입 변수의 이름으로 E를 사용한다.

public class ArrayList<E> extends AbstractList<E> {
    private transient E[] elementData;
    public boolean add(E o) {}
    publci E get(int index) {}
    ...
}

 

타입 변수가 여러개인 경우에는 Map<K, V>와 같이 콤마','를 구분자로 나열하면 된다. K는 Key(키)를 의미하고, V는 Valeu(값)을 의미한다.

 

 

 

- 타입 변수에 대입하기

: ArrayList와 같은 제네릭 클래스를 생성할 때는 다음과 같이 참조변수와 생성자에 타입 변수 E대신에 Tv와 같은 실제 타입을 지정해줘야 한다.

ArrayList<Tv> tvList=new ArrayList<Tv>();	//타입 변수 E 대신 실제 타입 Tv를 대입

→ 이때, 타입 변수 E 대신 지정된 타입 Tv를 '대입된 타입'이라고 한다.

 

 

 

- 제네릭스 용어

 

 

 

- 지네릭 타입과 다형성

: 지네릭 클래스의 객체를 생성할 때, 참조변수에 지정해준 지네릭 타입과 생성자에 지정해준 지네릭 타입은 일치해야 한다. 

import java.util.*;

class Product{}
class Tv extends Product{}
class Audio extends Product{}

class JavaJungsuk_Generics {
    public static void main(String args[]) {
       ArrayList<Product> productList= new ArrayList<Product>();
       ArrayList<Tv> tvList= new ArrayList<Tv>();
     //ArrayList<Product> tvList= new ArrayList<Tv>();	//에러.
     //List<Tv> tvList=new ArrayList<Tv>();	//OK. 다형성
     
       productList.add(new Tv());
       productList.add(new Audio());
       
       tvList.add(new Tv());
       tvList.add(new Tv());
       
       printAll(productList);
   }
   
   public static void printAll(ArrayList<Product> list) {
       for(Product p:list)
           System.out.println(p);
    }
}

결과
Tv@15db9742
Audio@6d06d69c

→ 클래스 Tv와 Product가 서로 상속관계에 있어도 일치해야 하기 때문에

ArrayList<Product> list = new ArrayList<Tv>();는 에러가 난다. 

 

 

 

- Iterator<E> 예제

import java.util.*;

class JavaJungsuk_Generics_Iterator {
    public static void main(String[] args) {
        ArrayList<Student> list=new ArrayList<Student>();
        list.add(new Student("자바왕", 1, 1));
        list.add(new Student("자바짱", 1, 2));
        list.add(new Student("홍길동", 2, 1));
        
        Iterator<Student> it=list.iterator();
        while(it.hasNext()) {
            Student s=it.next();
            System.out.println(s.name);
        }
    }
}

class Student {
    String name="";
    int ban;
    int no;
    
    Student(String name, int ban, int no) {
        this.name=name;
        this.ban=ban;
        this.no=no;
    }
}
결과
자바왕
자바짱
홍길동

→ Iterator에 제네릭스를 적용하는 예제이다. 지네릭스를 이용해 선언해주기만 하면 된다. 

 

 

 

- HashMap<K, V>

: HashMap처럼 데이터를 키(key)와 값(value)의 형태로 저장하는 컬렉션 클래스는 지정해줘야 할 타입이 두개이다.

 

키의 타입이 String이고 저장할 값의 타입이 Student인 HashMap을 생성하려면 다음과 같이 한다. 

HashMap<String, Student> map=new HashMap<String, Student>();	//생성
map.put("자바왕", new Student("자바왕", 1, 1, 100, 100, 100));	//데이터 저장

 

 

 

- 제한된 지네릭 클래스

: 지네릭 타입에 'extends'를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.

 

import java.util.ArrayList;

class Fruit implements Eatable {
    public String toString() {return "Fruit";}
}

class Apple extends Fruit {
    public String toString() {
        return "Apple";
    }
}

class Grape exnteds Fruit {
    public String toString() {
        return "Grape";
    }
}

class Toy {
    public String toString() {
        return "Toy";
    }
}

interface Eatable {}

class JavaJungsuk_Generics_limitedGenercis {
    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox= new FruitBox<Fruit>();
        FruitBox<Apple> appleBox= new FruitBox<Apple>();
        FruitBox<Grape> grapeBox= new FruitBox<Grape>();
      //FruitBox<Grape> grapeBox= new FruitBox<Apple>();	//에러, 타입 불일치
      //FruitBox<Toy> toyBox= new FruitBox<Toy>();	//에러.
      
        fruitBox.add(new Fruit());
        fruitBox.add(new Apple());
        fruitBox.add(new Grape());
        appleBox.add(new Apple());
      //appleBox.add(new Grape());
        grapeBox.add(new Grape());
        
        System.out.println("fruitBox-"+fruitBox);
        System.out.println("appleBox-"+appleBox);
        System.out.println("grapeBox-"+grapeBox);
    }
}

class FruitBox<T extends Fruit & Eatable> extends Box<T> {}

class Box<T> {
    ArrayList<T> list=new ArrayList<T>();
    
    void add(T item) {
        list.add(item);
    }
    
    T get(int i) {
        return list.get(i);
    }
    
    int size() {
        return list.size();
    }
    
    public String toString() {
        return list.toString();
    }
}
결과
fruitBox-[Fruit, Apple, Grape]
appleBox-[Apple]
grapeBox-[Grape]

→ 한 종류의 타입만 담을 수 있지만, Fruit클래스의 자손들만 담을 수 있다는 제한이 더 추가된 것이다.

→ 클래스가 아니라 인터페이스를 구현해야 한다는 제약이 필요하다면 이때도 'implements'가 아닌 'extends'를 사용해야 한다. 

 

반응형