제너릭스 / 열거형 / 어노테이션

주8·2023년 1월 26일
0

제너릭스(Generics)

  • java 1.5 버전부터 지원되는 기능으로 컴파일시 타입을 체크해 주는 기능(compile-time type check)이다.
  • 클래스나 메서드 선언시 사용할 내부 데이터 타입을 컴파일시 미리 지정하고, 컬렉션 선언시에도 저장할 특정 타입을 지정할 수 있게 되었다.
  • 제너릭스 기능 제공으로 객체의 타입 안전성을 제공하고, 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해졌다(기본형 타입은 사용할 수 없다).
  • Java 1.5 이전에는 일반적으로 클래스, 메서드, 컬렉션에서 여러 타입을 핸들링 해야할 경우 Object 타입으로 사용했었고, Object 타입으로 객체를 전달받은 곳에서 원하는 타입으로 캐스팅을 해야 했었고, 그 과정에서 타입 캐스트 오류가 발생했었다.
  • 컬렉션 클래스<저장할 객체의 타입> 변수명 = new 컬렉션클래스<저장할 객체의 타입>();
    • ArrayList<Integer> intList = new ArrayList<Integer>();
    • <>안에 지정한 타입으로 저장되어야 하며, 그렇지 않을 경우에는 컴파일 에러가 발생한다.
    • 객체를 꺼낼 때는 형변환할 필요가 없다.
    • 만약 <> 안의 타입을 상위클래스 타입으로 하면 하위클래스를 저장할 수는 있으나 꺼낼 때 형변환이 필요하다.
    • 저장된 객체에 다형성을 사용하려면 와일드 카드 ?를 사용하면 된다. 와일드 카드를 사용할 경우 하나 이상의 타입을 지정할 수 있다. 아래의 예제처럼 지정을 하게 되면 Product의 하위타입도 매개변수로 넘겨줄 수 있다.
public static void printAll2(ArrayList<? extends Product> list){
	for(Product p : list){
		System.out.println(p);
	}
}

  • 타입 안정성: 제너릭스에서는 단일 유형의 객체만 보유할 수 있고, 다른 객체를 저장할 수 없다. 제너릭스가 없으면 모든 유형의 객체를 저장할 수 있다.
  • 타입 캐스팅이 필요하지 않다. Generics를 사용하지 않을 경우 타입을 지정해야 하는데 컴파일 타임에 체크가 안 되며, 잘못 지정한 경우 ClassCastException이 발생한다.
  • Compile-Time 체크: 컴파일 타임에 체크하므로 런타임에 문제가 발생하지 않는다. 런타임보다 컴파일타임에 문제를 처리하는 것은 좋은 개발 전략이다.
public class GenericsVSNonGenericsEx1{
	public static void main(String[] args){
		//Generics를 지정하지 않을 경우 모든 객체를 저장
		List list1 = new ArrayList();
		list1.add(10);
		list1.add("10");
		//컬렉션의 get() 메서드들은 리턴타입이 Object라서 캐스팅이 필요하다
		Integer i = (Integer)list1.get(0);
		System.out.println(i);

		//Generics를 사용했을 경우
		List<Integer> list2 = new ArrayList<Integer>();
		list2.add(10);
		//list2.add("10"); //compile-time error
		Integer gi = list2.get(0); //형변환 필요 없음
		System.out.println(gi);
	}
}

제너릭스 선언

  • T는 타입변수(type variable) 또는 타입 매개변수라고 하며, 임의의 참조형 타입을 의미한다.
  • T 외에도 다른 문자를 사용해도 상관없다.
  • 클래스를 작성할 때, Object 타입 대신 T와 같은 타입변수를 사용한다.
  • 참조변수, 생성자에 T 대신 실제 타입을 지정하면 형변환이 생략 가능하다.
  • AbstractDAO<T>: Generic class로 ‘T의 AbstractDAO’ 또는 ‘T AbstractDAO’라고 한다.
  • T: 타입 변수 또는 타입 매개변수라고 하며, 배열 타입변수는 허용되지 않는다.
  • AbstractDAO: raw type
  • Java 7버전부터는 인스턴스 생성시 타입을 추정할 수 있는 경우 타입 생략 가능
