JAVA

[Java] Generic Programming

SangRok Jung 2022. 8. 1. 17:08
반응형

 

 

 

 


Data Type이나 Algorithms을 이용하여 재활용성, 유연성을 극대화 하기 위한 Programming technique

OOP나 Structured Programming이라는 paradigm을 벗어나 "단순하고 빠르게" 구현하는것이 목표.

 

 

 

* DataType : Size, Rule

* Generics : 컴파일시 타입을 체크해 주는 기능(compile-time type check)

* 실행시 에러를 컴파일 에러로 = generics

 

 

 

public class smple {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();   // Type Check가 강화됨
        
        // JDK1.5 부터 Generics가 도입된 이후 문법.
        ArrayList<Object> list2 = new ArrayList<Object>();  // 모든 Object 입력 가능

        list.add("string");
        list.add(1);    // Compile Error

        String s = list.get(0); // 형변환 생략 가능
    }
}

 

 

 

 

 

 

 

Generic Programming 이전의 오류


불변성의 Data Type으로 이를 Runtime시점(Instance 생성 시점)에 결정되게끔 바꾸면 독립성, 유연성을 대폭 높이게 된다.

DataType을 정의한뒤 logic을 세우는게 아닌 logic에 맞춰 datatype을 결정한다.

 

 

 

▶ 불필요한 형변환으로 Compiler가 Error를 발견하기 어렵다.

 

 

 

 

 

 

▶ 아래 코드에서도 실행 과정에서 Exception이 발생되지 않는다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Generic Programming의 예


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Generic Programming의 개념


객체 타입의 안정성을 높이고 타입체크와 형변환의 번거로움을 줄여줌으로서 코드가 간결해진다.

 

 

타입의 명명규칙의 관례(Recommand)

  • 대문자 사용.
  • 타입이 한개일 경우 큰 상관이 없음
  • 두 개 이상의 경우 한문자로 이름 결정

 

 

 

용어
E Element
K Key
N Number
Type
V Value

 

 

 

 

 

Exception Class와 RuntimeException Class 의 상속계층도

Exception = runtime중 발생하는 에러

RuntimeException = 프로그래머의 실수

 

Runtime보다 Compiletime에 발생하는 Error는 잡을 수 있기에 generics를 이용하여 프로그래머가 저지를 수 있는 실수를 예방한다.

 

 

 

 

 

 

class Tv{}
class Audio{}

public class ex2 {
    public static void main(String[] args) {
        ArrayList list0 = new ArrayList();
        Tv t1 = (Tv)list0.get(0);   // 타입 불일치라 형변환이 필요.
        
        ArrayList<Tv> list = new ArrayList<Tv>();   //Tv Type의 객체만 저장 가능
        Tv t2 = list.get(0);    // Generics로 형변환 필요없다.
        list.add(new Tv());
    }
}

 

 

 

 

▶ Generics 용어

Box<T> Generic Class. 'T의 박스', 'T Box' 라고 읽는다.
T 타입 변수 또는 타입 매개변수. (T는 타입 문자)
Box 원시 타입(raw type) 

 

 

 

 

 

 

 

 

 

 

 

▶ Generics type과 다형성

  • 참조 변수와 생성자의 대입된 타입은 일치해야 한다.
  • Generics Class간의 다형성은 성립(대입된 타입은 일치해야한다.)
  • 매개변수의 다형성도 성립한다.
    • 조상의 자손객체도 성립한다.

 

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

        ArrayList<Tv> list = new ArrayList<Tv>();       // 일치
        ArrayList<Product> list = new ArrayList<Tv>();  // Error

        List <Tv> list = new ArrayList<Tv>();   // 일치. ArrayList가 list를 구현
        List <Tv> list = new LinkedList<Tv>();  // 일치. LinkedList가 list를 구현
        
        ArrayList<Product> list = new ArrayList<Product>();
        list.add(new Product());
        list.add(new Tv());         // OK. Product 자손
        list.add(new Audio());      // OK. Product 자손

 

 

 

▶ 구현

import java.util.*;

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

public class smple {
    public static void main(String[] args) {
        ArrayList<Product> productList  = new ArrayList<Product>();
        ArrayList<Tv> tvList            = new ArrayList<Tv>();
        // ArrayList<Product> tvList    = new ArrayList<Tv>(); // ERROR.
        List<Tv> TvList2                = new ArrayList<Tv>(); // OK. 다형성

        productList.add(new Tv());
        productList.add(new Audio());

        tvList.add(new Tv());
        // tvList.add(new Audio());    // Error.

        printAll(productList);
        // printAll(tvList);   // Error. 컴파일 에러.
        // 참조변수와 생성자 타입이 같아야된다
    }
    
    public static void printAll(ArrayList<Product> list){   
                    
        for(Product p : list){
            System.out.println(p);
        }
    }
}

 

 

 

 

 

 

 

 

 

Iterator<E>


