티스토리 뷰

JAVA/JAVA기본

JAVA - 제네릭이란?

realizers 2022. 1. 30. 22:17
728x90
반응형

제네릭이란?


  • 제네릭이란 아래 코드에서 <> 괄호 안에 들어가는 타입을 지정할 수 있는데 클래스 내부에서 지정하는 것이 아니라 외부에서 사용자에 의해 지정되는 것을 의미합니다. 
ArrayList<String> arrayList = new ArrayList<>();
HashMap<String, Integer> hashMap = new HashMap<>();

 

제네릭의 장점


  • 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있습니다.
  • 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하거나 변환을 해줄 필요가 없게됩니다.
  • 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아집니다.

 

제네릭의 타입


public class 클래스명 <T>{ ... }
public interface 인터페이스명 <T>{ ... }
타입 인자 설명
<T> Type
<E> Element
<K> Key
<V> Value
<N> Number
<R> Result

 

제네릭의 사용 예제


  • Student객체를 생성할 때 제네릭을 사용하여 String과 Integer만 입력받도록 하고 Student의 필드값 중 age만 Integer타입으로 하여 제네릭을 사용할 수 있습니다.
public class Student<T1, T2> {
    private T1 name;
    private T1 gender;
    private T2 age;

    public T1 getName() {
        return name;
    }

    public void setName(T1 name) {
        this.name = name;
    }

    public T1 getGender() {
        return gender;
    }

    public void setGender(T1 gender) {
        this.gender = gender;
    }

    public T2 getAge() {
        return age;
    }

    public void setAge(T2 age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name=" + name +
                ", gender=" + gender +
                ", age=" + age +
                '}';
    }
}

public class Example {
    public static void main(String[] args) {
        Student<String, Integer> student = new Student();

        student.setName("홍길동");
        student.setGender("M");
        student.setAge(25);

        System.out.println(student.toString());
        // Student{name=홍길동, gender=M, age=25}
    }
}

 

Bounded Type(바운디드 타입)


  • 제네릭으로 사용될 타입 파라미터의 범위를 제한할 수 있는 방법입니다.

<K extends T> / <? extends T>
  • T와 T의 자식 타입만 가능
public class Creature <T>{
    private List<T> creatures = new ArrayList<>();

    void add (T t){
        creatures.add(t);
    }

    public List<T> getCreatures() {
        return creatures;
    }
}

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}

public class Student extends Person {

    public Student(String name) {
        super(name);
    }
}

public class Teacher extends Person{

    public Teacher(String name) {
        super(name);
    }
}

public class Animal {
    private String name;

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

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}

public class Lion extends Animal {
   
    public Lion(String name) {
        super(name);
    }
}

public class Dog extends Animal{
    public Dog(String name) {
        super(name);
    }
}

public class Example {
    public static void main(String[] args) {
        Creature<Person> personList = new Creature<>();
        personList.add(new Person("홍길동"));
        personList.add(new Teacher("이순신"));
        personList.add(new Student("유관순"));

        Creature<Animal> animalList = new Creature<>();
        animalList.add(new Animal("호랑이"));
        animalList.add(new Lion("사자"));
        animalList.add(new Dog("강아지"));

        personPrint(personList);
        System.out.println();
        animalPrint(animalList);
    }

    public static void personPrint(Creature<? extends Person> creature){
        Stream stream = creature.getCreatures().stream();
        stream.forEach(item -> {
            System.out.println(item.toString());
        });
    }

    public static void animalPrint(Creature<? extends Animal> creature){
        Stream stream = creature.getCreatures().stream();
        stream.forEach(item -> {
            System.out.println(item.toString());
        });
    }
}

 

<K super T> / <? super T>
  • T와 T의 부모 타입만 가능 
public class Creature <T>{
    private List<T> creatures = new ArrayList<>();

    void add (T t){
        creatures.add(t);
    }

    public List<T> getCreatures() {
        return creatures;
    }
}

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}

public class Student extends Person {

    public Student(String name) {
        super(name);
    }
}

public class Teacher extends Person{

    public Teacher(String name) {
        super(name);
    }
}

public class Animal {
    private String name;

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

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}

public class Lion extends Animal {
   
    public Lion(String name) {
        super(name);
    }
}

public class Dog extends Animal{
    public Dog(String name) {
        super(name);
    }
}

public class Example {
    public static void main(String[] args) {
        Creature<Person> personList = new Creature<>();
        personList.add(new Person("홍길동"));
        personList.add(new Teacher("이순신"));
        personList.add(new Student("유관순"));

        Creature<Animal> animalList = new Creature<>();
        animalList.add(new Animal("호랑이"));
        animalList.add(new Lion("사자"));
        animalList.add(new Dog("강아지"));

        personPrint(personList);
        System.out.println();
        animalPrint(animalList);
    }

    public static void personPrint(Creature<? super Student> creature){
        Stream stream = creature.getCreatures().stream();
        stream.forEach(item -> {
            System.out.println(item.toString());
        });
    }

