본문 바로가기

Programming/Java

[Java] 김영한의 자바 중급 2편 #1 - 제네릭

반응형

 

1. Generic이 필요한 이유에 대하여

 

 

Integer를 담을 수 있는 Box

package generic.ex1;

public class IntegerBox {

    private int value;

    public void set(Integer value) {
        this.value = value;
    }

    public Integer get() {
        return value;
    }
}

 

 

String을 담을 수 있는 Box

package generic.ex1;

public class StringBox {

    private String value;

    public void set(String object) {
        this.value = object;
    }

    public String get() {
        return value;
    }
}

 

 

Integer와 String을 각 Box에 담아서 출력하는 Main 코드

package generic.ex1;

public class BoxMain1 {

    public static void main(String[] args) {
        // integer
        IntegerBox integerBox = new IntegerBox();
        integerBox.set(10);

        Integer integer = integerBox.get();
        System.out.println("integer = " + integer);

        // string
        StringBox stringBox = new StringBox();
        stringBox.set("test");

        String string = stringBox.get();
        System.out.println("string = " + string);
    }
}

// 실행 결과
integer = 10
string = test

 

1.1. 이 코드의 문제점

만약 Double, Boolean 등의 다른 타입을 담는 Box가 필요하다면 별도로 클래스를 만들어야 한다.

→ 재사용성이 매우 떨어진다.

 

 

 

 


 

 

2. 다형성을 통해 중복 해결을 시도하기

 

Object를 담는 Box 클래스

→ Object는 모든 클래스의 부모이기 때문에 모든것을 담을 수 있다.

package generic.ex1;

public class ObjectBox {

    private Object value;

    public void set(Object object) {
        this.value = object;
    }

    public Object get() {
        return value;
    }
}

 

 

Object Box를 통해 String, Integer를 담는 Main 예제

package generic.ex1;

public class BoxMain2 {

    public static void main(String[] args) {

        // 1. Integer를 담기위한 Box를 생성한다.
        ObjectBox integerBox = new ObjectBox();
        integerBox.set(10);

        // 1.1. Object -> Integer로 캐스팅해야 한다.
        Integer integer = (Integer) integerBox.get();
        System.out.println("integer is " + integer);

        // 2. String을 담기 위한 Box를 생성한다.
        ObjectBox stringBox = new ObjectBox();
        stringBox.set("hello");

        // 2.1. Object -> String으로 캐스팅 필요
        String str = (String) stringBox.get();
        System.out.println("string is " + str);

        // 잘못된 타입의 인수 전달 시
        integerBox.set("문자100");
        Integer result = (Integer) integerBox.get();
        System.out.println("result is " + result);
    }
}

// 결과
integer is 10
string is hello
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
	at generic.ex1.BoxMain2.main(BoxMain2.java:25)

Process finished with exit code 1

 

 

2.1. 문제점

  • 자식은 부모를 담을 수 없다. → (Integer), (String) 처럼 타입 캐스팅 코드를 넣어서 Object 타입을 Integer, String 등으로 직접 다운 캐스팅을 해야 한다.
  • 잘못된 타입의 인수 전달 문제 integerBox에 Integer가 들어가길 기대했다. 하지만 Java 입장에서는 아무런 문제가 되지 않는다. 따라서 타입 안정성 문제가 발생한다.
integerBox.set("문자100");

 

 

 


 

3. 제네릭 적용

제네릭을 사용하면 두마리 토끼를 잡을 수 있다.

코드 재사용 & 타입 안정성

 

 

3.1. 예제

GenericBox

package generic.ex1;

public class GenericBox<T> {

    private T value;

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

    public T get() {
        return value;
    }
}
  • <> : 다이아몬드 기호를 통해 제네릭 클래스임을 나타낸다.
  • T : 타입 매개변수

 

 

Main 코드

package generic.ex1;

public class BoxMain3 {

    public static void main(String[] args) {

        // 생성 시점의 T의 타입이 결정된다.
        GenericBox<Integer> integerBox = new GenericBox<Integer>();

        // integerBox에 Integer만 넣을 수 있음 (타입 안정성)
        // integerBox를 타입 캐스팅 변환하지 않아도 됌
        integerBox.set(10);
        Integer integer = integerBox.get();
        System.out.println("integer = " + integer);

        GenericBox<String> stringBox = new GenericBox<String>();
        stringBox.set("Hello");
        String str = stringBox.get();
        System.out.println("str = " + str);

        // 타입 추론 가능 : 생성하는 제네릭 타입 생략 가능
        GenericBox<Integer> integerBox2 = new GenericBox<>();
    }
}

// 결과
integer = 10
str = Hello

 

 


 

4. Generic 용어와 관례

Generic의 핵심 : 사용할 타입을 미리 결정하지 않는다는 점 (실제 사용하는 생성 시점에 타입을 결정하는 것)

 

4.1. 메서드의 매개변수와 인자

void method(String param) // 매개변수(Parameter)

