Java 5 Tiger 버전부터 Annotation이라는 녀석이 등장합니다.
Annotation.
사전에 있는 발음은 [æ̀nətéiʃən] 이라고 되어있군요. 애너테이션이라고 읽으면 됩니다.
(어노테이션이라고도 많이 읽더군요)
다시 사전을 빌리면 "주석(을 달기)"라는 뜻입니다.
소스코드에 늘상 달아왔던 라인 주석(//)이나 블럭 주석(/*... */)이 있는데 또 주석이라니.
애너테이션은 위의 주석과는 조금 다른 성격의 주석입니다.
클래스나, 멤버 변수, 멤버 메서드, 지역 변수, 매개 변수 등 구성요소에 '부착'되는 주석입니다.
아래 소스코드를 보겠습니다.
public class AnnotationSample{ @Annotation private String greetings = "안녕하세요"; ... 이하 생략 }
위의 소스에서 greetings 라는 멤버 변수위에 써있는 @Annotation 이 부분이 애너테이션을 사용한 예입니다.
단순하게 greetings 라는 멤버 변수에 Annotation이라는 꼬리표를 달았다고 생각하면 됩니다.
하지만 저렇게 애너테이션을 달았다고 해서 어떤 기능이 생기는 것은 아닙니다.
우리가 사용하는 제품들에 바코드가 붙어있지만 이는 아무런 기능도 하지 않듯이 말이죠.
바코드 리더로 찍어보기 전에는 아무런 의미가 없습니다.
잠깐 숨을 돌려 김춘수 시인의 "꽃"을 읽어볼까요?
김춘수 - 꽃 내가 그의 이름을 불러 주기 전에는 그는 다만 하나의 몸짓에 지나지 않았다. 내가 그의 이름을 불러주었을 때 그는 나에게로 와서 꽃이 되었다. 내가 그의 이름을 불러준 것처럼 나의 이 빛깔과 향기에 알맞는 누가 나의 이름을 불러다오 그에게로 가서 나도 그의 꽃이 되고 싶다 우리들은 모두 무엇이 되고 싶다 너는 나에게 나는 너에게.. 잊혀지지 않는 하나의 눈빛이 되고 싶다..
바코드를 읽는 바코드 리더기와 같이 애너테이션이 부착된 구성요소는 자바의 리플렉션을 이용하여 기능을 부여할 수 있습니다.
이는 애너테이션의 활용에서 다루도록 하겠습니다. 아직 애너테이션에 대해 할 얘기가 많이 남았으니까요 :D
Sample 클래스니 Foo니 Bar니 하는 건 재미없으니까 목표가 있는 예제를 따라 막무가내로 시작해보겠습니다.
먼저 우리가 다룰 학생 데이터 클래스인 Student 클래스가 아래와 같이 생겼습니다.
public class Student { private String name; // 이름 private String number; // 학번 public Student(String name, String number){ this.name = name; this.number = number; } // 게터, 세터 생략 }
그리고 가짜 DB 역할을 해줄 MockDB 클래스는 이렇게,
import java.util.HashMap; import java.util.Map; public class MockDB { // DB 역할을 할 Map private static Map<String, Student> students; // 임의 데이터로 초기화하기 static { students = new HashMap<String, Student>(); Student student = null; String[] names = { "김태희", "이병헌", "최민식", "아이유♡", "전지현", "하정우"}; for(int i = 0 ; i < names.length ; i++){ student = new Student(names[i], Integer.toString(i)); students.put(student.getNumber(), student); } } // 학번을 이용하여 학생을 얻어오는 메서드 public static Student getStudentByNumber(String number){ return students.get(number); } }
학생 관리를 하는 StudentManager 클래스는 요렇게 생겼습니다.
public class StudentManager { private Student student0; // 학번이 0인 학생 private Student student1; // 학번이 1인 학생 private Student student2; // 학번이 2인 학생 private Student iu; // 학번이 3인 아이유♡ 학생 public StudentManager(){ this.student0 = MockDB.getStudentByNumber("0"); this.student1 = MockDB.getStudentByNumber("1"); this.student2 = MockDB.getStudentByNumber("2"); this.iu = MockDB.getStudentByNumber("3"); } public void printStudentName() { System.out.println(student0.getName()); System.out.println(student1.getName()); System.out.println(student2.getName()); System.out.println(iu.getName()); } }
StudentManager 클래스는 좀 무식하게 생겼군요.. 애너테이션을 이용한 효율적 작업의 예를 들기 위해
저렇게 무식하게 써봤습니다. 위의 무식한 StudentManager 클래스를 최종적으로는
애너테이션을 이용하여 아래와 같이 바꿀 예정입니다.
public class StudentManager { @Wired private Student student0; // 학번이 0인 학생 @Wired private Student student1; // 학번이 1인 학생 @Wired private Student student2; // 학번이 2인 학생 @Wired(number="3") private Student iu; // 학번이 3인 아이유♡ 학생 public void printStudentName() { System.out.println(student0.getName()); System.out.println(student1.getName()); System.out.println(student2.getName()); System.out.println(iu.getName()); } }
@Wired가 추가되고 MockDB로부터 학생 객체를 가져오는 작업이 없어졌습니다.
네번째에 등장한 Wired 애너테이션은 조금 다르게 생기긴 했군요. 일단은 가볍게 무시하고 넘어가줍니다.
물론 저 상태에서 printStudentName() 메서드를 호출했을 때 NullPointerException은 발생하지 않도록 만들겁니다.
가장 먼저 해야할 작업은 Wired 라는 이름을 가진 애너테이션을 만들어주는 일이겠군요.
애너테이션 역시 .java의 확장자를 가지고 있으며 정의는 아래와 같습니다.
public @interface Wired { // 아무런 내용이 없어도 @Wired로 사용할 수 있습니다! }
이렇게 아무런 애너테이션 변수없이(텅빈 애너테이션)을 마커(Marker) 애너테이션이라고 합니다.
변수가 하나라면? Single-value 애너테이션이라고 하고,
그 이상의 변수를 가진 애너테이션은 Full 애너테이션이라고 합니다.
하지만 위의 변경된 StudentManager에서 네 번째에 등장하는 @Wired(number="3")을 보면 알 수 있듯이
number라는 변수를 하나 추가해주어야겠군요.
public @interface Wired { // public String number(); // 기본값을 가지는 애너테이션 변수 public String number() default ""; }
주석 처리된 public String number(); 문장과
주석처리 되지 않은 public String number() default ""; 이 두개의 문장이 있습니다.
default 라는 키워드에서 딱 와닿는게 있으신가요?
default 값을 주지 않으면 항상 number 라는 애너테이션 변수에 값을 주어야만 합니다.
default 값이 주어진 애너테이션 변수는 값을 생략하면 default 값을 갖게 됩니다.
다시 말하면 default 값을 주지 않은 변수는 항상 @Wired(number="학번")의 형식으로 사용해야만 하고
default값을 준 변수는 @Wired, @Wired(number="학번") 두 형식 모두 사용할 수 있으며
값을 주지 않았을 때는 default 값인 빈 문자열("")이 number에 주어지게 됩니다.
여기까지 작성했다면 StudentManager 클래스의 멤버 변수들을 꼬리표로 나타내면 아래와 같겠군요
@Wired(number="3") 이라고 애너테이션을 붙여준 우윳빛깔 iu에만 number의 값이 3이고 나머지는 기본값인 빈 문자열("")을 가지고 있습니다.
횡설수설한 터라 여기까지 잘 따라오셨는지 모르겠습니다.
어쩌면 정갈한 다른 분들의 글을 참고하시는 게 더 빠를지도 모르겠군요..
그런 의미로 IBM Developers의 기술 자료를 링크해 드리겠습니다. (한글 문서입니다)
Annotations in Tiger, Part 1- ☞ http://www.ibm.com/developerworks/kr/library/j-annotate1/
지금까지 작성한 예제로는 아직은 애너테이션이 꼬리표로만 기능을 하고 그 이상의 기능을 가지진 않습니다.
더 얘기할 게 남았습니다. 그 얘기가 끝나면 Wired라는 이름을 불러 내게로 와서 의미가 되도록 할 수 있습니다 :)
다음에 애너테이션에 적용하는 애너테이션인 Meta-Annotation과
StudentManager 클래스의 애너테이션이 붙은 학생 변수들에 생명을 불어넣는 글로 이어가도록 하겠습니다.
(자바 Reflection에 대한 기초가 되어있다면 보다 쉽게 이해할 수 있는 내용들로 구성이 될 것입니다)