Collections class 뿐만 아니라 Iterator에도 Generics가 적용되어 있다는 것을 알 수 있다.

Generics가 도입되면서 기존의 소스에 Object가 들어간 Class에는 전부 아래와 같이 바뀌었다.

 

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

 

 

 

 

▶ 구현

코드가 간결해지는것을 볼 수 있다.

import java.util.*;

public class smple {
    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));

        // Iteraotr에 generics 적용
        Iterator<Student> it = list.iterator();

        while(it.hasNext()) {
            // generics를 적용 하지 않을 시 형변환을 해야한다.
            // Student s = (Student)it.next();
            // System.out.println(s.name);

            // 아래 코드처럼 코드가 간결해진다.
            System.out.println(it.next().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;
    }
}

 

 

 

 

 

 

 

 

HashMap<K, V>


HashMap처럼 데이터를 key, value의 형태로 저장하는 Collections class는 지정해 줘야 할 타입이 <K, V> 두개다.

여러 개의 타입 변수가 필요한 경우, 콤마(,)로 구분자를 선언한다.

 

 

import java.util.*;

public class smple {
    public static void main(String[] args) {
        HashMap<String, Student> map = new HashMap<>();  //JDK 1.7부터 생성자에 타입지정 생성가능
        
        map.put("김연아" ,new Student("김연아", 1, 1, 100, 100, 100));
        map.put("유재석", new Student("유재석", 1, 2, 5, 5, 5));
    }
}

class Student{
    String name = "";
    int ban;
    int no;
    int kor;
    int eng;
    int math;

    Student(String name, int ban, int no, int kor, int eng, int math){
        this.name = name;
        this.ban = ban;
        this.no = no;
        this.kor = kor;
        this.eng = eng;
        this.math = math;
    }
}

 

 

 

 

 

Generic Programming의 기본 문법


▶ 다중 매개변수(다중 템플릿)

 

기본자료형은 템플릿에 가져 올 수 없다. wrapper 이용.

 

 

 

 

▶ 타입입자의 생략

인자를 생략하는 SugarCode를 사용.

 

 

일반적으로 r-value를 먼저 해석하는데 신형 JAVA는 l-value를 참조하여 r-value를 해석하는 기법을 사용한다. (타입-추론 기법)

 

 

 

 

 

 

 

▶ 제한된 Generic Class

 

매개변수 T에 지정할 수 있는 타입의 종류를 제한한다.

public class FruitBox<T extends Fruit> {    // Fruit의 자손만 타입으로 지정가능
    ArrayList<T> list = new ArrayList<T>();
    ...

}

 

 

▷ Class fruit의 자손이면서 Eatable interface도 구현해야 한다면 아래와 같이 & 기호로 연결한다.

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

 

▷ 예제

import java.util.*;

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

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

interface Eatable{

}

public class Ex12_3 {
    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>();   // Error, 타입 불일치
        // FruitBox<Toy> toyBox = new FruitBox<Toy>(); // Error.

        fruitBox.add(new Fruit());
        fruitBox.add(new Apple());
        fruitBox.add(new Grape());
        appleBox.add(new Apple());
        // appleBox.add(new Grape());  // Error, Grape는 Apple의 자손이 아니다.
        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> { // Fruit을 상속받고 eatable interface를 구현해야는 조건 제약
    // &로 사용
}

class Box<T> {
    ArrayList<T> list = new ArrayList<T>();     //item을 저장할 list.
    void add(T item) {  //박스에 item 추가
        list.add(item);
    }
    T get(int i) {      // 박스에서 item 꺼낼때
        return list.get(i);
    }
    int size() {        
        return list.size();
    }
    public String toString() {
        return list.toString();
    }
}

 

 

 

 

 

 

▶ Generics의 제약

static맴버는 타입 변수에 지정된 타입, 대입된 타입의 종류에 관계없이 동일한 것이어야 한다.

* static - 모든 instance의 공통적.

 

 

▷ static 맴버에 타입 변수 사용 불가.

class Box<T> {
    static T item;  // 에러
    static int compare(T t1, T t2){ // 에러
    ...
}

 

 

▷ 객체, 배열 생성할 때 타입 변수 사용 불가, 타입 변수로 배열 선언은 가능.

class Box<T> {
    T[] itemArr;    //OK, T타입의 배열을 위한 참조변수

    T[] toArray() {
        T[] tmpArr = new T[itemArr.length];     //Error, 지네릭 배열 생성불가
    }
}

 

 

 

 

 

 

 

 

 

▶ Generic Method

Method를 호출 할 때 마다 타입을 대입해야한다.