void main() {
	String arg = "hello"
	method(arg) // 인수 (Argument)
}

매개변수(Parameter)를 정의하고 실행 시점에 인자(Argument)를 통해 원하는 값을 매개변수에 전달한다.

 

 

4.2. 제네릭의 타입 매개변수와 타입 인자

메서드의 매개변수와 유사하다.

  • 메서드의 매개변수는 사용할 값에 대한 결정을 나중으로 미루는 것
  • 제네릭의 타입 매개변수는 사용할 타입에 대한 결정을 나중에 미루는 것

즉, 제네릭 클래스는 타입 매개변수에 타입 인자를 전달해서 사용할 타입을 결정한다.

  • 타입 매개변수 : GenericBox<T>에서 T
  • 타입 인자
    • GenericBox<Integer>에서 Integer

 

 

4.3. 용어 정리

  • 제네릭 타입
    • 클래스나 인터페이스를 정의할 때 타입 매개변수를 사용하는 것을 말한다.
    • 제네릭 클래스, 제네릭 인터페이스를 모두 합쳐 제네릭 타입이라고 한다.
  • 타입 매개변수
    • 제네릭 타입이나 메서드에서 사용되는 변수로, 실제 타입으로 대체된다.
  • 타입 인자
    • 제네릭 타입을 사용할 때 제공되는 실제 타입이다.

 

4.4. 제네릭 명명 관례

  • E : Element
  • K : Key
  • N : Number
  • T : Type
  • V : Value

 

 


 

5. 제네릭 활용 예제

 

 

 

 

Animal, Dog, Cat 클래스

// Animal
package generic.animal;

public class Animal {

    private String name;
    private int size;

    public Animal(String name, int size) {
        this.name = name;
        this.size = size;
    }

    public String getName() {
        return name;
    }

    public int getSize() {
        return size;
    }

    public void sound() {
        System.out.println("동물울음소리");
    }

    @Override
    public String toString() {
        return "Animal{" +
                "name='" + name + '\\'' +
                ", size=" + size +
                '}';
    }
}

// Cat
package generic.animal;

public class Cat extends Animal{

    public Cat(String name, int size) {
        // 부모 클래스에 정의된 생성자가 있기 때문에 맞추어 super 호출
        super(name, size);
    }

    @Override
    public void sound() {
        System.out.println("냐옹");
    }
}

// Dog
package generic.animal;

public class Dog extends Animal{

    public Dog(String name, int size) {
        // 부모 클래스에 정의된 생성자가 있기 때문에 맞추어 super 호출
        super(name, size);
    }

    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}

 

 

Box<T> : 객체 보관용 제네릭 클래스

package generic.ex2;

// 객체를 보관할 수 있는 제네릭 클래스
public class Box<T> {

    private T value;

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

    public T get() {
        return value;
    }
}

 

 

Main 코드

package generic.ex2;

import generic.animal.Animal;
import generic.animal.Cat;
import generic.animal.Dog;

public class AnimalMain1 {

    public static void main(String[] args) {

        // 동물, 개, 고양이 정의
        Animal animal = new Animal("동물", 0);
        Dog dog = new Dog("멍멍이", 100);
        Cat cat = new Cat("냐옹이", 50);

        Box<Dog> dogBox = new Box<>();
        dogBox.set(dog);
        Dog findDog = dogBox.get();
        System.out.println("findDog : " + findDog);

        Box<Cat> catBox = new Box<>();
        catBox.set(cat);
        Cat findCat = catBox.get();
        System.out.println("findCat : " + findCat);

        Box<Animal> animalBox = new Box<>();
        animalBox.set(animal);
        Animal findAnimal = animalBox.get();
        System.out.println("findAnimal : " + findAnimal);
    }
}

// 결과
findDog : Animal{name='멍멍이', size=100}
findCat : Animal{name='냐옹이', size=50}
findAnimal : Animal{name='동물', size=0}
  • Box<T> : 제네릭 클래스이며 각 타입에 맞는 동물을 보관하는 용도
  • Box<Dog> dogBox = Dog 타입을 보관
  • ..

 

 

6. 문제

6.1. 제네릭 기본 #1

다음 코드와 실행 결과를 참고해서 Container 클래스를 만들어라. Container 클래스는 제네릭을 사용해야 한다.

package generic.test.ex1;

public class ContainerTest {
    public static void main(String[] args) {
        Container<String> stringContainer = new Container<>();
        System.out.println("빈값 확인1: " + stringContainer.isEmpty());

        stringContainer.setItem("data1");
        System.out.println("저장 데이터: " + stringContainer.getItem());
        System.out.println("빈값 확인2: " + stringContainer.isEmpty());

        Container<Integer> integerContainer = new Container<>();
        integerContainer.setItem(10);
        System.out.println("저장 데이터: " + integerContainer.getItem());
    }
}

 

정답

package generic.test.ex1;

public class Container<T> {

    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }

    public Boolean isEmpty() {
        return item == null;
    }
}

 

 

 

 

반응형