JAVA 기초 (32) 제네릭(1)

코린이서현이·2023년 8월 11일
0

Java

목록 보기
33/46

🧐들어가면서🧐

제네릭프로그래밍 방법이란 여러 참조 자료형이 쓰일 수 이는 곳에 
특정한 자료형을 지정하지 않고, 클래스나 메서드를 정의한 후 사용하는 시점에 
어떤 자료형을 사용할 것인지 지정하는 방식이다.

📕 제네릭 프로그래밍 방식이란

  • 참조 자료형이 쓰일 수 있는 곳에 특정한 자료형을 지정하지 않고, 클래스나 메서드를 정의한 후, 사용하는 시점에 어떤 자료형을 사용할 것인지를 지정하는 방식이다.

제네릭 프로그래밍 방법에서 특정한 자료형을 대신하지 않고 일반화하는 자료형 매개변수와, 제네릭 클래스, 제네릭을 인스턴스화 해서 사용하는 방법.

📖 자료형 매개변수

🤔 앞에서 제네릭 프로그래밍은 사용하는 시점에, 어떤 자료형을 사용할지 지정한다고 했다.그렇다면 그 전에 클래스를 생성할 때, 클래스내부에서는 어떻게 자료형을 표현할까??
👉 바로 자료형 매개변수 T 를 사용한다.

  • 자료형 매개변수는 문자 하나로 지정한다. (보통 T로 지정한다.)
  • 제네릭 클래스 내부에서 T로 클래스 필드와, 메소드의 매개변수의 타입을 지정한다.

👍 좋은 자료형 매개변수

타입설명
T타입(Type)
E요소(Element), 예를 들어 List
K키(Key), 예를 들어 Map<k, v>
V리턴 값 또는 매핑된 값(Variable)
N숫자(Number)
S, U, V2번째, 3번째, 4번째에 선언된 타입

📖 제네릭 클래스

📒 제네릭 클래스 정의하기

  • 클래스 명에 <T>를 붙여 제네릭 클래스를 선언할 수 있다. 클래스 내부의 멤버 변수의 자료형과 매소드의 매개변수 타입은 식별자 기호 T로 쓴다.
class SchoolMembers<T> {		//클래스명 옆에 <T>를 붙임.
    T schoolMember ;			//클래스 멤버변수의 자료형을 T를 쓴다.
}

📒 제네릭 클래스 사용하기

//Teacher이란 클래스가 있다고 가정
SchoolMembers<Teacher> mat = new SchoolMembers<>();
  • 제네릭 클래스를 사용할 때는 사용할 타입명을 다이아몬드 연산자<>내에 할당해서 인스턴스화한다.

👉 타입의 구체화가 일어난다.
자료형 매개변수 T가 전달받은 타입으로 변환되어 생성된다!!

📖 제네릭의 특징

📒 1. 제네릭에서 할당 받을 수 있는 자료형은 참조자료형

  • int형이나 double형처럼 원시형은 할당 받을 수 없고, 오직 참조 자료형만 가능하다.

📒 2. 복수 타입 파라미터

  • 타입 지정이 여러개가 필요한 경우 제네릭을 여러개 사용할 수 있다. 제네릭 타입의 구분은 꺽쇠 괄호 안에서 쉽표(,)로 하며 <T, U> 와 같은 형식을 통해 복수 타입 파라미터를 지정할 수 있다. 그리고 당연히 클래스 초기화할때 제네릭 타입을 두개를 넘겨주어야 한다.
class Apple {}
class Banana {}

class FruitBox<T, U> {
    List<T> apples = new ArrayList<>();
    List<U> bananas = new ArrayList<>();

    public void add(T apple, U banana) {
        apples.add(apple);
        bananas.add(banana);
    }
}

public class Main {
    public static void main(String[] args) {
    	// 복수 제네릭 타입
        FruitBox<Apple, Banana> box = new FruitBox<>();
        box.add(new Apple(), new Banana());
        box.add(new Apple(), new Banana());
    }
}

📒 3. 업캐스팅 적용 가능

제네릭 자료 매개변수에 참조자료형인 클래스가 올 수 있고, 클래스끼리 상속을 통해 관계를 맺는 객체 지향 프로그래밍의 다형성 원리가 그대로 적용이 된다.
즉 업캐스팅이 적용이 된다는 것뜻이다.

class Fruit { }
class Apple extends Fruit { }
class Banana extends Fruit { }

class FruitBox<T> {
    List<T> fruits = new ArrayList<>();

    public void add(T fruit) {
        fruits.add(fruit);
    }
}

public class Main {
    public static void main(String[] args) {
        FruitBox<Fruit> box = new FruitBox<>();
        
        // 제네릭 타입은 다형성 원리가 그대로 적용된다.
        box.add(new Fruit());
        //Apple과 Banana는 Fruit를 상속받아 업캐스팅이 적용된다.
        box.add(new Apple());
        box.add(new Banana());
    }
}

📒 4. 중첩 자료 매개변수형이 가능

  • 제네릭 인스턴스를 제네릭 타입 파라미터로 전달할 수 있다.
public static void main(String[] args) {
    // LinkedList<String>을 원소로서 저장하는 ArrayList
    ArrayList<LinkedList<String>> list = new ArrayList<LinkedList<String>>();

    LinkedList<String> node1 = new LinkedList<>();
    node1.add("aa");
    node1.add("bb");

    LinkedList<String> node2 = new LinkedList<>();
    node2.add("11");
    node2.add("22");

    list.add(node1);
    list.add(node2);
    System.out.println(list);
}

👉 실행화면

[[aa, bb], [11, 22]]

📖 제네릭 사용시 장점

