Generics

yeolyeol·2024년 7월 22일
0

ssafy12

목록 보기
13/32
post-thumbnail

으악 내 주말


라이브 강의 #1

Generics

다양한 타입의 객체를 다루는 메서드, 컬렉션 클래스에서 컴파일 시에 타입을 체크
미리 사용할 타입을 명시해서 형 변환을 하지 않아도 되게 함.
→ 안정성 향상 및 형 변환의 번거로움 감소

일상으로 예를 들어보자.
우리 집은 기존에 하나의 분리수거 통에 재활용품을 담아 매주 일요일에 분리수거를 했다.
하나의 통에 담았기에 분리수거 장에 가면 하나하나 확인하며 분리해야 했다.

하지만 어느 날부터 페트병과 유리, 비닐, 종이 등의 분리된 통을 구매하여 사용했고
분리수거 날 각 통만 우르르 부으면서 간편하게 분리수거를 할 수 있게 되었다.

이처럼, 담을 수 있는 통을 재활용 타입마다 분리하여 담는 것.
이게 Generics의 특징이다.

사용

public interface Interface_Name<T>{}
이와 같이 메서드, 클래스, 인터페이스 이름 옆에 <>로 타입 파라미터를 지정해주면 된다.
타입 파라미터인 T를 넣어서 선언하는 것을 형인자 자료형
타입 파라미터가 없는 것을 무인자 자료형이라고 부른다.

그리고 객체 생성시 변수 쪽과 생성 쪽에는 타입 파라미터를 같게 해야한다.

ClassName<Integer> generic = new ClassName<Integer>();
ClassName<Integer> generic = new ClassName<>();
ClassName generic = new ClassName();

그럼 무조건 T 대신 타입을 넣어야 할까?
굳이 그러지 않아도 된다.
특히, 아직 타입을 지정하지 못할 때 사용한다.

class GenericBox<T> {
	private T some;
	
	public GenericBox() {
	}

	public GenericBox(T some) {
		super();
		this.some = some;
	}

	public T getSome() {
		return some;
	}

	public void setSome(T some) {
		this.some = some;
	}
	
}

public class UseBoxTest {

    public static void main(String[] args) {
        useGenericBox();
    }

    private static void useGenericBox() {
//    	GenericBox<T> box = new GenericBox<>();
    	GenericBox<String> box = new GenericBox<>(); // T 대신 타입을 명시
    	box.setSome("heehee"); // 제네릭으로 타입을 String으로 지정해 줘서 Object 타입이 아닌 String 타입으로 들어감.
//    	box.setSome(1); // 단, 다른 타입은 넣을 수 없음.
    	String some = box.getSome(); // 그래서 굳이 타입을 확인하고 형 변환 하지 않아도 됨
        // END
    }
}

⚠주의 사항⚠

  1. raw type의 사용과 @SuppressWarning
    @SuppressWarnings({"rawtypes"})GenericBox box = new GenericBox();의 사용을 지양한다.
    해당 객체에 담을 타입을 알 수 없기 때문이다.

  2. 헷갈리는 사용법

public class GenericAttention<I> {
    public void confusing() {
        GenericBox<Person> pbox = new GenericBox<>();

        pbox.setSome(new Person("피터빠커"));
        pbox.setSome(new SpiderMan());

        GenericBox<SpiderMan> sbox = new GenericBox<>();
//      pbox = sbox; // 제네릭 타입이 상속 관계여도 pbox와 sbox가 상속관계가 되는 것은 아님.

    }
}

이와 같이 Person 타입을 담는 GenericBox과 SpiderMan 타입을 담는 GenericBox는 형 변환이 불가능하다.
물론 pboxPerson객체를 상속받은 SpiderMan객체를 담을 수 있지만, 객체 자체를 변화 시키는 것은 불가능.

  1. static 사용 불가
    타입 파라미터는 객체를 생성하면서 전달된다.
    즉, 타입의 결정 시점은 객체 생성 시점이므로 static 멤버에서는 사용 불가

  2. Generic은 컴파일 타임에 지정한 타입으로 존재함
    → 즉, 런타임에는 타입의 정보가 삭제되어 단순 Object로 관리됨.
    그래서, 런타임에 동작하는 new, instanceof 키워드 사용 불가.

