자바(java)에서 사용하는 정규표현식(Regular expression)(01)

Language/JAVA 2013. 8. 1. 20:06

문자열에 대한 값을 체크 할 때 간단한 길이계산 정도야 if (String.length() > 0) 등을 이용해 작성할 수 있다.  하지만 복잡한 문자열(예를들어 주민등록 번호, 혹은 이메일 형식)등을 점검하고자 할땐 조건문이 꽤 복잡해 진다.

이런 경우를 대비해 java에선 '정규표현식(Regular expression)을 1.4부터 제공한다. 

정규표현식?
문자열에 매치여부를 확인할 수 있고, 치환(replace)할 수 있는 문자열 형식
(필자 임의로 작성했습니다. 정확한 해석은 검색을 통해 ..)


그대로 해석하자면 '정규표현식'이란 문자열에 대한 형식이 있다. 그리고 우리가 검증하고자 하는 문자열이 '작성된 정규 표현식에 매치여부를 확인'할 수 있고, 특정 부분을 '치환할 수 있다'는 뜻이다.  
더 쉽게 보자면 이것은 '틀을 짜 놓고, 이 안에 들어가나?'를 보는 것 이다. 

그렇게 되면 복잡한 조건문을 통해 검증해야 하는 문자열도 정해진 식을 통해 단번에 매치여부를 확인할 수 있는 것이다.

이제 정규표현식에 사용되는 식을 살펴보자.


 정규식 설명예제 
 .임의의 한 문자(필수)를 의미 합니다.
ab.(abc, abd,abe) .. 
 ?바로 앞에 문자가 없거나 하나가 있음을 의미 합니다.a?c (ac, abc, bc) .. 
 *바로 앞에 문자가 없거나 하나이상 반복을 의미 합니다.ab* (a, ab, aaa) .. 
 +바로 앞에 문자가 하나이상 반복을 의미 합니다.ab+ (ab, abb, abbb) ..
 ^문자열의 시작을 의미 합니다.^ab (abc, abcd, abcde) ..
 [^]^이후의 괄호안 형식을 제외함을 의미 합니다.[^ab]cd (ecd, fcd, gcd) ..

문자열의 끝을 의미 합니다. 
abc$ (pupu abc, story abc) ..
 [][]안의 형식 일치를 의미 합니다.[abc] (a, b, c, ab, bc, abc) ...
 {}{}앞 문자열(혹은 문자) 반복 갯수를 의미 합니다.ab{2} (abab) 
ab{2,} (2개이상) 
ab{1,2} (1부터 2까지)
 ()()안의 내용 을 하나의 묶음으로 사용 함을 의미 합니다.(ab){2} (abab)
(ab)+ (ab, abab, ababab ..)
 |or연산을 의미 합니다.(a|b|c)  (a, b, c, ab,abc ..)
 [0-9](부터 - 까지)의 숫자를 의미 합니다.[0-9] (0, 1, 2, 3, 4 ..)
[a-z]
(부터 - 까지)의 소문자를 의미 합니다.[a-z] (a, b, c, d ..)
 [a-zA-Z](부터 - 까지)의 대,소문자를 의미 합니다. [a-zA-Z] (a, b, A, B ..)
 \p(Alpha)대,소문자 아파벳을 의미 합니다.(a, b, c, D, E, F ..) 
\p(Digit) 
숫자를 의미 합니다.(1, 2, 3 ..)
 \p{Alnum}대,소문자 아파벳, 숫자를 의미 합니다.(a, b, c, d, e, 1, 2, 3 ..) 
\d
숫자를 의미 합니다.(1, 2, 3, 4 ..) 
 \D숫자가 아닌 것을 의미 합니다.  
 (a, b, d, E ..)


그냥 보기엔 복잡하기만 하다. 필자도 처음봤을땐 '외계어'로 보여서 도무지 손대고 싶지 않았다. 그런데 이게 .. 계속 왜 안되는지 이해를 못하면서 헤딩하다 보니 .. '특별한 매력'을 느끼게 되었다. --b 

우리가 처음 자바(그 외 다른 언어 등등)을 배울때 생각해 보자. public ? static ? void ? main ? request ? response ? session ? 어느것 하나 생소하지 않은것이 없었다. 영어는 어떠한가 ? 영어 알파벳을 처음 봤을 땐 '문자' 가 아닌 '기호'로 받아드리는게 맞는것 같다. 왜냐면 처음 보는거니까 .. 

