공부했던 자료 정리하는 용도입니다.
재배포, 수정하지 마세요.
정규식(Regular Expression) - java.util.regex패키지
정규식이란 텍스트 데이터 중에서 원하는 조건(패턴, pattern)과 일치하는 문자열을 찾아내기 위해 사용하는 것으로 미리 정의된 기호와 문자를 이용해서 작성한 문자열을 말한다. 정규식을 이용하면 많은 양의 텍스트 파일 중에서 원하는 데이터를 손쉽게 뽑아낼 수 있고, 입력된 데이터가 형식에 맞는지 체크할 수도 있다. Java API문서에서 java.util.regex.Pattern 에 정규식에 사용되는 기호와 작성방법이 모두 설명되어 있다.
자주 사용되는 패턴
정규식패턴 | 설 명 | 결 과 |
c[a-z]* | c 로 시작하는 영단어 | c, ca, car, combat, count … |
c[a-z] | c 로 시작하는 2자리 영단어 | ca, ci … |
c[a-zA-Z] | c 로 시작하는 2자리 영단어(대소문자 구분 X) | cA, co, aP… |
c[a-zA-Z0-9] c\w |
c 로 시작하고 숫자 + 영어로 조합된 2글자 | c0, cE, cm… |
.* | 모든 문자열 | |
c. | c 로 시작하는 2자리 문자열 | c. ca, c# |
c.* | c 로 시작하는 모든 문자열(기호포함) | c., c0, c#, count … |
c\. | c. 와 일치하는 문자열 . 은 패턴작성에 사용되는 문자라 escape문자인 \ 를 사용해야함 | c. |
c\d c[0-9] |
c 와 숫자로 구성된 2자리 문자열 | c0 |
c.*t | c 로 시작하고 t 로끝나는 모든 문자열 | combat, count… |
[b|c].* [bc].* [b-c].* |
b 또는 c 로 시작하는 문자열 | bonus, cA, bat, c., c0, c#… |
[^b|c].* [^bc].* [^b-c].* |
b 또는 c 로 시작하지 않는 문자열 | date, disc… |
.*a.* | a 를 포함하는 모든 문자열 * : 0 또는 그 이상의 문자 |
ca, combat, date… |
.*a.* | a 를 포함하는 모든 문자열 + : 1 또는 그 이상의 문자, + 는 * 과 달리 반드시 하나 이상의 문자가 있어야 하므로 a 로 끝나는 단어는 포함되지 X |
date, bat… |
[b|c].{2} | b 또는 c 로 시작하는 세자리 문자열 ( b 또는 c 다음에 두자리이므로 모두 세자리) |
bat, car… |
0\\d{1,2} | 0 으로 시작하는 최소 2자리 최대 3자리 숫자(0포함) | |
\\d{3,4} | 최소 3자리 최대 4자리의 숫자 | 512, 8516… |
\\d{4} | 4자리의 숫자 |
문자열 검사
package Example;
import java.util.regex.*; // Pattern과 Matcher가 속한 패키지
public class Example {
public static void main(String[] args) {
String[] data = {"bat", "baby", "bonus", "cA", "ca", "co", "c.", "c0", "car", "combat", "count", "date", "disc"};
Pattern p = Pattern.compile("c[a-z]*"); //c로 시작하는 소문자영단어
for(int i = 0 ; i < data.length ; i++) {
Matcher m = p.matcher(data[i]);
if(m.matches())
System.out.print(data[i] + ",");
}
}
}
1. 정규식을 매개변수로 Pattern 클래스의 static 메서드인 Pattern compile(String regex) 을 호출하여 Pattern 인스턴스를 얻는다. |
문자 배열인 data 에 담긴 문자열을 정규식과 맞는지 검사해서 일치하는 문자열을 출력하는 예제이다. Pattern 은 정규식을 정의하는 데 사용되고 Matcher 는 정규식(패턴)을 데이터와 비교하는 역할을 한다.
Matcher 클래스의 메서드들
메서드 | 설 명 |
find( ) | 패턴이 일치하는 경우 true를 반환, 불일치하는 경우 false반환(여러개가 매칭되는 경우 반복실행하면 일치하는 부분 다음부터 이어서 매칭됨) |
find(int start) | start 위치 이후부터 매칭검색 |
start( ) | 매칭되는 문자열의 시작위치 반환 |
start(int group) | 지정된 그룹이 매칭되는 시작위치 반환 |
end( ) | 매칭되는 문자열 끝위치의 다음 문자위치 반환 |
end(int group) | 지정된 그룹이 매칭되는 끝위치의 다음 문자위치 반환 |
group( ) | 매칭된 부분을 반환 |
group(int group) | 그룹화되어 매칭된 패턴중 group 번째 부분 반환 |
groupCount( ) | 괄호로 지정해서 그룹핑한 패턴의 전체 개수 반환 |
matches( ) | 패턴이 전체 문자열과 일치할 경우 true반환 |
package Example;
import java.util.regex.*; // Pattern과 Matcher가 속한 패키지
public class Example {
public static void main(String[] args) {
String source = "HP:011-1111-1111, HOME:02-999-9999";
String pattern = "(0\\d{1,2})-(\\d{3,4})-(\\d{4})";
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(source);
System.out.println("그룹의 개수 : " + m.groupCount()); //그룹화된 개수가 몇개인지 출력
int i = 0;
while(m.find()) { // m의 요소가 있을 때까지
System.out.println(++i + ": " + m.group() + " -> " + m.group(i));
//System.out.println(++i + ": " + m.group() + " -> " + m.group(1) + "," + m.group(2) + "," + m.group(3));
}
}
}
- 패턴을 괄호 ( ) 로 이용해서 세 부분( 0\\d{1,2} , \\d{3,4} , \\d{4} 으로 나눴기 때문에 group(1) , group(3) , group(3) …으로 호출할 수 있다.( group(int i) 의 i 를 실제 그룹의 수를 넘어가는 수로 하게 되면 예외( java.lang.IndexOutOfBoundsException )가 발생한다.)
- group( ) 이나 group(0) 은 그룹으로 매칭 된 문자열을 나눠지지 않은 전체로 반환한다.
- find( ) 는 주어진 소스에서 패턴과 일치하는 부분을 찾아내면 true 를 반환하고, 찾지 못하면 false 를 반환한다. find( ) 를 호출해서 패턴과 일치하는 부분을 찾은 뒤에 다시 한번 find( ) 를 호출하면 이전에 발견한 패턴과 일치하는 부분의 다음부터 다시 패턴 매칭을 시작한다.
package Example;
import java.util.regex.*; // Pattern과 Matcher가 속한 패키지
public class Example {
public static void main(String[] args) {
String source = "A broken hand works, but not broken heart.";
String pattern = "broken";
StringBuffer sb = new StringBuffer();
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(source);
System.out.println("source : " + source); //변환되기 이전 문장 출력
int i = 0;
while(m.find()) {
//start()와 end()로 일치하는 부분의 위치를 알아낼 수 있다.
System.out.println(++i + "번째 매칭 : " + m.start() + " ~ " + m.end());
// broken을 drunken으로 치환하여 sb에 저장
m.appendReplacement(sb, "drunken");
}
m.appendTail(sb); //마지막으로 변환 위치 이후의 문장들을 붙여넣어준다.
System.out.println("Replacement count : " + i);
System.out.println("result : " + sb.toString());
}
}
Matcher 의 find( ) 로 정규식과 일치하는 부분을 찾으면, start( ) 와 end( ) 로 위치를 찾아낼 수도 있고 appendReplacement(StringBuffer sb, String replacement) 를 이용해서 원하는 문자열( replacement )로 치환할 수도 있다. 치환된 결과는 StringBuffer 인 sb 에 저장된다.
1. 문자열 source 에서 broken 을 m.find( ) 로 찾은 후 처음으로 m.appendReplacement(sb, "drunken"); 가 호출되면 source 의 시작부터 broken 을 찾은 위치까지의 내용에 drunken 을 더해서 저장한다. |
java.util.Objects 클래스
Object 클래스의 보조 클래스로 Math 클래스처럼 모든 메서드가 static 이다. 객체의 비교나 널 체크(null check)에 유용하다. Object 클래스에는 두 객체의 등가 비교를 위한 equals( ) 만 있고, 대소 비교를 위한 compare( ) 가 없지만, Objects 에서는 compare( ) 가 있다. 비교대상이 같으면 0 , 크면 양수 , 작으면 음수 를 반환한다. 또한 Objects 클래스에 있는 equals( ) 는 null 검사를 위한 조건식을 따로 넣지 않아도 되는 장점이 있다. a 와 b 가 모두 null 일 경우에는 참을 반환한다.
메서드 | 설 명 |
static boolean isNull(Object obj) | 해당 객체가 null 인지 확인 (null이면 true를 반환, 아니면 false 반환) |
static boolean nonNull(Object obj) | isNull( ) 과 반대 ( !Objects.isNull(obj) 와 같다) |
static <T> T requireNonNull(T obj) static <T> T requireNonNull(T obj, String message) static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) |
T 는 Object 타입을 의미한다 |
static int compare(Object a, Object b, Comparator c) |
객체의 대소 비교 |
static boolean equals(Object a, Object b) |
Object 클래스에 정의된 equals( ) 와 같지만 null 검사를 위한 조건식을 따로 넣지 않아도 된다(a와 b가 모두 null인 경우에는 참을 반환) |
static boolean deepEquals(Object a, Object b) |
객체를 재귀적으로 비교(다차원 배열의 비교 가능) |
static String toString(Object o) ststic String toString(Object o, String nullDefault) |
Object 클래스에 정의된 toString( ) 와 같지만 내부적으로 null 검사를 하기 때문에 null 검사를 위한 조건식을 따로 넣지 않아도 된다. ( nullDefault 는 o가 null일 때 대신 사용할 값) |
static int hashCode(Object o) static int hash(Object... values) |
내부적으로 null 검사를 한 후 Object 클래스의 hashCode( ) 를 호출(단, null일때는 0을 반환) |
package Example;
import java.util.*;
import static java.util.Objects.*; //Objects클래스의 메서드를 static import
public class Example {
public static void main(String[] args) {
String[][] str2D_1 = new String[][] {{"aaa", "bbb"}, {"AAA", "BBB"}};
String[][] str2D_2 = new String[][] {{"aaa", "bbb"}, {"AAA", "BBB"}};
System.out.print("str2D_1 = {");
for(String[] tmp : str2D_1) //str2D_1을 출력하기 위한 반복문
System.out.print(Arrays.toString(tmp));
System.out.println("}");
System.out.print("str2D_2 = {");
for(String[] tmp : str2D_1) //str2D_1을 출력하기 위한 반복문
System.out.print(Arrays.toString(tmp));
System.out.println("}");
System.out.println("equals(str2D_1, str2D_2) = " + Objects.equals(str2D_1, str2D_2));
System.out.println("deepEquals(str2D_1. str2D_2) = " + Objects.deepEquals(str2D_1, str2D_2));
System.out.println("isNull(null) = " + isNull(null));
System.out.println("nonNull(null) = " + nonNull(null));
System.out.println("hashCode(null) = " + Objects.hashCode(null)); //null일경우 0반환
System.out.println("toString(null) = " + Objects.toString(null));
System.out.println("toString(null, \"\") = " + Objects.toString(null, ""));
Comparator c = String.CASE_INSENSITIVE_ORDER; //대소문자 구분 없이 비교
System.out.println("compare(\"aa\",\"bb\") = " + compare("aa","bb",c));
System.out.println("compare(\"bb\",\"aa\") = " + compare("bb","aa",c));
System.out.println("compare(\"ab\",\"AB\") = " + compare("ab","AB",c));
}
}
위의 표를 코드로 확인해보는 예제이다. static import 문을 사용했음에도 Object 클래스의 메서드와 같은 것들은 컴파일러가 구별을 못한다.(충돌남) 이럴 때는 클래스의 이름을 붙여줄 수밖에 없다. 또한 String 클래스에 상수로 정의되어 있는 Comparator 가 있어서 이걸 이용해서 compare( ) 를 호출했다. Comparator 는 문자열을 대소문자 구분 없이 비교할 때 사용하기 위한 것이다. 그래서 ab 와 AB 를 비교한 결과가 0 으로 같다고 나온다.
java.util.Scanner 클래스
Scanner(String source)
Scanner(File source)
Scanner(InputStream source)
Scanner(Readable source)
Scanner(ReadableByteChannel source)
Scanner(Path source) // JDK 1.7부터 가능
Scanner 클래스를 이용하면 화면, 파일, 문자열과 같은 입력 소스로부터 문자 데이터를 읽어올 수 있다.
Scanner 에는 위의 표처럼 다양한 생성자를 지원하기 때문에 다양한 입력 소스로부터 데이터를 읽을 수 있다.
Scanner useDelimiter(Pattern pattern)
Scanner useDelimiter(String pattern)
또한 정규식 표현(Regular expression)을 이용한 라인 단위의 검색을 지원하며 구분자(delimiter)에도 정규식 표현을 사용할 수 있어서 복잡한 형태의 구분자도 처리할 수 있다. ( Scanner 말고도 입출력을 담당하는 java.io.Console 도 있지만 이클립스와 같은 IDE에서는 잘 동작하지 않기 때문에 거의 Scanner 를 사용한다.)
입력 변환
자료형 | 메서드 |
boolean | nextBoolean( ) |
byte | nextByte( ) |
short | nextShort( ) |
int | nextInt( ) |
long | nextLong( ) |
double | nextDouble( ) |
float | nextFloat( ) |
String | nextLine( ) |
Scanner 에서는 자료형을 변환하는 메서드를 제공해주기 때문에 입력받은 문자열을 변환하는 과정이 비교적 간단하다.
package Example;
import java.util.*; //Scanner 클래스를 사용하기 위한 import
public class Example {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
String[] argArr = null;
while(true) {
String prompt = ">>";
System.out.print(prompt);
String input = s.nextLine(); //라인단위로 입력을 받는다
input = input.trim(); //입력받은 값에서 앞뒤 공백 제거
argArr = input.split(" +"); //공백이 여러개 일수도 있어서 정규식을 " +"로함(하나이상의 공백)
String command = argArr[0].trim(); //공백으로 구분되는 첫번째 부분을 공백을 제거해서 command로 저장
if("".equals(command)) continue; //첫번째 부분이 없어도(공백으로 제거되어도) 계속 진행
command = command.toLowerCase(); //명령어를 소문자로 변환(q나 Q를 똑같이 구분하기 위해서)
if(command.equals("q")) {
System.exit(0);
}else {
for(int i = 0 ; i < argArr.length ; i++)
System.out.println(argArr[i]);
}
}
}
}
입력받은 내용을 공백을 구분자로 해서 출력하는 예제이다. 입력받은 라인의 단어는 공백이 여러 개일 수 있으므로 split 의 구분자로 정규식을 지정한다. 정규식의 " +" 는 하나이상의 공백을 의미한다. 그래서 tab 은 구분하지 못한다( 456 과 789 사이) 또한 argArr[0].trim( ) 을 사용했기 때문에 공백으로 구분되는 첫 번째 부분이 q 나 Q 가 되면 프로그램이 종료된다.( quit 같이 단독으로 들어가지 않는 것은 제외)
java.util.StringTokenizer 클래스
StringTokenizer 는 긴 문자열을 구분자(delimiter)를 기준으로 토큰(token)이라는 여러 개의 문자열로 잘라내는 데 사용한다. 예를 들어 100,200,300,400 이라는 문자열을 , 를 구분자로 잘라내면 100 , 200 , 300 , 400 이라는 4개의 문자열(토큰)을 얻을 수 있다. String 의 split(String regex) 이나 Scanner 의 useDelimiter(String pattern) 과 다른 점은 구분자로 단 하나의 문자밖에 사용할 수 없다는 것이다. 복잡한 형태의 구분자를 사용할 경우에는 split( ) 이나 useDelimiter( ) 와 같이 정규식을 사용하는 메서드를 사용해야 한다.
package Example;
import java.util.*; //StringTokenizer클래스를 사용하기 위한 import
public class Example {
public static void main(String[] args) {
String expression = "x=100*(200*300)/2";
StringTokenizer st = new StringTokenizer(expression, "+-*/=()", true);
while(st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
}
}
구분자로 여러 문자들을 지정할 경우 모든 문자가 전부 구분자가 된다.
StringTokenizer의 생성자와 메서드
생성자 / 메서드 | 설 명 |
StringTokenizer(String str, Strin delim) | 문자열( str )을 지정된 구분자( delim )로 나누는 StringTokenizer 를 생성 (구분자는 토큰으로 간주되지 X) |
StringTokenizer(String str, String delim, boolen returnDelims) | 문자열( str )를 지정된 구분자( delim )로 나누는 StringTokenizer 를 생성. returnDelims 의 값을 true로하면 구분자도 토큰으로 간주 |
int countTokens( ) | 전체 토큰의 수를 반환 |
boolean hasMoreTokens( ) | 토큰이 남아있는지 알려줌 |
String nextToken( ) | 다음 토큰을 반환 |
StringTokenizer과 String클래스의 split( )
package Example;
import java.util.*; //StringTokenizer클래스를 사용하기 위한 import
public class Example {
public static void main(String[] args) {
String data = "100,,,200,300";
String[] result = data.split(",");
StringTokenizer st = new StringTokenizer(data, ",");
for(int i = 0 ; i < result.length ; i++) { // split()을 출력하기 위한 반복문
System.out.print(result[i] + "|");
}
System.out.printf("%nsplit()의 개수 : " + result.length + "%n%n");
int i = 0;
for(; st.hasMoreElements() ; i++) { // StringTokenizer을 출력하기 위한 반복문
System.out.print(st.nextToken() + "|");
}
System.out.printf("%nStringTokenizer개수 : " + i);
}
}
구분자를 , 로 하는 문자열 데이터를 String 클래스의 split( ) 과 StringTokenizer 로 잘라낸 결과를 비교하는 예제이다. split( ) 은 빈 문자열도 토큰으로 인식하는 반면 StringTokenizer 는 빈 문자열을 토큰으로 인식하지 않기 때문에 토큰 개수에 차이가 있는 것을 확인할 수 있다. 이외에도 성능의 차이가 있는데 split( ) 은 데이터를 토큰으로 잘라낸 결과를 배열에 담아서 반환하기 때문에 데이터를 토큰으로 바로바로 잘라서 반환하는 StringTokenizer 보다 성능이 떨어진다. 그러나 데이터의 양이 많은 경우가 아니라면 별 문제가 되지 않는다.
java.util.Random 클래스
double randNum = Math.random( );
double randNum = new Random( ).nextDouble( );
Random 클래스를 사용하면 난수를 얻을 수 있다. Math.random( ) 은 내부적으로 Random 클래스의 인스턴스를 생성해서 사용하므로 위의 두 문장은 같은 의미이다. Math.random( ) 과 Random 의 가장 큰 차이점은 seed (종자값)값의 유무이다. 종자값이 같은 Random 인스턴스들은 항상 같은 난수를 같은 순서대로 반환한다. seed 값은 난수를 만드는 공식에 사용되는 값으로 같은 공식에 같은 값을 넣으면 같은 결과를 얻는 것처럼 같은 seed 값을 넣으면 같은 난수를 얻게 된다.
Random클래스의 생성자와 메서드
메서드 | 설 명 |
Random( ) | System.currentTimeMillis( ) (현재시간)을 seed (종자값)로 이용하는 Random 인스턴스를 생성한다. (실행할 때마다 얻는 난수가 달라진다.) |
Random(long seed) | 매개변수 seed 를 종자값으로 하는 Random 인스턴스를 생성 |
boolean nextBoolean( ) | boolean 타입의 난수 반환 |
void nextBytes(byte[ ] bytes) | bytes 배열에 byte 타입의 난수를 채워서 반환 |
double nextDouble( ) | double 타입의 난수를 반환 ( 0.0 <= x < 1.0 ) |
float nextFloat( ) | float 타입의 난수를 반환( 0.0 <= x < 1.0 ) |
double nextGaussian( ) | 평균은 0.0 이고 표준편차는 1.0 인 가우시안(Gaussian)분포에 따른 double 형의 난수 반환 |
int nextInt( ) | int 타입의 난수 반환(int의 범위) |
int nextInt(int n) | 0 ~ n 의 범위에 있는 int 값을 반환( n 은 범위에 포함되지 X) |
long nextLong( ) | long 타입의 난수를 반환(long의 범위) |
void setSeed(long seed) | 종자값을 주어진 값( seed )으로 변경 |
package Example;
import java.util.*;
public class Example {
public static void main(String[] args) {
Random rand1 = new Random(1);
Random rand2 = new Random(1);
System.out.println("== rand1 ==");
for(int i = 0 ; i < 5 ; i++)
System.out.println(i + " : " + rand1.nextInt());
System.out.println();
System.out.println("== rand2 ==");
for(int i = 0 ; i < 5 ; i++)
System.out.println(i + " : " + rand2.nextInt());
}
}
seed 값이 같아서 같은 값들을 같은 순서로 얻는 것을 확인할 수 있다. 이처럼 같은 종자값을 갖는 Random 인스턴스는 시스템이나 실행시간 등에 관계없이 항상 같은 값을 같은 순서로 반환할 것을 보장한다.
'Back end > Java' 카테고리의 다른 글
[Java] 컬렉션 프레임워크(Collections Framework) (0) | 2019.07.17 |
---|---|
[Java] java.math 패키지 - BigInteger클래스와 BigDecimal 클래스 (0) | 2019.07.17 |
[Java] java.lang 패키지 - 래퍼 클래스 (Wrapper Class) (0) | 2019.07.16 |
[Java] java.lang 패키지 - Math 클래스 (0) | 2019.07.15 |
[Java] java.lang 패키지 - StringBuffer 클래스와 StringBuilder 클래스 (0) | 2019.07.15 |