인터페이스의 분리

오늘은 oop와 관련된 이야기를 해볼 예정이다. oop의 구성요소를 살펴볼 것은 아니고 원칙들을 살펴 볼건데 그중에서 오늘은 인터페이스 분리 원칙(ISP) 에 대해서 살펴보도록 하자. 간단하게만 살펴볼 예정이니.. 사실 필자가 이해한 정도만..
그 전에 oop의 어떤 원칙들이 있나 보자. oop에는 5가지 원칙들이 있다. 위에서도 적었지만 인터페이스 분리 원칙 (ISP), 단일책임원칙 (SRP), 개방 폐쇄의 원칙 (OCP), 리스코프의 치환원칙 (LSP), 의존성 역전 원칙 (DIP) 등 이 있다.
뭔가 심오한 단어인듯만 같다. 여러가지 원칙들이 있지만 오늘은 인터페이스 분리 원칙 (ISP)에서만 다뤄보자. 나머지는 다음 기회에 포스팅해 보도록 하고(쓸려나 모르겠지만).. 솔직히 이해하기가 이게 제일 쉬워서 먼저 쓰는거 뿐이다.

인터페이스 분리 원칙

일단 정의를 살펴보도록 하자. 위키에 보면 다음과 같이 설명하고 있다.

The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use

대충 해석해보면 다음과 같다.

인터페이스 분리 원칙이란 클라이언트가 사용하지 않는 메서드에 의존하지 않아야 한다고 한다.

이게 무슨말인가.. 정의를 봐도 모르겠구나..
아무튼 간단하게 말하자면 자신과 관련없는 메서드들은 구현하지 말아야 한다는 뜻이다.

말보단 코드가 낫기 때문에 코드로 살펴보자.
음.. 예제는 필자가 노트북을 살려고 고민중이니 노트북으로 예를 들어보자. (맥북에어로는 개발이 쉽지 않아..)

public interface MacPro {

  void display();

  void keyboard();

  void touch();
}

맥프로에는 디스플레이, 키보드, 터치바등이 있다. 물론 여러가지가 더 있겠지만 중요한건 그게 아니므로..
그리고 이번에 나온 MacPro 2016년형을 구현해보자!.

public class MacPro2016 implements MacPro {
  @Override
  public void display() {
    System.out.println("mac pro2016 display");
  }

  @Override
  public void keyboard() {
    System.out.println("mac pro2016 keyboard");
  }

  @Override
  public void touch() {
    System.out.println("mac pro2016 touch bar");
  }
}

이번에 나온 맥프로를 구현했다. 이번에 나온 맥프로에는 touch bar 도 존재한다. 그닥 잘모르겠다 좋은지.. 그래서 2015년형을 사려고 하는데.. 그래도 비싸..ㅜㅜ

아무튼 내가 갖고 싶은 mac pro를 구현해봤다. 음.. 근데 필자가 갖고 싶은 맥북프로는 2015년형이다. 그래서 2015년형 맥북도 구현했다.

public class MacPro2015 implements MacPro {
  @Override
  public void display() {
    System.out.println("mac pro2015 display");
  }

  @Override
  public void keyboard() {
    System.out.println("mac pro2015 keyboard");
  }

  @Override
  public void touch() {
    // ?? 
  }
}

하하 드디어 내가 갖고 싶은 맥북프로가 완성되었다. 잉? 근데 이상하다. 2015년형에는 터치바가 없다. 터치바는 이번에 나온 신형부터 있으니 말이다. 그래서 구현을 하지 않았다. 뭔가 느낌이 왔다면 그게 바로 정답이다.

이게 바로 아까 위에서 이야기 했던 클라이언트가 사용하지 않는 메서드에 의존하지 않아야 한다고 한다 이 이야기이다. 이제야 조금 이해가 되지 않나?

그럼 어떻게 해야 될까? 한번 인터페이스 분리 원칙에 따라 수정해보도록 하자!!
말그대로 인터페이스를 분리하면 된다.

public interface MacPro {

  void display();

  void keyboard();

}

기본적인 맥프로에는 display 와 keyboard가 존재한다. 모든 맥프로에는 display와 keyboard가 있으니 MacPro 인터페이스에는 display, keyboard만 정의해주자.

public interface MacProTouch {

  void touch();
}