이제 사용법을 살펴보자. 몇가지 예제를 살펴보면 더욱 쉽게 이해할 수 있다.

RegexSample .java

  1. package pupustory.regex.sample;  
  2. public class RegexSample {  
  3.     public static void main(String ar[]) throws java.io.IOException{  
  4.        //정규표현식 적용 a로 시작하며, a다음 아무문자1개만,  마지막은c로 끝남  
  5.         final String regex = "^a.c$";   
  6.         String useStr = "a1c";  
  7.         System.out.println(useStr.matches(regex));  
  8.     }  
  9. }  



별로 길지 않은 코드다. regex에 형식을 지정하고 useStr의 내용이 맞는지 여부를 확인했다. 정규표현식은 자바에서 제공하는(1.4 이후) java.util.regex.* 패키지가 있다. 그리고 String 에서도 손쉽게 사용할 수 있다. 만약 사용시 좀 더 강력한 정규표현식(예를들면 매치되는 문자열의 그룹이라던가 하는)을 이용하고자 할땐 패키지를 사용하는 것이 좋다.

이번엔 좀 더 난이도 있는 표현식을 살펴보자.

RegexSample .java

  1. package pupustory.regex.sample;  
  2. public class RegexSample {  
  3.     public static void main(String ar[]) throws java.io.IOException{  
  4.         final String regex = "\\p{Alnum}+@\\p{Alnum}+\\.\\p{Alnum}+";  
  5.         String[] useStr = {"pupustory@gmail.com"  
  6.                             ,"pupu한글@gmail.net"  
  7.                             ,"pupu@gmail.net.net"  
  8.                             ,"@.net"  
  9.         };  
  10.         for (int i=0; i<useStr.length; i++) {  
  11.             System.out.println(useStr[i].matches(regex));  
  12.         }  
  13.     }  
  14. }  




이것은 이메일을 검증하는 코드다. 표현식이 좀 복잡해 보이지만 하나하나 살펴보자.

// 숫자, 혹은 영문자가 오게된다. 마지막에 +가 있으므로 반드시 하나 이상의 문자가 와야 한다.
\p{Alnum}+ 
// @문자가 온 뒤 위와 같은 형식이다.
@\p{Alnum}+
// .문자가 온 뒤 위와 같은 형식이다. '.'앞에 \가 온 이유는 정규표현식에서 '.'문자를 사용한다.
// 따라서 '이것은 정규표현식이 아닌 그냥 내가 원하는 특수 문자'임을 뜻한다.
\.\p{Alnum}+

결과는 처음 배열 문자만 통과되고 나머지는 false를 반환한다. 실제로 아주 쉽다. 단지 처음보는 문자열을 코드로 사용함에 있어 복잡해 보이는것은 사실이다. 이것은 점점 많은 예제를 통해 익숙해질 수 있다. 

이제 좀 더 고급 표현식을 사용하기 위해 패키지에 포함된 라이브러리를 이용해 보자.

RegexSample .java

  1. package pupustory.regex.sample;  
  2. import java.util.regex.*;  
  3. public class RegexSample {  
  4.     public static void main(String ar[]) throws java.io.IOException{  
  5.           
  6.         // 정규표현식 형식 정의  
  7.         // 2009/07/28 pupustory@gmail.co.kr 의 패턴에 대응하도록 변경함.  
  8.         //final String regex = "\\p{Alnum}+@\\p{Alnum}+.\\p{Alnum}+";   
  9.         final String regex = "^[A-Za-z]+@[A-Za-z]+(.[A-Za-z]+){1,2}$";        
  10.         // 정규표현식에 맞춰볼 문자열  
  11.         String useStr = "pupustory@gmail.com";  
  12.           
  13.         // 패턴 정의 ( 주의할점은 인스턴스 생성이 아니라는 점이다.  
  14.         // 실제 소스를 열어보면 다음과 같이 되어있다.  
  15.         // return new Pattern(regex, 0);  
  16.         // 하지만 본 생성자는 private로 구성되어 있다.  
  17.         Pattern pattern = Pattern.compile(regex);  
  18.           
  19.         // 패턴에 대해 맞춰볼 문자열을 넘겨 Matcher 객체로 받는다.  
  20.         Matcher match = pattern.matcher(useStr);  
  21.         // 패턴 일치사항을 발견할 경우 true를 반환한다.  
  22.         System.out.println(match.find());  
  23.     }  
  24. }  