class GenericsClassEx2<T>{
	T element;
	void setElement(T element){
		this.element = element;
	}
	T getElement(){
		return element;
	}
}
...
GenericsClassEx2<String> gc = new GenericsClassEx2<String>();
GenericsClassEx2<String> gc = new GenericsClassEx2<>(); //java 7부터 가능

제너릭스 다형성

  • 타입변수의 다형성: 예제처럼 특정 클래스를 상속하고 있는 하위클래스는 컬렉션 객체 생성시 상위클래스 타입으로 선언하여 해당 타입외에 객체는 저장할 수 없다.
class LandAnimal{
	public void crying(){
		System.out.println("육지동물");
	}
}

class Cat extends LandAnimal{
	public void crying(){
		System.out.println("냐옹냐옹");
	}
}

class Dog extends LandAnimal{
	public void crying(){
		System.out.println("멍멍");
	}
}

class Sparrow{
	public void crying(){
		System.out.println("짹짹");
	}
}

class AnimalList<T>{
	ArrayList<T> al = new ArrayList<T>();
	void add(T animal) {
		al.add(animal);
	}
	T get(int index) {
		return al.get(index);
	}
	boolean remove(T animal) {
		return al.remove(animal);
	}
	int size() {
		return al.size();
	}
}

public class GenericsPolymorphicEx1 {
	public static void main(String[] args) {
		AnimalList<LandAnimal> landAnimal = new AnimalList<>();
		landAnimal.add(new LandAnimal());
		landAnimal.add(new Cat());
		landAnimal.add(new Dog());
		// landAnimal.add(new Sparrow()); // 오류가 발생함.

		for (int i = 0; i < landAnimal.size() ; i++) {
			landAnimal.get(i).crying();
		}
	}
}

[결과]
육지동물
냐옹냐옹
멍멍

제너릭스 메서드(Generics Method)

  • 제너릭스 클래스와 마찬가지로 모든 유형의 데이터에 사용할 수 있는 메서드를 개발할 수 있는데 이것을 제너릭스 메서드라고 한다.
  • 메서드의 선언부에 타입 변수를 사용한 메서드를 의미한다.
class GenericsMethodClass {
	// generics method 생성
	public <T> void genericsMethod(T data) {
		System.out.println("Generics Method Data Passed: " + data);
		System.out.println("매개변수 타입 : " + data.getClass().getName()+"\n");
	}
}
public class GenericsMethodEx1 {
	public static void main(String[] args) {
		GenericsMethodClass demo = new GenericsMethodClass();
		// generics method의 매개변수가 String 타입으로 동작됨
		demo.<String>genericsMethod("제너릭스 메서드");
		// generics method의 매개변수가 Integer 타입으로 동작됨
		demo.<Integer>genericsMethod(25);
		// 이 경우 컴파일러는 메서드에 전달된 값을 기반으로 형식 매개 변수를 일치시킬 수 있습니다.
		demo.genericsMethod(50);
	}
}

[결과]
Generics Method Data Passed: 제너릭스 메서드
매개변수 타입 : java.lang.String
Generics Method Data Passed: 25
매개변수 타입 : java.lang.Integer
Generics Method Data Passed: 50
매개변수 타입 : java.lang.Integer

Generics Collections

  • Iterator<E>: Iterator에도 제너릭스가 적용된다.
  • Comparable<T>과 Collections.sort()
    • 클래스의 기본 정렬기준을 구현하는 Comparable 인터페이스에도 제너릭스가 적용된다.
    • 선수들의 총점을 내림차순으로 기본정렬해서 출력하는 간단한 소스
  • HashMap<K,V>
    • HashMap은 key와 value의 쌍 구조로 저장하는 컬렉션 클래스로, 지정해줘야 할 타입이 두 개다.
    • 두 개의 타입을 ‘,’로 구분한다.
    • 저장된 값을 가져올 때 형변환을 하지 않아도 된다.
      StudentHash s1 = map.get(”1-1”);