그리고 나서 MacProTouch라는 인터페이스를 만들어서 touch 라는 메서드를 정의하자. 한번 다시 구현해 보도록 하자!

public class MacPro2016 implements MacPro, MacProTouch {
  @Override
  public void display() {
    System.out.println("mac pro2016 display");
  }

  @Override
  public void keyboard() {
    System.out.println("mac pro2016 keyboard");
  }

  @Override
  public void touch() {
    System.out.println("mac pro2016 touch bar");
  }
}

위는 이번에 나온 맥프로를 구현했다. 기본이 되는 MacPro와 추가된 MacProTouch 인터페이스를 구현하면 끝이다. 이번엔 2015년형 맥북 프로를 구현해보자.

public class MacPro2015 implements MacPro {
  @Override
  public void display() {
    System.out.println("mac pro2015 display");
  }

  @Override
  public void keyboard() {
    System.out.println("mac pro2015 keyboard");
  }
}

간단하다. 2015년형에는 터치바가 없으니 MacPro만 구현해주면 된다. 참 쉽다? 말은 청산유수다..

인터페이스를 각각 쪼개서 사용하는 인터페이스만 구현해주면 되는 것이다. 그렇지만 너무 쪼개다 보면 원하지 않게 인터페이스가 어마어마하게 많아 질 수도 있다. 그럼 어떻게하냐? 글쎄다.. 필자도 모르겠다. 한 클래스가 적당한 책임을 쥔 상태에서 나누는게 좋을 듯하다. 이건 경험에서 나올 수 있으니 연습을 많이 해야 할 듯 싶다.

인터페이스를 잘 설계하는 건 쉽지 않다. 만약 인터페이스가 변경 된다면 구현한 모든 클래스들에게 영향을 주니 고민을 많이하고 설계를 해야 될 듯 싶다. 만약 인터페이스를 설계한다면 구현의 시간만큼 인터페이스를 설계하는데에도 시간을 많이 투자하자! 잘 설계된 인터페이스라면 상관없겠지만 잘 설계되지 않았던 인터페이스 였다면 그 보다 시간을 뺏길지도 모르니..

오늘은 이렇게 인터페이스 분리 원칙(ISP)에 대해서 알아봤다. oop는 어렵단 말이야.. 다음 시간에는 단일책임원칙(SRP) 에 대해서 살펴보도록 하자..
SRP.. 이건 또 무엇인가..

java의 String 얼마나 알고 쓰나요?

오늘은 자바의 String에 대해서 알아보자. 딱히 많이 중요한 이야기는 아니니 참고 정도만 하면 되겠다.

String 과 new String()

자바에서는 두가지 방법으로 String을 선언할 수 있다. 아래와 같이 말이다.

String a = "hello";
String b = new String("hello");

뭐 이건 자바의 왕기초이니 뭐 설명할 것도 없지만.. 한 가지는 String literal을 사용해서 선언가능하고 다른 한 가지는 new 키워드를 사용해서 생성할 수 있다. 그럼 두개의 차이점은 뭘까? 일단 가장 중요한 것은 메모리 구조가 다르다. 둘다 힙영역에 저장되지만 literal의 경우에는 힙영역 안의 string pool이라는 영역에 자리 잡게 된다.

좀 더 살펴보도록 하자.

String a = new String("String");
String b = "String";
System.out.println(a == b);

위와 같은 코드가 있다고 가정하자. 그럼 어떤 결과가 나올까? 뭐 누구나 알겠지만 당연히 false가 나온다. 자바를 처음 배울때 ==는 레퍼런스를 가르킨다고 배워 무조건 equals 를 사용해 값을 비교해야 한다고 대부분 그렇게 배웠을 것이다.

그럼 다음과 같은 코드가 있을 경우에는 어떨까?

String b = "String";
String c = "String";
System.out.println(b == c);

동일한 String 의 문자열이다. 하지만 둘 다 String literal을 사용해서 비교하고 있다. 결과는 참혹(?) 하다. 우리가 기대하고 있던 false가 나오지 않는다. (아닌가? 하긴 false가 나왔다면 이 글을 쓸일이 없겠지..) 두개는 정확히 동일한 래퍼런스를 가르키고 있다. 우리 똑똑한(?) 자바의 컴파일러가 최적화를 위해 동일한 String 문자열이 존재 한다면 재사용한다. 더 정확히는 string pool 에는 동일한 문자가 있다면 한개의 String이라는 문자열을 넣고 그것을 사용한다는 것이다.