🧐 제네릭이 생기기 전 클래스의 가장 상위클래스인 Object타입으로 업캐스팅을 이용해 다양한 참조 자료형을 사용했다.
하지만! 클래스의 메소드로 Object객체를 반환할 경우 형변환을 해야하고, 런타임 에러가 발생할 수 있다.

⚠️ 런타임 오류로 이어진다.(컴파일 단계에서 에러가 발생하지 않고 런타임때 에러 발생) → 타입이 안전하지 않다.

👉 제네릭을 사용하면서 불필요한 형변환을 줄여 성능을 줄이고, 런타임 에러를 방지할 수 있다.

📖 제네릭 사용시 주의사항

📒 1. 제내릭 타입의 객체는 생성이 불가능

  • 제네릭 타입 자체로 타입을 지정해서 객체를 생성할 수 는 없다.
//이렇게 사용할 수는 없음
T t = new T();

📒 2. static메소드나 멤버에 제네릭 타입이 올수 없다.

  • static 메소드나 멤버는 클래스가 동일하게 공유하는 변수로서 제네릭 객체가 생성되기도 전에 이미 자료 타입이 정해져 있어야 하기 때문에 이후 지정되는 제네릭이 올 수 없다.

⚠️ 제네릭 메소드는 static이 가능하다.

📒 제네릭 배열

class Sample<T> {
}

public class Main {
    public static void main(String[] args) {
		//이건 오류가 나지만 
        Sample<Integer>[] arr1 = new Sample<>[10];

        //이건 오류가 나지 않는다..?! 이왜진?
        Sample<Integer>[] arr2 = new Sample[10];

        // 제네릭 타입을 생략해도 위에서 이미 정의했기 때문에 Integer 가 자동으로 추론됨
        arr2[0] = new Sample<Integer>();
        arr2[1] = new Sample<>();


    }
}

☢️필수로 추가 공부하기☢️
더 알아보기
더 알아보기

📖 제네릭 인터페이스

인터페이스에서도 인터페이스명 옆에 제네릭 타입 매개변수를 붙여 제네릭 인터페이스를 사용할 수 있다.

📖 제네릭 메소드

  • 메서드의 반환형 왼쪽에 <T>가 선언된 메서드이다. <T>로 메서드에 직접 타입을 지정하면서, 독립적으로 운용가능한 제네릭 메서드이다.

⚠️ 제네릭 클래스에서 제네릭 메소드가 지정되었다고 하더라도 서로의 제네릭 자료형매개변수는 관련이 없다. 제네릭 메소드는 자신의 <T>를 통해서 독립적으로 운용한다.
👉 제네릭 클래스의 <T> ≠ 제네릭 메서드의 <T>

class FruitBox<T> {
	
    // 클래스의 타입 파라미터를 받아와 사용하는 일반 메서드
    public T addBox(T x, T y) {
        // ...
    }
    
    // 독립적으로 타입 할당 운영되는 제네릭 메서드
    public static <T> T addBoxStatic(T x, T y) {
        //이때의 T는 제네릭 메서드의 T와 달라도 상괍없다.
    }
}

📒 제네릭 메소드 호출하기

  • 메서드 왼쪽에 자료형을 위치한다. (선언위치와 동일)
class FruitBox<T, U> {
    static <T> void printFruitBox(T t){
        System.out.println(t);
    }
}

public class GenericTest {
    public static void main(String[] args) {
        FruitBox.<String>printFruitBox("과일박스");
    }
}

하지만! 제네릭메서드에 들어가는 매개변수를 통해 메서드의 자료형매개변수를 추정할 수 있기 때문에 이런경우 생략할 수 있다.

FruitBox.printFruitBox("과일박스");

📒 제네릭 메서드는 static을 붙여서 사용할 수 있다.

위의 예시에서 보듯이 제네릭 메서드는 static을 붙일 수 있다.
제네릭 메서드는 독립적으로 운용되기 때문이다.

⚠️ 제네릭 메서드는 static 가능!!

📖 제네릭 타입 한정 키워드

제네릭 클래스에 올 수 있는 참조자료형을 extends를 통해 제한할 수 있다.
이때 extends는 클래스 상속의 뜻이 아닌 해당 참조 자료형으로 한정한다는 뜻으로 뒤에 일반 클래스, 추상클래스, 인터페이스 모두 올 수 있다.

➕ 제네릭을 쓰는 이유는 참조 자료형을 자유롭게 하기 위한것으로 당연히 한 클래스로만 한정하면 제네릭을 쓰는 이유가 없을 것이다.
따라서 제네릭의 가능한 찹조 자료형을 하위클래스를 여러개 가진 상위클래스로 한정시키거나, 인터페이스를 구현한 클래스만 가능하게 한다.

interface Readable {
}

// 인터페이스를 구현하는 클래스
public class Student implements Readable {
}
 
// 인터페이스를 Readable를 구현한 클래스만 제네릭 가능
public class School <T extends Readable> {
}

📒 다중 타입 한정

  • 만일 가능한 자료형을 2개 이상의 타입을 동시에 상속한 경우로 타입을 제한하고 싶다면 &연산자를 사용할 수 있다.
  • 단 자바에서는 다중상속을 지원하지 않기 때문에, &연산자로 묶이는 참조자료형은 인터페이스만 가능하다.

⚠️ 한 클래스를 동시에 상속한 클래스는 존재하지 않는다.

➕ 제네릭 자료 매개변수형 생략가능

  • 제네릭 메소드를 사용할 때, 매개변수로 사용할 참조 자료형을 전달하거나 제네릭 인스턴스 생성시 다이아몬드 연산자내에 괄호 제내릭 타입을 지정하기 때문에 둘 중 한군데는 생략할 수 있다.
profile
24년도까지 프로젝트 두개를 마치고 25년에는 개발 팀장을 할 수 있는 실력이 되자!

0개의 댓글