    public static void animalPrint(Creature<? super Animal> creature){
        Stream stream = creature.getCreatures().stream();
        stream.forEach(item -> {
            System.out.println(item.toString());
        });
    }
}

<?> : 와일드 카드
  • 모든 타입 가능 <? extends Object>와 같은 의미입니다.
  • 제네릭으로 구현된 메서드의 경우 선언된 타입으로만 매개변수를 입력해야합니다. 이를 상속받은 클래스 혹은 부모 클래스를 사용하고 싶어도 불가능하고 어떤 타입이 와도 상관없는 경우에 대응하기 힘듭니다. 이를 위한 해결책으로 와일드 카드를 사용합니다.

 

와일드 카드의 종류

 


Unbounded WildCard
  • List<?>와 같은 형태로 물음표만 가지고 정의할 수 있습니다. 내부적으로 Object로 정의되어서 사용되고 모든 타입의 인자를 받을 수 있습니다. 
Upper Bounded WildCard
  • List<? extends Person>와 같은 형태로 사용하고 특정 클래스의 자식 클래스만을 인자로 받는다는 의미입니다. Student 클래스를 상속받은 어떤 클래스가 와도 되지만 사용할 수 있는 기능은 Student 클래스에 정의된 기능만을 사용 가능합니다.
Lower Bounded WildCard
  • List<? super Student>와 같은 형태로 사용하고 Upper Bounded WildCard와는 다르게 특정 클래스의 부모 클래스만을 인자로 받습니다.

 

제네릭 메서드 만들기


 

Erasure


  • 제네릭은 타입의 안정성을 보장하며 실행시간에 오버헤드가 발생하지 않도록 하기 위해 추가되었습니다. 컴파일러는 컴파일 시점에 제네릭에 대하여 type erasure(타입 이레이저)라고 부르는 프로세스를 적용합니다.
  • 타입 이레이저는 모든 타입의 파라미터를 제거하고 나서 그 자리를 제한하고 있는 타입으로 변경하거나 타입 파라미터의 제한 타입이 지정되지 않았을 경우에 Object로 대체합니다. 따라서 컴파일 이후에 바이트 코드는 새로운 타입이 생기지 않도록 보장하는 일반 클래스들과 인터페이스, 메서드들만 포함합니다. Object 타입도 컴파일 시점에 적절한 캐스팅이 적용됩니다.

 

바인딩되지 않은 경우
  • 컴파일시 컴파일러는 바인딩되지 않은 형식 매개변수 E를 Object로 변경하게 됩니다.
public class Stack<E> {
    private E[] stackContent;

    public Stack(int capacity) {
        this.stackContent = (E[]) new Object[capacity];
    }

    public void push(E data) {
        // ..
    }

    public E pop() {
        // ..
    }
}

public class Stack {
    private Object[] stackContent;

    public Stack(int capacity) {
        this.stackContent = (Object[]) new Object[capacity];
    }

    public void push(Object data) {
        // ..
    }

    public Object pop() {
        // ..
    }
}

 

바인딩된 경우
  • 컴파일러는 바인딩 된 매개 변수 E를 첫번째 바인딩 된 클래스인 Comparable로 대체하게 됩니다.
public class BoundStack<E extends Comparable<E>> {
    private E[] stackContent;

    public BoundStack(int capacity) {
        this.stackContent = (E[]) new Object[capacity];
    }

    public void push(E data) {
        // ..
    }

    public E pop() {
        // ..
    }
}

public class BoundStack {
    private Comparable [] stackContent;

    public BoundStack(int capacity) {
        this.stackContent = (Comparable[]) new Object[capacity];
    }

    public void push(Comparable data) {
        // ..
    }

    public Comparable pop() {
        // ..
    }
}

 

Method Type Erasure


  • Method Type Erasure의 경우 method-level type erasure가 저장되지 않고 바인딩되지 않은 경우 부모 형식 Object로 변환되거나 바인딩 될 때 첫번째 바인딩 된 클래스로 변환합니다.
바인딩되지 않은 경우
  • 컴파일시 컴파일러는 Type parameter E를 Object로 변경하게 됩니다.
public static <E> void printArray(E[] array) {
    for (E element : array) {
        System.out.println(element);
    }
}

public static void printArray(Object[] array) {
    for (Object element : array) {
        System.out.println(element);
    }
}

 

바인딩된 경우
  • Type parameter E를 지우고 첫번째 바인딩된 클래스로 변경하게 됩니다.
public static <E extends Comparable<E>> void printArray(E[] array) {
    for (E element : array) {
        System.out.println(element);
    }
}

public static void printArray(Comparable[] array) {
    for (Comparable element : array) {
        System.out.println(element);
    }
}
728x90
반응형

'JAVA > JAVA기본' 카테고리의 다른 글

JAVA - String Constant Pool이란  (0) 2022.05.15
JAVA - 람다식이란?  (0) 2022.02.10
JAVA - 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O  (0) 2022.01.30
JAVA - 어노테이션이란?  (0) 2022.01.29
JAVA - Enum이란?  (0) 2022.01.29