물론 위와 같이 사용할 일은 없다. 왜냐하면? 저렇게 비교할 일이 없기 때문이다. 보통은 변수와 변수를 비교하기 때문에 꼭 equals 를 사용해야 한다. 혹은 상수를 비교한다고 하더라도 여타 프레임워크나 라이브러리들이 어떤 방식으로 줄지 모르니 그냥 마음 편하게 equals를 사용하도록 하자. 왠지 이글은 ==를 사용해도 된다는 말 같으니.. 만약 행여나 이런 경우가 있더라도 그냥 equals 를 사용하자. 괜한 버그를 만들지 말고..

그럼 또 해볼 수 있는게 있다. 아래와 같이 말이다.

String b = "String";
System.out.println(b == "String");

위와 같은 경우에는 어떨까? 한 번 생각해보자. 결과는 한번 생각해보고 코드를 돌려보도록 하자.

그리고 주니어 개발자를 위해 String의 참고할 만한 것들을 살펴보자.
객체형 String을 literal로 변환할 수 있는데 String의 intern() 메서드를 사용하면 된다.

String a = new String("String");
String c = "String";
System.out.println(a.intern() == c);

위 경우의 결과값은 true가 출력 된다. 근데 필자는 이 메서드를 사용한적이 거의 없어서.. 아니 한번도 없던거 같다. 아무튼 이런것도 있으니 참고하면 되겠다.

또 하나는 객체형 String은 되도록 이면 사용하지 말자. 아주아주 특별한 이유가 없다면.. 필자가 볼 때는 사용할 특별한 이유가 없다. 그러므로 무조건 String 은 literal 로 선언하자. IDEA 에서는 실제 힌트도 준다. 대충 해석하자면 String을 객체화 할필요 없다. 또한 자주 수행될 경우에는 성능에 문제가 있을 수 있다. 라고 하니 사용하지 말자!

String의 String.valueOf(Object obj) 메서드는 null safe 하다. 하지만 null일 경우에는 실제 문자열 "null"을 리턴한다. 그래서 원하는 값이 안나올 수 도 있다. 왜 "null"로 만들어 놨을까.. 흠흠

마지막으로 주니어 개발자들이 코드를 보면 자주 등장하는 부분이다. 매직 String이 자주 등장한다. 되도록이면 매직 String 을 사용하지 말자. String 뿐만아니라 Number 들도 그렇다. 유지보수에도 좋지 않고 실수할 여지가 있을 수 있다.

이 정도면 어느정도 팁을 준 듯 하다. 더 생각나지 않는다. 더 생각나면 다음에 기회에.. 다음으로 넘어가자!

String 의 final

이 글에서는 String만 관련지어서 이야기 한다. 다른 부분들은 다른 글을 참고하기 바란다. 필자가 쓴 글도 있으니 참고하면 되겠다.

필자는 final을 좋아한다. final은 변하지 않는 상수를 이야기한다. 변하지 않는다는 것은 안전하다는 뜻도 포함될 수 있다. 또 한 실수를 방지할 수 있다. 변하지 않아야 하는 값이 변한다면 버그가 나올 가능성이 높아진다.

private static boolean hello(String str) {

  something(str);
  //something
  // ...
  // ...
  if(str.equals("hello")){
    return true;
  }
  return false;
}

대략 위와 같은 코드가 있다고 가정하자. 현재 딱히 좋은 예제가 생각나지 않아서… 긴 메서드라고 생각하자!. 물론 긴메서드 자체를 만들면 안되겠지만 예제이니.. 만약 저 str 파라미터가 중간에 끼어서 사용되어 가공된다면 우리는 원하는 결과를 얻지 못할 수 도 있다. 물론 자신이 개발하고 자신이 유지보수를 한다면 그래도 다행이겠지만 다른 사람이 유지보수를 한다고 하면 긴 메서드를 단시간에 이해하기 힘들다. 그래서 중간에 그냥 str을 가공시킬지도 모른다. 버그가 안생기면 천만다행이겠지만 생긴다면 저 코드 하나로 인해 쓸때 없이 버그만 만들게 된 것이다. 아직은 필자도 final을 쓰는 습관이 안되어 있다. 차근차근 습관화 시켜야 겠다. 이건 뭐 그렇게 중요한 내용은 아니므로 그냥 그렇게 보면 되겠다. 사실 이걸 말할려고 한건 아닌데.. 예제가 좀 시원찮아도 이해해주시길..