와일드 카드 ‘?’

  • 제너릭 타입에 와일드 카드를 사용하면 여러 타입을 대입해서 사용할 수 있다.
  • 이름에 제한을 두지 않는다.
  • <? extends T>: T와 그 자손 타입만 가능하다.
  • <? super T>: T와 그 조상 타입만 가능하다.
  • <?>: 제한이 없으며, 모든 타입이 가능하다. <? extends Object>와 동일하다.
  • Person을 상속하는 Man, Woman 이라는 클래스가 있다고 가정하면 아래와 같이 매개변수에 Person을 상속하는 타입을 매개변수로 전달할 수 있다.
void add(List<? extends Person> list)
  • add() 메서드의 매개변수로 Person을 상속하고 있는 Man, Woman 타입으로 파라미터 전달 가능, 그 외의 타입은 불가능하다.
List<Man> mlist = new ArrayList<Man>();add(mlist);

제너릭스 상속관계 선언

  • 만일 매개변수가 여러개면서 extends 타입이 동일할 경우 아래와 같이 간략히 할 수 있다.
    (만약 Product가 클래스가 아닌 interface라도 implements가 아닌 extends를 사용해야 한다)
  • 클래스와 인터페이스를 동시에 상속받고 구현해야 한다면 & 기호를 사용하면 된다.
interface WarmBlood{...}
...
// implements 키워드를 사용해서는 안됨
class AnimalList<T extends WarmBlood> { ... }

// 클래스와 인터페이스를 동시에 상속받고 구현해야 한다면 & 사용
class AnimalList<T extends LandAnimal & WarmBlood> { ... }
public static void printAll2(ArrayList<? extends Product> list, ArrayList<? extends Product> list2){
	for(Product p : list){
		System.out.println(p);
	}
}public static <T extends Product> void printAll2(ArrayList<T> list, ArrayList<T> list2){
	for(Product p : list){
		System.out.println(p);
	}
}

열거체(Enumeration type)

  • Java 1.5버전부터 열거체를 정의한 Enum 클래스를 사용할 수 있다.
  • 장점
    • 열거체를 비교할 때 실제 값 뿐만 아니라 타입까지도 체크한다.
    • 열거체의 상수값이 재정의되더라도 재컴파일할 필요가 없다.
  • 관련된 상수들을 같이 묶어 놓은 것이다.
  • 열거형 정의 방법: enum 열거형명 {상수명1, 상수명2, …}
  • 열거체의 첫번째 상수값을 0부터 설정되어 옆의 정의된 값 순으로 1씩 증가된다.
  • 선언 및 방법
enum Colors{RED, ORANGE, YELLOW, GREEN, BLUE}
class EnumTest{
	Colors col; //열거형 인스턴스 변수
	void test(){
		col = Colors.BLUE;
	}
}
  • 열거형은 값의 비교도 가능하다(==, compareTo() 사용 가능).
if(col == Colors.BLUE)

예제

enum Colors{RED,ORANGE,YELLOW,GREEN,BLUE}

public class EnumEx01{
	public static void main(String[] args){
		Colors[] arr = Colors.values();
		for(Colors rb : arr){
			System.out.println(rb);
		}
		System.out.println("--------------");
		Colors.cols = Colors.valueOf("GREEN");
		System.out.println(cols);
	}
}

[결과]
RED
ORANGE
YELLOW
GREEN
BLUE
--------------
GREEN

  • 상수값을 별도 지정하고 싶을 경우 괄호{ }를 추가하고 지정하면 된다.
  • 이때 인스턴스 변수와 생성자를 별도로 추가해야 한다.
public enum Rainbow{
	RED(3), ORANGE(10), YELLOW(21), GREEN(5), BLUE(1), INDIGO(-1), VIOLET(-11);

	//인스턴스 변수 선언
	private final int value;
	//생성자 선어
	Rainbow(int value) {this.value = value;}
	public int getValue(){return value;}
}

  • java.lang.Enum 클래스는 모든 자바 열거체의 공통 조상 클래스이다.
  • Enum 클래스는 열거체를 컨트롤하기 위한 다양한 메서드를 포함하고 있다.
  • values() 메서드
    • 해당 열거체의 모든 상수를 저장한 배열을 생성하여 반환한다.
    • 자바의 모든 열거체를 컴파일러가 자동으로 추가해주는 메서드이다.
  • valueOf() 메서드
    • 전달된 문자열과 일치하는 해당 열거체의 상수를 반환한다.
  • ordinal() 메서드
    • 해당 열거체 상수가 열거체 정의에서 정의된 순서(0부터 시작)를 반환한다.
    • 반환되는 값은 열거체 정의에서 해당 열거체 상수가 정의된 순서이지 상수값은 아니다.
enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }
public class EnumTestEx1 {
	public static void main(String[] args) {
		Color[] arr = Color.values();
		for (Color c : arr) {
			System.out.println(c);
		}

		Color cs = Color.valueOf("GREEN");
		System.out.println(cs);

		int idx = Color.YELLOW.ordinal();
		System.out.println(idx);
	}
}

[결과]
RED
ORANGE
YELLOW
GREEN
BLUE
INDIGO
VIOLET
GREEN
2

어노테이션(Annotation)

  • 주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공한다. (Annotation은 interface이다)
  • 어노테이션은 메타데이터를 나타내는 태그인데 클래스, 인터페이스, 메서드 또는 필드에 첨부되어 Java 컴파일러 및 JVM에서 사용할 수 있는 몇 가지 추가정보를 나타낸다.
  • 이 어노테이션은 추가 정보를 제공하는 과거 XML의 대체 옵션이다.
  • 어노테이션 사용 예
@Test //해당 메소드가 단위 테스트임을 명시하는 어노테이션이다
private void runTest(){...}
  • *이 붙은 것은 메타 어노테이션 그 외는 Java 기본 제공 주석
  • Java 8버전부터는 클래스, 메서드, 인터페이스 필드 등의 요소 선언 위에 어노테이션을 배치(Annotation placement)할 수 있다.
어노테이션설명
@Override컴파일러에게 오버라이딩하는 메서드라는 것을 알린다.
@Deprecated앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.
@SuppressWarnings컴파일러의 특정 경고메시지가 나타나지 않게 해준다.
@SafeVarargs제너릭스 타입의 가변인자에 사용한다. (jdk 1.7)
@FunctionalInterface함수형 인터페이스라는 것을 알린다. (jdk 1.8)
@Nativenative 메서드에서 참조되는 상수 앞에 붙인다. (jdk 1.8)
@Target*어노테이션이 적용가능한 대상을 지정하는데 사용한다.
@Documented*어노테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다.
@Inherited*어노테이션이 자손 클래스에 상속되도록 한다.
@Retention*어노테이션이 유지되는 범위를 지정하는데 사용한다.
@Repeatable*어노테이션을 반복해서 적용할 수 있게 한다. (jdk 1.8)
  • 내장 어노테이션(Built-in Annotation): 기본으로 제공되는 주석이다.
    • @Override, @SuppressWarnings, @Deprecated

커스텀 어노테이션(Custom Annotation): 사용자 정의 어노테이션

  • 어노테이션 유형: Marker Annotation, Single-Value Annotation, Multi-Value Annotation
  • Marker Annotation: 메서드가 없는 주석을 마커 어노테이션이라고 한다. @Override, @Deprecated가 마커 어노테이션이다.
@interface MyAnnotation()
  • Single-Value Annotation: 하나의 메서드가 있는 주석을 단일 값 주석이라고 한다.
    기본값도 제공할 수 있다.
@interface MyAnnotation(){
	int value() default 0;
}

//사용 예 @MyAnnotation(value=20)
  • Multi-Value Annotation: 두 개 이상의 메서드가 있는 주석을 다중 값 주석이라고 한다.
@interface MyAnnotation{
	int value1() default 1;
	String value2() default "";
	String value3() default "i";
}

//사용 예 @MyAnnotation(value1=10,value2="gil Dong",value3="xxx")

주의사항

  • 메서드에서 throws 절이 없어야 한다.
  • 메서드에 매개변수가 없어야 한다.
  • 주석을 정의하려면 interface 키워드 바로 옆에 @을 붙여야 한다.
  • 메서드에 기본값을 할당할 수 없다.
profile
웹퍼블리셔의 백엔드 개발자 도전기

0개의 댓글