  • Collections.sort()가 Generic Method다.

 

 

Generic Class에 정의된 타입 매개변수가 T이고 Generic Method에 정의된 타입 매개변수가 T이어도 이 둘은 전혀 별개의 것이다.

class FruitBox<T>{
    static <T> void sort(List<T> list, Comparator<? super T> c){
        ...
    }
}

 

위의 코드에서 Generic Class FruitBox에 선언된 타입 매개변수 T와 Generic Method sort()에 선언된 타입 매개변수 T는 타입 문자만 같은 뿐 서로 다른 것이다.

 

 

 

이 Method를 호출 할 때는 아래와 같이 타입 변수에 타입을 대입해야 한다.

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

System.out.println(Juicer.<Fruit>makeJuice(fruitBox)));
System.out.println(Juicer.<Apple>makeJuice(appleBox)));

 

 

 

 

 

 

▶ Generic Type의 형변환

Generic Type과 원시 타입 간의 형변환은 바람직 하지 않다.

    Box box = null;
    Box<Object> objBox = null;

    box = (Box)objBox;          //OK. 지네릭 타입   => 원시 타입, 경고 발생
    objBox = (Box<Object>box);  //OK. 원시 타입     => 지네릭 타입, 경고 발생
    

    Box<Object> objBox = null;
    Box<String> strBox = null;

    objBox = (Box<Object>strBox);   //에러. Box<String> => Box<Object>
    strBox = (Box<String>objBox);   //에러. Box<Object> => Box<String>

 

 

 

Wild card가 사용된 GenericType으로는 형변환 가능

    Box<Object> objBox = (Box<Object>)new Box<String>();    //에러. 형변환 불가능
    Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>(); //OK.
    Box<? extends Object> wBox = new Box<String>(); //위 문장과 동일.

    static Juice makeJuice(FruitBox<? extends Fruit> box) {}
    FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); //OK
    FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); //OK

 

 

 

 

 

 

 

 

 

▶ Generic Type의 제거

Complier는 GenericType을 제거하고 필요한 곳에 형변환을 넣는다.

 

Generic Type의 경계를 제거한다.

 

Generic Type 제거 후에 타입이 불일치하면 형변환을 추가한다.

 

 

 

 

 

 

 

 

 

타입 인자로 타입을 전달.


public class gnExam2 {
    public static void main(String[] args) {
        box<String> sBox = new box<>();
        sBox.set(new String("Rain"));

        box<box<String>> bBox = new box<>();
        bBox.set(sBox);

        System.out.println(bBox.get().get());

        box<box<box<String>>> bbBox = new box<>();
        bbBox.set(bBox);
        System.out.println(bbBox.get().get().get());
    }
}

public class box <T> {
    private T obj;

    public void set(T obj){
        this.obj = obj;
    }

    public T get(){
        return obj;
    }

    @Override
    public String toString(){
        return "box(" + obj + ")";
    }
    // T : Type Parameter, Template (타입 매개변수)
    // Apple : Type Argument (타입 인자)
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Type Restriction (타입의 제한)


▶ Class를 이용한 Type restriction

 

 

class Box <T extends Number> {...}

 

Instance 생성시 Type 인자로 Number 또는 이를 Inheritance하는 class만을 올 수 있다.

 

 

Box <Integer> iBox = new Box<>(); //또는
BOX <Integer> dBox = new Box<>();

 

 

 

 

 

 

▶ Type restriction의 효과

 

 Type restriction을 통해 compiler가 method가 호출 명령을 만들 수 있다.

 

class Box<T> {
   private T ob;
   ....
   public int toIntValue() {
      return ob.intValue(); // ERROR!
   }
}



class Box<T extends Number> {
   private T ob;
   ....
   public int toIntValue() {
      return ob.intValue();   // OK!
   }
}

 

 

 

 

 

▶ Interface를 이용한 Type restriction

 

 

 

 

 

 

 

 

▶Type restrictio의 다중 제한

&를  통해 이중으로 제한을 둘 수 있다.

class Box<T extends Number & Eatable> { ... }

 

 

 

 

 

 

 

 

 

 

▶ Generic Method

 

Class 전체가 아닌 Method에만 Generic을 적용

 

 

 

 

Generic Method는 호출 시점에 결정.

 

Box<String> sBox = BoxFactory.<String>makeBox("Sweet");
Box<Double> dBox = BoxFactory.<Double>makeBox(7.59);   	// 7.59에 대해 오토 박싱 진행됨
Box<String> sBox = BoxFactory.makeBox("Sweet"); 		// 생략 가능

 

 

 

 

 

 

 

 

 

Inheritance of GenericClass


GenericClass도 Inheritance가 가능하며 일반적인 Inheritance Mechanism을 그대로 사용한다.

 

 

 

 

 

 

 

 

 

 

Target Type


타입-추론을 통해 알아낸 데이터 타입

용어가 javadoc에 많이 나온다.

 

 

 

 

 

 

 

 

반응형

'JAVA' 카테고리의 다른 글

[Java] Annotation  (0) 2022.08.04
[Java] enum(열거형)  (0) 2022.08.03
[Java] WildCard  (0) 2022.08.01
[Java] Collections Framework 예제  (0) 2022.07.28
[Java] Collections Framework - Comparator & Comparable  (0) 2022.07.28