프로젝트를 하다보면 비슷한 기능들이 자주 사용되는 경우가 있고 개발자들은 흔히 이런 기능들을 스태틱(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 IOException
-file
이 정상적으로 읽을 수 있는 파일인지 검사한 후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"이라는 유명한 격언도 있어서 남들이 해놓은 걸 다시 할 필요는 없다는 게 상식이다. 잘 찾아서 가져다 쓰는 것도 사실 쉽지 않은데 그걸 다시 더 잘 만드는 건 더욱 쉽지 않은 일이니까 말이다. 남들이 해놓은 걸 굳이 다시 만들 경우는 내가 스티브 잡스 같은 사람일 경우 뿐이라 생각된다…