final이 더 좋은 이유를 살펴보자.
다시한번 말하지만 final 키워드는 상수를 의미한다. 그러므로 변경되지 않는다. final로 선언되어 있는 변수를 변경하려고 하면 컴파일 에러가 난다.

final String a = "hello";
a = "world"; //컴파일 에러

컴파일러도 상수라는 것을 알고 있다. 다음 코드르 보자.

final String a = "hello";
String b = "hello";
System.out.println(a);
System.out.println(b);

a와 b의 변수의 다른점은 a는 상수이고 b는 변수이다. 아까도 말했듯이 컴파일러는 a가 상수인 것을 알고 있다. 그래서 컴파일러가 최적화를 통해 System.out.println(a) 이 부분에 System.out.println("hello") 로 바꾸어 버린다.

   L0
    LINENUMBER 10 L0
    LDC "hello"
    ASTORE 1
   L1
    LINENUMBER 11 L1
    LDC "hello"
    ASTORE 2
   L2
    LINENUMBER 12 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "hello"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L3
    LINENUMBER 13 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 17 L4
    RETURN

위는 해당 코드의 바이트코드의 일부분이다. L2 (LINENUMBER 12)를 보면 알 수 있다.

또 한 다음과 같이 사용할 일은 없겠지만 그래도 한번 살펴보자.

final String a = "hello";
final String b = "hello";
System.out.println("result : " + (a == b));

위와 같이 a,b 모두 상수로 선언하였다. 그리고 비교하면 컴파일러가 result : true 으로 바꿔 버린다. final을 쓰면 컴파일러가 좀 더 최적화를 하지만 그렇게 성능에 영향을 미칠 정도는 아닐 듯하다. 그렇지만 final을 사용하면 여러모로 좋으니 추천정도만 한다.

여타 언어들은 기본적으로 final을 사용하고 있다. 스칼라도 그렇고 코틀린도 그렇고 es6에도 상수의 개념으로 const라는 키워드가 생겼다. 누군가가 final로 도배를 한다면 뭐라하지 말고 칭찬해주자… 그렇지만 회사에서는 회사 코딩 컨벤션을 따르는게 맞다. 혹시나 이글을 보고 갑자기 회사 코드를 final 도배하는 일은 없도록 하자.

오늘은 이렇게 java의 String에 대해서 알아봤다. 물론 중요하면 중요할 수도 있겠지만 실제 개발을 할 경우에는 그렇게 많은 도움은 되지 않을 듯 하다.

오늘 내용은 그냥 이런게 있구나.. 생각하면 되겠다.
오늘은 이만!

lombok을 잘 써보자! (2)

오늘은 lombok 관련해서 포스팅하는 두 번째 시간이다.

저번시간에는 특히나 자주 사용하는 것에 대해 설명 하였는데 오늘은 자주 사용하지는 않을 거 같지만(필자 생각) 있는 기능이니 필요하다면 사용해도 괜찮을 것 같은 기능들을 살펴도록 할텐데 그 전에 어제 자주 사용하는 것 중에 설명하려고 했던 기능이 있는데 어제 깜빡하고 못해서 오늘은 자주 사용할 것 같은 기능 위주로 처음에 작성해보겠다. 흠..말이 이상한데.. 아무튼 그냥 설명하겠다. 참고로 IDEA 기준으로 설명한다. 다른 툴들은 각자가 인터넷에서 구글링 해보면 잘 나와있다.

그럼 시작해보자!

@Log

이 이노테이션 역시 자주 사용할 것 같은 기능에 속한다. 어노테이션 명 그대로 log를 출력할 수 있도록 도와준다. 이 어노테이션은 log라는 변수를 자동으로 만들어 주는 아주 편리한 기능이다.

@Log
public class LogObject {
  public void print() {
    log.info("hello world");
  }
}

위와 같이 log를 선언하지 않아도 자동으로 log라는 변수를 만들어 주어 개발자가 편리하게 사용할 수 있다.
하지만 로그 라이브러리는 아주 다양하다. 그 중에서 가장 많이 사용되는 jul, log4j, log4j2, logback, JBossLog(?) 이건 잘.. 기타 등등 그리고 로그 라이브러리는 아니지만 흠.. 파사드 라이브러리라 해야되나.. slf4j, jakarta(apache) common logging 등이 존재 한다. 위의 @Log 어노테이션은 jul(java util logger) 을 사용하는 어노테이션이다.
각각에 맞게 어노테이션이 존재하니 사용하고 싶은 라이브러리 추가 후 사용하며 된다.

log4j

@Log4j
public class LogObject {

  public void print() {
    log.info("hello world");
  }
}

위와 같이 @Log4j 어노테이션을 작성해주면 된다. 위의 코드는 아래의 코드와 동일하다.

public class LogObjectNot {
  private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogObjectNot.class);
}

참고로 log4j는 end of life 처리 되었으니 신규 프로젝트에서는 다른 더 좋은 라이브러리를 선택하자!

Log4j2

public class LogObject {

  public void print() {
    log.info("hello world");
  }
}

public class LogObjectNot {
  private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogObjectNot.class);
}

JBossLog

@JBossLog
public class LogObject {

  public void print() {
    log.info("hello world");
  }
}

public class LogObjectNot {
  private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogObjectNot.class);
}

위와 같이 요즘은 로그를 직접적으로 사용하지 않고 파샤드 라이브러리를 이용해서 로그를 출력하는 경우가 많을 것이라고 생각된다.

Slf4j

@Slf4j
public class LogObject {

  public void print() {
    log.info("hello world");
  }
}

public class LogObjectNot {
  private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogObjectNot.class);
}

jakarta(apache) common logging

@CommonsLog
public class LogObject {

  public void print() {
    log.info("hello world");
  }
}

class LogObjectNot {
  private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogObjectNot.class);
}

동일하게 파사드 라이브러리도 해당 어노테이션으로 사용가능하다. 레거시 라이브러리 말고 요즘 나오는 라이브러리들은 jakarta logging 보다는 slf4j를 더 많이 사용하고 있다고 생각하면 된다. 예전에 jakarta logging이 메모리 누수가 발생한다 했었던 기억이 어렴풋이 난다. 하지만 Spring의 메인 로그 파사드 라이브러리는 jakarta common logging 을 사용한다. 아마 후회한다고 했지…

다 좋지만 log라는 변수명이 맘에 들지 않을 수 도 있다. 변수명을 바꿀 수 없을까? lombok 만든 개발자분들이 다 될 수 있게 만들었다. IDEA 기준으로 root 디렉토리에 lombok.config 라는 파일을 만들어서 다음 같이 작성하자.

lombok.log.fieldName=logger

참고로 IDEA lombok plugin 어느 정도 최신 버전을 설치해야 된다. 아니면 컴파일은 되지만 해당 필드에 에러로 표시 되어 있다. 아무튼 위와 같이 설정하면 log 라는 변수명을 logger로 변경 할 수 있다.

@Slf4j
public class LogObject {

  public void print() {
    logger.info("hello world");
  }
}

만약 static도 맘에 들지 않는다면 다음과 같이 설정하면 된다.

lombok.log.fieldIsStatic=false

이 외의 다른 설정들은 문서를 참고하길 바란다. 딱히 없긴 할 듯 하다.

@FieldDefaults, @NonFinal

@FieldDefaults 어노테이션은 클래스 레벨에 사용가능하며 필드를 컨트롤 할 수 있다. 필드의 접근제한자를 설정할 수 있으며 final 설정도 가능하다.

@RequiredArgsConstructor
@FieldDefaults(makeFinal=true, level= AccessLevel.PRIVATE)
public class FieldDefaultsObject {
  @NonFinal Long id;
  String name;
}

속성은 makeFinallevel이 있으며 makeFinal 경우에는 final 여부를 묻는 것이고 level은 접근제한을 할 수 있는 속성이다.
만약 위와 같이 코드를 작성 하였다면 아래와 같은 코드로 변환이 될 것으로 예상된다.

public class FieldDefaultsObjectNot {
  private Long id;
  private final String name;

  public FieldDefaultsObjectNot(String name) {
    this.name = name;
  }
}

Spring 에서 여러 Object를 접근할 때 사용하면 적절할 듯 하다. 대략 이런식?

@RequiredArgsConstructor
@FieldDefaults(makeFinal=true, level= AccessLevel.PRIVATE)
class SomeService {
  SomeRepository someRepository;
  SomeRepository1 someRepository1;
  SomeRepository2 someRepository2;
  SomeRepository3 someRepository3;
  SomeRepository4 someRepository4;

  //something
}

한 두개 쯤이야 그냥 사용하는 것이 낫겠지만 많이 접근해야 한다면 위와 같은 방식도 나쁘지는 않다.

@NonNull

해당 어노테이션은 파라미터에 자주 사용될 듯 싶다. 메서드 레벨에도 가능하긴한데 무슨 의미 일까?
null 아니여야 한다는 의미를 갖고 있다. 파라미터에 해당 어노테이션을 작성하면 파라미터에 null이 들어 왔을 경우 NullPointerException을 던진다. 뭐 어차피 변수를 사용하면 NullPointerException이 떨어 지긴 할텐데 말이다..

public class NonNullObject {

  public void print(@NonNull String name) {
    System.out.println(name);
  }
}

위와 같이 파라미터에 @NonNull을 작성할 경우에는 다음과 같은 코드가 만들어 진다.

public class NonNullObjectNot {
  public void print(String name) {
    if(name == null){
      throw new NullPointerException("name");
    }
    System.out.println(name);
  }
}

하지만 나는 NullPointerException 싫다. IllegalArgumentException으로 던지고 싶다. 라고 한다면 lombok.config에 다음과 같이 에 설정을 해주면 된다.

lombok.nonNull.exceptionType=IllegalArgumentException

그럼 파라미터에 null이 들어 왔을 경우에는 IllegalArgumentException을 던지게 된다.

@SneakyThrows

checked exception 처리를 간단하게 처리 할 수 있다.

public class SneakyThrowsObject {

  @SneakyThrows(ClassNotFoundException.class)
  public void classFind() {
    Class.forName("me.wonwoo.test");
  }
}

@SneakyThrows 어노테이션을 사용해서 해당 exception을 처리 하면 된다. 해당 코드를 다시 작성하면 이런코드가 나온다고 한다.

class SneakyThrowsObjectNot {
  public void classFind() {
    try {
      Class.forName("me.wonwoo.test");
    } catch (ClassNotFoundException e) {
      throw Lombok.sneakyThrow(e);
    }
  }
}

@ExtensionMethod

메서드를 확장할 수 있는 기능으로 보인다. 안타까운건 intellij에서는 동작하지 않는다..(이클립스에선 동작할려나..) 아주 좋은 기능 같은데.. 지금 현재는 테스트를 할 수 없으니 문서에 있는 예제를 가져왔다. 딱 보면 대충 어떤 느낌인지 알 것이다. 코틀린에서도 이런 기능 메서드를 확장할 수 있는 기능이 존재 했던걸로 기억한다.

@ExtensionMethod({java.util.Arrays.class, Extensions.class})
public class ExtensionMethodObject {
  public String test() {
    int[] intArray = {5, 3, 8, 2};
    intArray.sort();

    String iAmNull = null;
    return iAmNull.or("hELlO, WORlD!".toTitleCase());
  }
}

class Extensions {
  public static <T> T or(T obj, T ifNull) {
    return obj != null ? obj : ifNull;
  }

  public static String toTitleCase(String in) {
    if (in.isEmpty()) return in;
    return "" + Character.toTitleCase(in.charAt(0)) +
      in.substring(1).toLowerCase();
  }
}

이 외에도 간단하게 자원을 해제하는 @Cleanup, 빌더(Builder) 패턴을 만들어주는 @Builder, 메서드를 동기화 시켜주는 @Synchronized 등 많은 기능을 지원 해준다. 만약 궁금하다면 한번씩 해보는 것도 나쁘지 않다!

오늘도 이렇게 lombok을 잘 사용하는 방법에 대해서 살펴봤다. 각자가 더 궁금한게 있다면 나머지는 해당 문서를 살펴보도록 하자!

하루 쉬었더만 일주일이 금방가구만..