주석 내용만 살펴봐도 별 부담없는 소스다. 이제 패키지를 이용한 강력한 기능을 살펴보자. 만약 사용자가 txt형식의 큰 문서 파일을 받았다고 가정 하자. 그 문서 파일 중 '특정 규약에 맞는 문자열이 몇번이나 반복되어 나오는가?'를 분석하고자 한다. 우리가 지금까지 알아본 방법은 '임의의 하나 문자열이 지정된 형식에 맞는가?'에 대한 방법을 알아왔다. 즉. '단건'에 대한 정보를 넘겨 그 '단건'정보가 일치하는지를 알아본 것이다.

큰 문서에 대해서 '얼마나 많이 지정된 패턴이 반복되는가?'를 알아보려고 한다면 어떻게 해야할까 ? 우리가 DAO등을 사용할 때 row를 가져오는 방법으로 루프돌린 방법은 다음과 같다.

  1. // other code ..  
  2. ResultSet record = PreparedStatment.executeQery();  
  3. while(record.next()) {  
  4. // order code ..  
  5. }  
  6. // other code ..  



간단히 위와같이 사용한다. 우리가 위에 살펴본 .find()메소드도 같은 역할이다. 정규표현식에 대해 문서를 검색하고, 매치되는 부분을 찾는다. 후에 다음 부분을 차자게되고 끝까지 찾아 더이상 없다면 false를 반환하는 것 이다. 이제 좀 복잡한 코드를 만들어 보자.

RegexSample .java

  1. package pupustory.regex.sample;  
  2. import java.util.regex.*;  
  3. public class RegexSample {  
  4.     public static void main(String ar[]) throws java.io.IOException{  
  5.         final int MAX_STRING_SIZE = 1000;  
  6.         java.util.Random random = new java.util.Random();  
  7.         // 정규표현식 형식 정의  
  8.         final String regex = "\\p{Alnum}+@\\p{Alnum}+.\\p{Alnum}+";  
  9.         // 정규표현식에 맞춰볼 문자열  
  10.         StringBuffer useStr = new StringBuffer();  
  11.         // 정규표현식에 맞춰볼 문자열을 생성 합니다.  
  12.           
  13.         for (int i=0;i<MAX_STRING_SIZE;i++ ) {  
  14.             useStr.append("pupustory"+i);  
  15.             // 랜덤으로  @gmail.com을 추가합니다.   
  16.             // @gmail.com이 추가되면 정규표현식에 맞으므로 후에 찾게 될 것 입니다.  
  17.             // 추가되지 않으면 틀렸으므로 pass할 것 입니다.  
  18.             if (random.nextBoolean()) useStr.append("@gmail.com");  
  19.             useStr.append(" ");  
  20.         }  
  21.         Pattern pattern = Pattern.compile(regex);  
  22.         Matcher match = pattern.matcher(useStr);  
  23.         // 매치된 문자열 갯수를 샙니다.  
  24.         int matchCount=0;  
  25.         while(match.find()) {  
  26.             System.out.println(match.group());  
  27.             matchCount++;  
  28.         }  
  29.         System.out.println("정규표현식에 맞는 문자열 갯수 > " + matchCount);  
  30.     }  
  31. }  



복잡해 보이지만 간단하다. for문을 통해 반복하며 문자열을 만드는데 '@gmail.com'을 추가할지 안할지 정한다. 추가되면 정규표현식에 맞게되고, 그렇지 않으면 맞지 않게된다. while(boolean) 돌며 매치되는 문자열을 검색하고, 더이상 내용이 없으면 false를 반환하므로 빠져나온다.

지금까지 살펴본 예제는 단순하다. 정규표현식이 어려운것은 바로 '처음 손대기가 까다로워 보인다'라는 것이 가장 큰게 아닌가 싶다. 표현식을 만드는게 복잡해 보이지만 익숙해지면 매력이 있고, 또 벨리데이션 체크에 있어 여러개의 조건이 들어갈 필요도 없다. 라이브러리 추가도 필요없다. String에서 바로 사용하거나 패키지를 사용하면 된다.

필자도 처음에 어려워서 그만둘까 했지만 하다보니 정말 재미있어서 계속 빠지게 되었다. 이글을 보고 있고, 정규표현식에 관심있는 많은 개발자들이 허접한 이 글을 통해서 코드작성에 좀더 빠르게, 좀더 아름답게 하는데 보템이 되었으면 한다.


출처 - http://pupustory.tistory.com/132

: