알아두면 편리한 자바 유틸리티 클래스들

프로젝트를 하다보면 비슷한 기능들이 자주 사용되는 경우가 있고 개발자들은 흔히 이런 기능들을 스태틱(static) 메서드로 만들어 유틸리티 클래스를 만들곤 한다. 그런데 이런 유틸리티 기능들이 사실 우리가 이미 사용하는 오픈소스 프레임웍이나 라이브러리에 포함된 경우가 상당히 많다. 또한 개발자가 나름 만든 클래스에는 버그가 숨어 있는 경우도 많아서 가급적 검증된 라이브러리를 사용하는 게 훨씬 낫다. 이번 글에서는 알아두면 편리한 자바 유틸리티 클래스들 에 대해 찾아보았다.

스프링 프레임웍

스프링 프레임웍에는 다양한 패키지가 있는데 그중 코어의 org.springframework.util 패키지에 다양한 유틸리티가 있다. 개발자들이 가장 흔하게 사용하는 유틸리티 기능은 아마도 문자열 처리 작업일 것이다. StringUtils 클래스의 다음과 같은 유틸리티성 스태틱 메서드들을 사용해보면 편리하다. 대부분은 String 클래스를 조금만 활용하면 간단히 처리할 수 있는 기능들이지만 특히 일일이 null 검사를 안해도 된다는 점이 개발자들에게 유용한 것 같다. 다음에 나열한 것 외에도 많은 메서드가 있으므로 필요하면 API를 확인해보기 바란다.

  • boolean isEmpty(Object str) – 문자열이 null도 아니고 빈 문자열(“”)도 아닌지 확인할 수 있다. 헌데 str 인자가 보다시피 사실 Object형이므로 변수의 형식 자료형이 String형이 아니더라도 형변환할 필요 없이 바로 빈 값인지 확인할 수 있다. 참고로 자바 1.6부터는 String 클래스에 isEmpty() 메서드가 있는데 이미 객체가 존재해야 메서드도 사용할 수 있는 것이므로 null 검사는 따로 해야 한다는 문제가 있다.
  • boolean hasText(CharSequence str) – 문자열에 공백이 아닌 문자가 들어가 있는지 검사하는 기능이다. 당연히 null도 아니고 빈 문자열도 아닌지 검사한다. 인자의 자료형이 CharSequence이므로 String, StringBuffer, StringBuilder 등을 다 검사할 수 있다.
  • boolean startsWithIgnoreCase(String str, String prefix), boolean endsWithIgnoreCase(String str, String suffix) – 문자열의 시작 부분 및 끝 부분을 검사할 수 있는 메서드다. String 클래스의 startsWith, endsWith는 대소문자를 구별하는데 반해 이 메서드들은 그렇지 않다는 점이 유용할 때가 많다.
  • String replace(String inString, String oldPattern, String newPattern) String.replaceAll
    메서드는 정규식을 사용해 문자열을 대치하는데 이 메서드는 일반 문자열을 대치하는 메서드다. 정규식 생각하지 않고 속도도 빠른 문자열 대치 기능을 원한다면 이 메서드가 딱이다.
  • String getFilenameExtension(String path) – “mypath/myfile.txt”와 같은 경로에서 확장명 “txt”를 집어낸다. StringUtils 클래스에는 이 외에도 경로나 파일명 처리를 위한 메서드들이 있는데 주의할 점은 경로 구분자가 슬래시(/)인 경우만 된다는 것이다. 즉, 윈도에서는 간혹 기능 오류가 있을 수 있다.
  • String[] addStringToArray(String[] array, String str) – 배열은 리스트와 달리 요소를 추가하기가 힘들다. 이 메서드는 그러한 문제를 쉽게 해결해준다.
  • String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) – 콤마나 탭과 같은 어떠한 문자열로 구분된 다른 문자열을 문자열 배열로 바꿔준다. CSV와 같은 복잡한 예외가 있는 경우(값 안에 컴마가 있는 경우 등)는 안되고 단순한 문자열만 되지만 charsToDelete 인자를 활용하여 줄바꿈이나 공백 같은 걸 지울 수 있으므로 때로 아주 유용하다.

다음으로는 FileCopyUtils를 살펴보라. 다음과 같은 여러 버전으로 파일 복사 기능을 제공한다. 이 외에도 많다.

  • int copy(File in, File out) throws IOException
  • void copy(byte[] in, File out) throws IOException
  • int copy(InputStream in, OutputStream out) throws IOException
  • byte[] copyToByteArray(InputStream in) throws IOException
  • int copy(Reader in, Writer out) throws IOException

이 밖에도 org.springframework.util 패키지에서는 다음과 같은 클래스를 구비해놓고 있다. 모두 거들떠볼 필요가 있다.

  • FileSystemUtils 클래스에서는 (하위 폴더, 파일을 포함하는, 즉 recursive) 폴더 삭제(deleteRecursively), 복사(copyRecursively) 기능을 제공한다.
  • CollectionUtils 클래스에는 isEmpty, contains, mergeArrayIntoCollection 메서드가 있다.
  • StopWatch 클래스는 코드 실행 시간을 측정할 수 있는 기능을 제공한다. 원초적으로 System.currentTimeMillis() 메서드로 시간을 재지 말고 이걸 사용해보면 편리할 것이다.
    StopWatch w = new StopWatch();
    w.start("작업 1");
    ... // 뭔가 작업 실행
    w.stop();
    w.start("작업 2");
    ... // 뭔가 또 다른 작업 실행
    w.stop();
    System.out.print(w.prettyPrint()); // 작업별 경과 시간 출력

org.springframework.web.util 패키지에도 유용한 유틸리티 클래스들이 있다.

  • 우선 HtmlUtils 클래스는 메서드가 몇 개 없는데 대표적인 메서드가 String htmlEscape(String input)다. < 기호를 &lt;로, & 기호를 &amp;로 바꾸는 등의 작업을 처리해준다. 사용자가 입력한 문자열에 HTML 코드가 섞여 있으면 문제가 될 경우 이러한 이스케이프 처리는 필수이다. 다만 JSP에서 JSTL을 사용할 경우는 <c:out> 태그를 사용해 이스케이프시켜 출력할 수 있으므로 어떤 경우에 사용할 것이냐가 중요하다고 볼 수 있다. 반대 작업을 하는 unescape 메서드도 제공된다.
  • JavaScriptUtils 클래스에는 String javaScriptEscape(String input) 메서드 하나만 있다. 큰따옴표를 \"로, 백슬래시를 \\로, 탭을 \t로, 줄바꿈(CR, NL이 합친 경우 포함해서)을 \n로, 부등호를 \u003C 또는 \u003E 등으로 바꿔주는 작업을 한다.
  • 웹에서 여러 개의 쿠키를 만들 때 대부분의 인자는 비슷하게 하고 값만 바꿔 설정할 경우 CookieGenerator 클래스를 사용하면 반복 작업을 줄일 수 있다. 즉, setCookieName, setCookieDomain, setCookieMaxAge 등으로 쿠키 기본 설정을 한 후 void addCookie(HttpServletResponse response, String cookieValue)로 HTTP에 쿠키를 추가하거나 void removeCookie(HttpServletResponse response)로 쿠키를 제거할 수 있다.

commons 라이브러리

아파치 커먼스(Apache Commons) 라이브러리는 그 자체가 이미 유틸리티 라이브러리므로 유틸리티 클래스를 선별하는 건 의미가 없지만 내 경험상 다음 몇 가지 클래스는 자주 나타나는 번거로운 작업을 단순화해주었길래 나열해본다. 먼저 IO 라이브러리의 FileUtils 클래스를 보자.

  • FileInputStream openInputStream(File file) throws IOExceptionfile이 정상적으로 읽을 수 있는 파일인지 검사한 후 FileInputStream을 만들어낸다.
  • FileOutputStream openOutputStream(File file, boolean append) throws IOException – 비슷하다. 정상적인 파일로 쓸 수 있는지 검사해주며 상위 디렉터리가 없는 경우 만들어주기까지 한다. 실패하면 IOException을 던진다.
  • String byteCountToDisplaySize(long size)size 값을 1000 이하의 범위로 읽을 수 있도록 단위를 환산해 “KB”, “MB”, “GB”, “TB” 등을 붙인다.
  • Collection listFiles(
    File directory, IOFileFilter fileFilter, IOFileFilter dirFilter)
    – 디렉터리내 파일들을 필터에 따라 걸러 컬렉션으로 반환한다. IOFileFilter 인터페이스를 알고 클래스를 구현할 수 있어야 사용할 수 있다.
  • void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) throws IOException – 파일을 복사한다. null 검사, 파일 및 디렉터리 존재 여부, 쓰기 가능 여부 등을 다 검사해준다.
  • void deleteDirectory(File directory) throws IOException – 디렉터리를 삭제한다.
  • String readFileToString(File file, String encoding) throws IOException – 파일 내용을 String 문자열로 읽어온다. 문자열이 아니라 리스트로 읽어오는 List readLines(File file, String encoding) throws IOException 메서드도 있다.
  • void write(File file, CharSequence data, String encoding, boolean append) throws IOException – 문자열 데이터를 파일에 쓴다. Spring IO 2.1부터 나타난 메서드다.

다음으로 IO 라이브러리의 IOUtils 클래스를 살펴보자. 파일 복사 기능 같은 건 위 FileUtils에 있었지만 이 클래스는 스트림 수준에서 복사 또는 읽기, 쓰기 기능을 제공한다.

  • static byte[] toByteArray(InputStream input, int size) throws IOException – 스트림의 내용을 읽어 바이트 배열로 반환한다. input에 대한 null 검사는 하지 않지만 size가 0 이상인지는 확인하며 읽은 크기가 size가 아니면 IOException을 던진다. 비슷한 메서드로 toCharArray, toString 등이 있다.
  • int copy(InputStream input, OutputStream output) throws IOException – 대표적인 스트림 복사 메서드다. 비슷한 메서드들이 많이 제공된다. copy(InputStream input, Writer output), copy(InputStream input, Writer output, String encoding), copy(Reader input, Writer output) 등.

커먼스 Email 라이브러리도 유용하다. 스프링 프레임웍을 사용할 경우 MailSenderImpl 등을 사용할 수 있다고 언급한 적이 있는데 커먼스의 e메일 라이브러리는 좀더 기능이 많다. 기본적으로 HTML, 첨부 파일 메일을 보낼 수 있는 건 같지만 SMTP 암호화, 원격 웹사이트의 파일 첨부, 이미지 내장 e메일 등을 보내는 게 가능하다. 자세한 설명은 사용자 지침서를 참고한다. 예시가 잘 나와 있다.

구슬이 서말이라도

스프링 프레임웍과 아파치 커먼스 라이브러리 두 가지에 대해서만 살펴봤지만 사실 대부분의 프레임웍과 라이브러리는 자체적인 유틸리티 클래스와 메서드를 구비해놓고 있다. 또한 Google Guava, 커먼스 Lang와 같이 특정 기술 분야에 대한 라이브러리가 아니라 자바 언어 자체에 대한 유틸리티성 라이브러리도 있다.

그러나 구슬이 서말이라도 꿰어야 보배라고 이를 잘 활용하지 않으면 아무 소용이 없겠지… 그리고 “Do not reinvent the wheel”이라는 유명한 격언도 있어서 남들이 해놓은 걸 다시 할 필요는 없다는 게 상식이다. 잘 찾아서 가져다 쓰는 것도 사실 쉽지 않은데 그걸 다시 더 잘 만드는 건 더욱 쉽지 않은 일이니까 말이다. 남들이 해놓은 걸 굳이 다시 만들 경우는 내가 스티브 잡스 같은 사람일 경우 뿐이라 생각된다…

의견 있으시면 냉큼 작성해주세요~