public class GenericAttention<I> {
    public void cantUseGeneric() {
        //I i = new I();//Cannot instantiate the type I

        GenericBox<SpiderMan> obj = new GenericBox<>();

        // compile error : Type Object cannot be safely cast to GenericBox<String>
        //if(obj instanceof GenericBox<String>) {  }

        if (obj instanceof GenericBox gb) {
            gb.setSome("Hello"); // 타입에 안전하지 않음, 타입이 깨져버림
            System.out.println("맞지만 타입에 안전하지 않음: " + gb.getSome());
        }

        if (obj instanceof GenericBox<?> gb) { // ? : 타입 파라미터가 적용된 GenericBox 객체인가?
            // gb.setSome("Hello"); // compile error
            System.out.println("이것이 최선: 뭐든 담기는 GenericBox");
        }
    }

    public static void main(String[] args) {
        GenericAttention<Integer> ga = new GenericAttention<>();
        ga.cantUseGeneric();
    }
}
  1. Generic을 이용한 배열 생성 불가
    배열은 runtime에 객체의 정보를 유지하고 동일한 타입의 객체만 처리.
public class GenericAttention<I> {
    public void genericArray() {
        // Generic 타입으로는 배열 생성 불가 
        // GenericBox<String>[] boxes1 = new GenericBox<String>[3]; // compile error
        // GenericBox<String>[] boxes2 = new GenericBox<>[3]; // compile error

        GenericBox<Person>[] boxes3 = (GenericBox<Person>[]) new GenericBox[3];
        boxes3[0] = new GenericBox<Person>();
        // TODO: 위코드가 의미 있게 된다면 어떤 불상사가 발생하는지 생각해보자.
        GenericBox<String> strbox = new GenericBox<String>("heel");
        boxes3[1] = (GenericBox) strbox;
        
     // boxes3는 Person타입의 GenericBox를 받아야 하는데
     // String 타입의 GenericBox도 들어올 수 있게됨.
        System.out.println(boxes3[1].getSome()); 
        // END
    }

    public static void main(String[] args) {
        GenericAttention<Integer> ga = new GenericAttention<>();
        ga.genericArray();
    }
}

Generic Method

파라미터와 리턴 타입으로 Type Parameter를 갖는 메서드
→ 리턴 타입 앞에 타입 파라미터 변수 선언

public class TypeParameterMethodTest<T> {
    T some;

    public TypeParameterMethodTest(T some) {
        this.some = some;
    }

    public <P> void method1(P p) {
        System.out.printf("클래스 레벨의 T: %s%n", some.getClass().getSimpleName());
        System.out.printf("파라미터 레벨의 P: %s%n", p.getClass().getSimpleName());
    }

    public <P> P method2(P p) {
        System.out.printf("클래스 레벨의 T: %s%n", some.getClass().getSimpleName());
        System.out.printf("파라미터 레벨의 P: %s%n", p.getClass().getSimpleName());
        return p;
    }

    public static void main(String[] args) {
        // 객체 생성 시점 - 클래스에 선언된 타입 파라미터 T의 타입 결정
        TypeParameterMethodTest<String> tpmt = new TypeParameterMethodTest<>("Hello");
        // 메서드 호출 시점 - 메서드에 선언된 타입 파라미터 P의 타입 결정
        tpmt.<Long>method1(20L); // 명시적으로 타입 결정
        tpmt.method2(10);        // 묵시적으로 타입 결정 
    }
}

결과 화면

클래스 레벨의 T: String
파라미터 레벨의 P: Long
클래스 레벨의 T: String
파라미터 레벨의 P: Integer

✨형한정 형인자

Generic에 extends를 사용하여 구체적인 타입 제한을 하는 것.

public class NumberBox<T extends Number> {
	public void addAll(T... ts) {
		double sum = 0;
		
		for(T t : ts) {
			sum += t.doubleValue();
		}
		
		System.out.println(sum);
	}
}

이와 같이 클래스 단계에서 타입 파라미터를 주는데,
T는 Number를 상속하는 타입으로 제한함.

+) 인터페이스로 제한할 경우에도 extends 사용
class TypeRestrict1<T extends List>{}
+) 인터페이스와 클래스를 같이 사용하여 제한할 경우 &로 연결
class TypeRestrict1<T extends Number & List>{}


라이브 강의 #2

✨와일드 카드(?) 자료형✨

내가 진짜 헷갈렸던 부분
실제 타입 파라미터가 무엇인지 모르거나 신경쓰고 싶지 않을 경우에 사용

구분표현설명
비 한정형 와일드카드 자료형Generic type<?>타입에 제한이 없음(Object)
한정형 와일드 카드 자료형(upper bounded) Generic type<? extends T>T or T의 자식 타입들만 가능
 (lowwer bounded) Generic type<? super T>T or T의 조상 타입만 가능

예시 코드

class GenericBox<T> {
	private T some;
	
	public GenericBox() {
	}

	public GenericBox(T some) {
		super();
		this.some = some;
	}

	public T getSome() {
		return some;
	}

	public void setSome(T some) {
		this.some = some;
	}
	
}

...

public class WildTypeTest {
    public void boxCallTest() {
        GenericBox<Object> boxObject = new GenericBox<>();
        GenericBox<Person> boxPerson = new GenericBox<>();
        GenericBox<SpiderMan> boxSpiderMan = new GenericBox<>();

     // notUseWildCardType(boxObject);
        notUseWildCardType(boxPerson);
     // notUseWildCardType(boxSpiderMan);

        // Type1
        useWildCardType1(boxObject);
        useWildCardType1(boxPerson);
        useWildCardType1(boxSpiderMan);
        
		// Type2
//      useWildCardType2(boxObject); // 못들어감
        useWildCardType2(boxPerson);
        useWildCardType2(boxSpiderMan);
        
		// Type3
        useWildCardType3(boxObject);
        useWildCardType3(boxPerson);
//      useWildCardType3(boxSpiderMan); // 못들어감
        
    }

    public void notUseWildCardType(GenericBox<Person> boxPerson) {
    }

    public void useWildCardType1(GenericBox<?> boxAll) {
//    	boxAll.setSome(new SpiderMan());
    	boxAll.setSome(null);
    	
    	Object obj = boxAll.getSome();

    }

    // 공급자(get), Person 타입의 GenericBox와 Person 자식 타입의 GenericBox가 파라미터
    public void useWildCardType2(GenericBox<? extends Person> boxExtendsPerson) {
//    	boxExtendsPerson.setSome(new Person("hello"));
//    	boxExtendsPerson.setSome(new SpiderMan());
//    	boxExtendsPerson.setSome(new Object());
    	boxExtendsPerson.setSome(null);
    	Person person = boxExtendsPerson.getSome();
    	Object object = boxExtendsPerson.getSome();
//    	SpiderMan spiderMan = boxExtendsPerson.getSome();
    }

    // 소비자(set)
    public void useWildCardType3(GenericBox<? super Person> boxSuperPerson) {
        System.out.println(boxSuperPerson.getClass());

        boxSuperPerson.setSome(new Person("HI"));
        boxSuperPerson.setSome(new SpiderMan());
        
        // super : 소비(사용)
    }
    public static void main(String[] args) {
        WildTypeTest wtt = new WildTypeTest();
        GenericBox<Object> boxObject = new GenericBox<>();
        GenericBox<Person> boxPerson = new GenericBox<>();
        GenericBox<SpiderMan> boxSpiderMan = new GenericBox<>();
        
        wtt.useWildCardType3(boxPerson);
        
        boxPerson.setSome(new SpiderMan());
        
    }
}

주석은 컴파일 에러가 뜨는 코드다.

그럼 이제 Type별로 설명을 해보겠다.

Type1

타입 파라미터로 <?> 즉, 모든 타입 파라미터의 GenericBox 객체를 받는 메서드다.
그럼 그 파라미터 변수 명인boxAll은 GenericBox<Object ~ 모든 타입>이 가능한 상태이다.
그런 상태에서 GenericBoxsetSome 메서드를 실행할 수 있을까?

런타임 시점에서 바라본다면 가능할지 몰라도, 컴파일 단계에서 에러가 난다.
즉, 들어오는 타입이 무엇인지 모르고 들어오는 타입과 setSome메서드에 들어오는 타입의 관계를 알 수 없기 때문이다.

그렇기에 모든 객체 타입의 초기 값인 nullsetSome메서드에 들어갈 수 있고
getSome 메서드를 통해 최상위 타입인 Object객체만 받아올 수 있다.

Type2

타입 파라미터의 한정형 형인자인 extends를 사용했다.
<? extends Person>Person 과 그 하위 클래스만 들어올 수 있다. 그렇기에 useWildCardType2(boxObject) 메서드는 파라미터 타입이 GenericBox<Object>라서 들어올 수 없다.

setSomegetSome도 확인해보자.
코드에서 볼 수 있는 것처럼 setSomeType1과 같은 이유로 null만 넣을 수 있다.
그럼 getSome은?
Type1과 다르게 들어올 수 있는 타입 파라미터의 최상위 클래스는 Person으로 제한이 되었기에 Person부터 상위 객체로 getSome을 받을 수 있다. (다형성)

Type3

한정형 형인자로 super 카워드가 들어갔다.
즉, Person이거나 그 상위 클래스를 담는 GenericBox클래스만 파라미터로 들어갈 수 있다.

그럼 setSomegetSome이 어떤 결과를 내는지 알아보자.
매개변수로 들어오는 타입 파라미터의 최소 객체는 Person인 점을 알 수 있다. 즉, 최소 Person이 들어온다는 것을 보장받을 수 있다.
그럼, Person클래스의 하위 클래스를 setSome메서드의 매개변수로 사용될 수 있다.
이게 가능한 이유는 하위 클래스가 최소 Person클래스로 형변환이 가능하기 때문이다.
getSome메서드는 나올 수 있는 객체는 Object클래스까지 가능하므로 기본적으로 Object타입이 반환된다.

Type별로 서로 용법이 다르다.
extends를 쓰는 Type2는 데이터를 외부로 제공하는 공급자 역할
suepr를 쓰는 Type3는 데이터를 사용하는 소비자 역할
Producer Extends Consumer Super (==) PECS


추가

enum

데이터 열거 타입 : 데이터가 몇 가지 한정된 값(주로 상수들)만을 갖는 형태로 구성된다.
java.lang.Enum을 내부적으로 상속 받은 형태의 특.별.한 클래스

특.별.한

그래서 enum타입을 작성할 때,

enum Grade{
	SALES, PART_TIME_JOB, NORMAL
}

처럼 class키워드 대신 enum키워드로 작성한다.
그리고 안에 있는 SALES 같은 것들은 enum 상수라고 부르며, 상수 선언 naming을 따라 작성한다.

사용

클래스와 동일하게 선언하며 할당될 수 있는 값은 enum 상수와 null이다.

Grade grade = Grade.SALES; // new 연산자는 사용하지 않음

System.out.println(grade);
System.out.println(grade instanceof Enum); // true
System.out.println(grade instanceof Object); // true

필요성

값 비교 시 단순히 값 뿐 아니라 타입에 대해서도 체크가 가능하다.
즉, 논리적인 오류를 방지할 수 있다.

public class EnumOrNot { 
    private static final int SALES = 1;
    private static final int PART_TIME_JOB = 2;
    private static final int NORMAL = 3;

    private static final int SPRING = 1;

    private void nonEnume(int grade) {
        if (grade == SALES) {
            System.out.println("영업 실적 반영");
        } else if (grade == PART_TIME_JOB) {
            System.out.println("근무 시간 반영");
        } else if (grade == NORMAL) {
            System.out.println("근로 계약 기준");
        }
    }

    public static void main(String[] args) {
        EnumOrNot eon = new EnumOrNot();
        eon.nonEnume(SALES);
        eon.nonEnume(SPRING); //논리적 오류
        
        // TODO: useEnume을 호출하면서 논리적인 오류를 발생시켜보자.

        // END
        
        eon.enumMethods();
    }
}

SALES와 SPRING이라는 상수가 모두 int타입의 1을 가리키고 있기 때문에,
nonEnume메서드에 들어가면 같은 결과를 출력한다.
이를 해결할 수 있는 방식이 enum이다.

public class EnumOrNot {

    enum Grade {
    	SALES, PART_TIME_JOB, NORMAL, SPRING
    }

    public static void main(String[] args) {
        Grade sales = Grade.SALES;
        Grade spring = Grade.SPRING;
        
        System.out.println(sales.equals(spring)); // false
    }

}

주로 ==이나 equals 메서드로 비교 연산이 가능하다.

주요 메서드

추가로 자체 멤버 변수와 생성자를 통해 enum상수를 초기화 할 수 있음.

public class EnumOperation {
    enum Greeting {
        GOOD_MORNING("좋은 아침"),
        GOOD_AFTERNOON("오후도 힘내"), 
        GOOD_EVENING("오늘도 수고했어");

        private String message;

        Greeting(String message) {
            this.message = message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public String getMessage() {
            return message;
        }
    }
    
    public static void main(String[] args) {
        Greeting g = Greeting.GOOD_AFTERNOON;
        System.out.println(g.getMessage()); // 오후도 힘내
    }
}

생성자는 언제나 private 접근 제한자를 가지며,
내부에서 enum 상수 선언 시 값 할당.


느낀점

자바 만든 개발자는 신임

profile
한 걸음씩 꾸준히

0개의 댓글

관련 채용 정보