본문 바로가기

Better SW Development

[dw Review] 자바 DTO 개선시키기, Lombok

아쉽게도 많은 언어들이 자신만의 장점을 소개할 때 많이 사용하는 방법 중 하나는 자바빈(Java Bean)을 얼마나 적은 코드로 구현할 수 있는지를 보여주며 자랑으로 삼곤 한다.

별거 아닌데 살짝 분하다는 생각이 든다. 그게 뭐 큰 자랑이라고!... 라고는 하지만 때때로 자랑은 자랑이다. 너무 축약되서 의미가 훼손되는 수준이 아니라면, 코드 줄 수는 적을 수록 좋은게 맞다.

자바의 대표적인 노다가성 코드가 엔티티 클래스나 자바 빈계열의 DTO클래스를 만들때 발생한다. getter와 setter로 클래스의 대부분을 채우기 십상이다. 그리고 hashcode나 equals구현, toString 정도 집어넣으면 별 로직도 없는 코드가 잔뜩 길어진다. 이럴때 유용한 자바 라이브러리가 바로 Lombok(http://projectlombok.org) 이다.



[IBM DW] Java 코드에서 불필요한 코드를 제거할 수 있는 편리한 방법
http://www.ibm.com/developerworks/kr/library/os-lombok/index.html

아래 코드는 Lombok을 사용하기 전
001 import java.util.Arrays;
002 
003 public class DataExample {
004   private final String name;
005   private int age;
006   private double score;
007   private String[] tags;
008   
009   public DataExample(String name) {
010     this.name = name;
011   }
012   
013   public String getName() {
014     return this.name;
015   }
016   
017   void setAge(int age) {
018     this.age = age;
019   }
020   
021   public int getAge() {
022     return this.age;
023   }
024   
025   public void setScore(double score) {
026     this.score = score;
027   }
028   
029   public double getScore() {
030     return this.score;
031   }
032   
033   public String[] getTags() {
034     return this.tags;
035   }
036   
037   public void setTags(String[] tags) {
038     this.tags = tags;
039   }
040   
041   @Override public String toString() {
042     return "DataExample(" this.getName() ", " this.getAge() ", " this.getScore() ", " + Arrays.deepToString(this.getTags()) ")";
043   }
044   
045   @Override public boolean equals(Object o) {
046     if (o == thisreturn true;
047     if (o == nullreturn false;
048     if (o.getClass() != this.getClass()) return false;
049     DataExample other = (DataExampleo;
050     if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
051     if (this.getAge() != other.getAge()) return false;
052     if (Double.compare(this.getScore(), other.getScore()) != 0return false;
053     if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
054     return true;
055   }
056   
057   @Override public int hashCode() {
058     final int PRIME = 31;
059     int result = 1;
060     final long temp1 = Double.doubleToLongBits(this.getScore());
061     result = (result*PRIME(this.getName() == null this.getName().hashCode());
062     result = (result*PRIMEthis.getAge();
063     result = (result*PRIME(int)(temp1 ^ (temp1 >>> 32));
064     result = (result*PRIME+ Arrays.deepHashCode(this.getTags());
065     return result;
066   }
067   
068   public static class Exercise<T> {
069     private final String name;
070     private final T value;
071     
072     private Exercise(String name, T value) {
073       this.name = name;
074       this.value = value;
075     }
076     
077     public static <T> Exercise<T> of(String name, T value) {
078       return new Exercise<T>(name, value);
079     }
080     
081     public String getName() {
082       return this.name;
083     }
084     
085     public T getValue() {
086       return this.value;
087     }
088     
089     @Override public String toString() {
090       return "Exercise(name=" this.getName() ", value=" this.getValue() ")";
091     }
092     
093     @Override public boolean equals(Object o) {
094       if (o == thisreturn true;
095       if (o == nullreturn false;
096       if (o.getClass() != this.getClass()) return false;
097       Exercise<?> other = (Exercise<?>o;
098       if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false;
099       if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false;
100       return true;
101     }
102     
103     @Override public int hashCode() {
104       final int PRIME = 31;
105       int result = 1;
106       result = (result*PRIME(this.getName() == null this.getName().hashCode());
107       result = (result*PRIME(this.getValue() == null this.getValue().hashCode());
108       return result;
109     }
110   }
111 }

Lombok을 사용하면 동일한 내용이 아래와 같이 구현가능
01 import lombok.AccessLevel;
02 import lombok.Setter;
03 import lombok.Data;
04 import lombok.ToString;
05 
06 @Data public class DataExample {
07   private final String name;
08   @Setter(AccessLevel.PACKAGEprivate int age;
09   private double score;
10   private String[] tags;
11   
12   @ToString(includeFieldNames=true)
13   @Data(staticConstructor="of")
14   public static class Exercise<T> {
15     private final String name;
16     private final T value;
17   }
18 }

Lombok에는 이외에도 리소스 정리 작업( close 메소드 호출 계열)을 지원해 주는 @Cleanup 기능.
원하는 수준으로 적절히 Lock을 제공하는 @Synchronized
등등의 기능을 제공한다.

다음 JDK에서는 일부 기능을 언어차원에서 지원해 줘도 괜찮을 것 같다는 생각이 든다. :)