본문 바로가기
Study/JAVA

[Java] 9-2. 유용한 클래스

by jeongwle 2022. 9. 14.
728x90
반응형

 

유용한 클래스

1.  java.util.Objects 클래스

Object클래스의 보조 클래스로 모든 메서드가 static이다. 객체의 비교나 널 체크(null check)에 유용하다.

static boolean isNull(Object obj)
static boolean nonNull(Object obj)​

isNull()은 해당 객체가 널인지 확인하고 null이면 true를 반환하고 아니면 false를 반환한다. nonNull()은 isNull()과 정반대이다.


static <T> T requireNonNull(T obj)
static <T> T requireNonNull(T obj, String message)
static <T> T requireNonNull(T obj, Supplier<String> messageSupplier)​

requireNonNull()은 해당 객체가 널이 아니어야하는 경우에 사용한다. 만약 객체가 널이면 NullPointerException을 발생시킨다. 두 번째 매개변수로 지정하는 문자열은 예외 메시지가 된다.


Objects클래스에는 compare()가 존재한다. 두 비교대상이 같으면 0, 크면 양수, 작으면 음수를 반환한다.

static int compare(Object a, Object b, Comparator c)​

두 객체를 비교하는 기준은 Comparator이다. 이 친구는 11장에서 자세하게 배운다고 한다.


static boolean equals(Object a, Object b)
static boolean deepEquals(Object a, Object b)​

Objects클래스의 equals()는 null 검사를 하지 않아도 된다는 장점이 있다. equals()의 내부에서 널 검사를 하기 때문에 따로 조건식을 넣지 않아도 된다. 다만 a와 b 모두 널일 경우 true를 반환한다. deepEquals()는 다차원 배열의 비교도 가능하다.


static String toString(Object o)
static String toString(Object o, String nullDefault)​

toString()메서드도 내부적으로 널 검사를 한다는 것 말고는 특별한 것이 없다. 두 번째 메서드는 o가 널일 경우 대신 사용할 값을 지정할 수 있다.


static int hashCode(Object o)
static int hashCode(Object... values)​

hashCode() 역시 내부적으로 널 검사를 한 후 Object클래스의 hashCode()를 호출할 뿐이다. 단, 널일 경우 0을 반환한다. 보통 클래스에 선언된 인스턴스의 변수들의 hashCode()를 조합해서 반환하도록 hashCode()를 오버라이딩하는데, 그 대신 매개변수 타입이 가변인자인 두 번째 메서드를 사용하면 편리하다. hashCode() 또한 11장에서 자세하게 배운다고 한다.

 

2.  java.util.Random클래스

난수를 얻는 방법을 생각하면 지금까지 사용해온 Math.random()이 생각난다. Random클래스를 사용해도 난수를 얻을 수 있다. 사실 Math.random()은 내부적으로 Random클래스의 인스턴스를 생성해서 사용하는 것이다. 그래서 둘 중 편한것을 사용하면 된다.

double randNum = Math.random();
double randNum = new Random().nextDouble(); // 위의 문장과 동일

int num = (int)(Math.random() * 6) + 1;
int num = new Random().nextInt(6) + 1; // nextInt(6)은 0 ~ 6 사이의 정수를 반환​


Math.random()과 Random의 가장 큰 차이점은 종자값(seed)을 설정할 수 있다는 것이다. 종자값이 같은 Random인스턴스들은 항상 같은 난수를 같은 순서대로 반환한다.

Random클래스의 생성자와 메서드
생성자 Random()은 종자값을 System.currentTimeMillis()로 하기 때문에 실행할 때마다 얻는 난수가 달라진다.

메서드 설 명
 RanDom()  현재시간을 종자값으로 이용하는 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인 가우시안분포에 따른 double형 난수를 반환
 int nextInt()  int타입의 난수를 반환(int의 범위)
 int nextInt(int n)  0 ~ n의 범위의 int값을 반환(n은 범위에 포함되지 않음)
 long nextLong()  long타입의 난수를 반환(long의 범위)
 void setSeed(long seed)  종자값을 주어진 값으로 변경
*nextBytes()는 BigInteger(int signum, byte[] magnitude)와 함께 사용하면 int 범위보다 넓은 범위의 난수를 얻을 수 있다는 것도 알아두자

import java.util.Arrays;

public class RandomEx3 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.print(getRand(5, 10) + " ");
        }
        System.out.println();

        int[] result = fillRand(new int[10], new int[]{2, 3, 7, 5});
        System.out.println(Arrays.toString(result));
    }

    // 배열에 from과 to 범위의 값들로 채워서 반환한다.
    public static int[] fillRand(int[] arr, int from, int to) {
        for (int i = 0; i < arr.length; i++) {
            arr[i] = getRand(from, to);
        }
        return arr;
    }

    // 배열 arr을 배열 data에 있는 값들로 채워서 반환
    public static int[] fillRand(int[] arr, int[] data) {
        for (int i = 0; i < arr.length; i++) {
            arr[i] = data[getRand(0, data.length - 1)];
        }
        return arr;
    }

    // from과 to범위의 정수(int)값을 반환한다. from과 to 모두 범위에 포함된다.
    public static int getRand(int from, int to) {
        return (int) (Math.random() * (Math.abs(to - from) + 1)) + Math.min(from, to);
    }
}​

하나의 예제이다. 이렇게 자주 사용되는 코드를 메서드로 만들어 놓으면 도움이 된다.

 

3. 정규식(Regular Expression) - java.util.regex패키지

정규식은 텍스트 데이터 중에서 원하는 조건(패턴, pattern)과 일치하는 문자열을 찾아내기 위해 사용되고, 미리 정의된 기호와 문자를 이용해서 작성한 문자열을 뜻한다. 정규식을 이용하면 많은 양의 텍스트 파일 중에서 원하는 데이터를 손쉽게 뽑아낼 수 있고 입력된 데이터가 형식에 맞는지 체크할 수 있다. 자주 사용되는 정규식의 작성 예를 보고 응용할 수 있을 정도까지 학습하자.

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegularEx1 {
    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 (String word : data) {
            Matcher m = p.matcher(word);
            if (m.matches()) {
                System.out.print(word + ","); // ca,co,car,combat,count,
            }
        }
    }
}​


import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegularEx2 {
    public static void main(String[] args) {
        String[] datas = {
                "bat", "baby", "bonus",
                "c", "cA", "ca", "co", "c.",
                "c0", "c#", "car", "combat", "count",
                "date", "disc"
        };

        String[] patterns = {
                ".*", "c[a-z]*", "c[a-z]", "c[a-zA-Z]", "c[a-zA-Z0-9]",
                "c.", "c.*", "c\\.", "c\\w", "c\\d",
                "c.*t", "[b|c].*", ".*a.*", ".*a.+", "[b|c].{2}"
        };

        for (String pattern : patterns) {
            Pattern p = Pattern.compile(pattern);
            StringBuilder sb = new StringBuilder(60);
            sb.append("Pattern : ")
                    .append(pattern)
                    .append("\n")
                    .append("결과 : ");
            for (String data : datas) {
                Matcher m = p.matcher(data);
                if (m.matches()) {
                    sb.append(data)
                            .append(",");
                }
            }
            System.out.println(sb);
        }
    }
}​


정규식 패턴 설 명 결 과
c[a-z]*  c로 시작하는 영단어  c, ca, co, car, combat, count
c[a-z]  c로 시작하는 두자리 영단어  ca, co
c[a-zA-Z]  c로 시작하는 두자리 영단어(대소문자 구분 X)  cA, ca, co
c[a-zA-Z0-9]
c\w
 c로 시작하고 숫자와 영어로 조합된 두글자  cA, ca, co, c0
.*  모든 문자열  datas에 있는 모든 문자열
c.  c로 시작하는 두자리 문자열  cA, ca, co, c., c0, c#
c.*  c로 시작하는 모든 문자열  cA, ca, co, c., c0, c#,
 car, combat, count
c\.  c.이랑 일치하는 문자열  c.
c\d
c[0-9]
 c와 숫자로 구성된 두자리 문자열  c0
c.*t  c로 시작하고 t로 끝나는 모든 문자열  combat, count
[b|c].*
[bc].*
[b-c].*
 b 또는 c로 시작하는 문자열  bat, baby, bonus, c, cA, ca, co,
 c., c0, c#, car, combat, count
[^b|c].*
[^bc].*
[^b-c].*
 b 또는 c로 시작하지 않는 문자열  date, disc
.*a.*  a를 포함하는 모든 문자열
 * : 0 또는 그 이상의 문자
 bat, baby, ca, car, combat, date
.*a.+  a를 포함하는 모든 문자열
 + : 1 또는 그 이상의 문자
 bay, baby, car, combat, date
[b|c].{2}  b 또는 c로 시작하는 세자리 문자열  bat, car

정규식 패턴 설 명
0\\d{1,2}  0으로 시작하는 최소 2자리 최대 3자리 숫자(0포함)
\\d{3,4}  최소 3자리 최대 4자리의 숫자
\\d{4}  4자리의 숫자

* m.group(), m.find(), m.appendReplacement(), m.appendTail() 찾아보기 m은 Matcher

 

4.  java.util.Scanner 클래스

Scanner는 화면, 파일, 문자열과 같은 입력소스로부터 문자 데이터를 읽어오는데 도움을 줄 목적으로 JDK1.5부터 추가되었다. Scanner에는 다음과 같은 생성자를 지원하기 때문에 다양한 입력소스로부터 데이터를 읽을 수 있다.

Scanner(String source)
Scanner(File source)
Scanner(InputStream source)
Scanner(Readable source)
Scanner(ReadableByteChannel source)
Scanner(Path source) // JDK1.7부터​


또 Scanner는 정규식 표현을 이용한 라인단위의 검색을 지원하고 구분자(delimiter)에도 정규식 표현을 사용할 수 있어 복잡한 형태의 구분자로 처리할 수 있다.

Scanner useDelimiter(Pattern pattern)
Scanner useDelimiter(String pattern)​

JDK1.6부터는 화면 입출력만 전담하는 java.io.Console이 새로 추가되었다. Console은 이클립스와 같은 IDE에서 잘 동작하지 않아서 이 책에서는 Scanner를 주로 사용한다고 한다. Scanner와 Console은 사용법이나 성능측면에서 거의 같아 어떤걸 사용해도 상관없다.

 

5.  java.util.StringTokenizer클래스

StringTokenizer는 긴 문자열을 지정된 구분자(delimiter)을 기준으로 토큰(token)이라는 여러 개의 문자열로 잘라내는 데 사용한다. 그동안 학습했던 split이나 useDelimiter를 사용하는 방법도 있지만 정규식 표현이 익숙하지 않을 경우 StringTokenizer를 사용하는 것이 간단하면서도 명확한 결과를 얻을 수 있다.

StringTokenizer의 생성자와 메서드

생성자 / 메서드 설 명
 StringTokenizer(String str,
 String delim)
 문자열(str)을 지정된 구분자(delim)로 나누는 StringTokenizer
 를 생성한다.(구분자는 토큰으로 간주되지 않음)
 StringTokenizer(String str,
 String delim,
 boolean returnDelims)
 문자열(str)을 지정된 구분자(delim)로 나누는 StringTokenizer
 를 생성한다. returnDelims의 값을 true로 하면 구분자도 토큰으로
 간주한다.
 int countTokens()  전체 토큰의 수를 반환한다.
 boolean hasMoreTokens()  토큰이 남아있는지 알려준다.
 String nextToken()  다음 토큰을 반환한다.

import java.util.StringTokenizer;

public class StringTokenizerEx4 {
    public static void main(String[] args) {
        String input = "삼십만삼천백십오";
        System.out.println(input);
        System.out.println(hangulToNum(input));
    }

    public static long hangulToNum(String input) {
        long result = 0;
        long tmpResult = 0;
        long num = 0;

        final String NUMBER = "영일이삼사오육칠팔구";
        final String UNIT = "십백천만억조";
        final long[] UNIT_NUM = {10, 100, 1000, 10000, (long) 1e8, (long) 1e12};

        StringTokenizer st = new StringTokenizer(input, UNIT, true);
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            int check = NUMBER.indexOf(token);

            if (check == -1) {
                if ("만억조".indexOf(token) == -1) {
                    tmpResult += (num != 0 ? num : 1) * UNIT_NUM[UNIT.indexOf(token)];
                } else {
                    tmpResult += num;
                    result += (tmpResult != 0 ? tmpResult : 1) * UNIT_NUM[UNIT.indexOf(token)];
                    tmpResult = 0;
                }
                num = 0;
            } else {
                num = check;
            }
        }
        return result + tmpResult + num;
    }
}​

단번에 이해하기 힘들었던 예제이지만 이렇게 사용하면 될 것 같다. 참고용으로 남겨둔다.

 

6. java.math.BigInteger클래스

정수형으로 표현할 수 있는 값의 한계가 있다. 가장 큰 정수형 타입인 long보다 더 큰 값을 다뤄야 할 때 사용하면 좋은 것이 BigInteger이다.

BigInteger의 생성
BigInteger를 생성하는 방법은 여러가지가 있는데 문자열로 숫자를 표현하는 것이 일반적이다.

BigInteger val;
val = new BigInteger("12345678901234567890");	// 문자열로 생성
val = new BigInteger("FFFF", 16);	// n진수(radix)의 문자열로 생성
val = BigInteger.valueOf(1234567890L);		// 숫자로 생성​


다른 타입으로의 변환
BigInteger를 문자열 또는 byte배열로 변환하는 메서드는 다음과 같다.

String toString()		// 문자열로 변환
String toString(int radix)	// 지정된 진법의 문자열로 변환
byte[] toByteArray()		// byte 배열로 변환
int intValue()
long longValue()
float floatValue()
double doubleValue()
byte byteValueExact()
int intValueExact()
long longValueExact()​


BigInteger의 연산
BigInteger에는 정수형에 사용할 수 있는 모든 연산자와 수학적인 계산을 쉽게 해주는 메서드들이 정의되어 있다.

BigInteger add(BigInteger val)
BigInteger subtract(BigInteger val)
BigInteger multiply(BigInteger val)
BigInteger divide(BigInteger val)
BigInteger remainder(BigInteger val)​


비트 연산 메서드
워낙 큰 숫자를 다루기 위한 클래스이므로 성능 향상을 위해 비트단위로 연산을 수행하는 메서드들이 많다. and, or, xor, not과 같이 비트연산자를 구현한 메서드들은 물론이고 다음과 같은 메서드들도 제공한다.

int bitCount()	// 2진수로 표현 시 1의 개수(음수는 0의 개수) 반환
int bitLength()	// 2진수로 표현하는데 필요한 bit수
boolean testBit(int n)	// 우측에서 n+1번쨰 비트가 1이면 true, 0이면 false
BigInteger setBit(int n) // 우측에서 n+1번째 비트를 1로 변경
BigInteger clearBit(int n) // 우측에서 n+1번째 비트를 0으로 변경
BigInteger flipBit(int n) // 우측에서 n+1번째 비트를 전환(1 -> 0, 0 -> 1)​

 

7. java.math.BigDecimal클래스

BigDecimal은 실수형과 달리 정수를 이용해서 실수를 표현한다. 오차가 없는 2진 정수로 변환하여 다루는 것이다. 실수를 정수와 10의 제곱의 곱으로 표현한다. scale은 0부터 Integer.MAX_VALUE사이의 범위에 있는 값이다. 그리고 BigDecimal은 정수를 저장하는데 BigInteger를 사용한다.

private final BigInteger intVal;	// 정수
private final int scale;		// 지수(scale)
private transient int precision;	// 정밀도 - 정수의 자리수

BigDecimal val = new BigDecimal("123.45");	// 12345×10⁻²
System.out.println(val.unscaledValue());	// 12345
System.out.println(val.scale());		// 2
System.out.println(val.precision());		// 5


BigDecimal의 생성
BigDecimal 또한 생성하는 방법이 여러가지가 있는데 문자열로 숫자를 표현하는 것이 일반적이다.

BigDecimal val;
val = new BigDecimal("123.4567890");	// 문자열
val = new BigDecimal(123.456);		// double타입
val = new BigDecimal(123456);		// int, long타입
val = BigDecimal.valueOf(123.456);	// valueOf(double)
val = BigDecimal.valueOf(123456);	// valueOf(int)​

한가지 주의할 점은 double타입의 값을 매개변수로 갖는 생성자를 사용하면 오차가 발생할 수 있다는 것이다.


다른 타입으로의 변환

String toPlainString()	// 숫자로만 표현
String toString		// 필요하면 지수형태로 표현 가능
int intValue()
long longValue()
float floatValue()
double doubleValue()
byte byteValueExact()
short shortValueExact()
int intValueExact()
long longValueExact()
BigInteger toBigIntegerExact()​


BigDecimal의 연산

BigDecimal add(BigDecimal val)
BigDecimal subtract(BigDecimal val)
BigDecimal multiply(BigDecimal val)
BigDecimal divide(BigDecimal val)
BigDecimal remainder(BigDecimal val)​

한 가지 알아둬야 할 것은 연산결과의 정수, 지수, 정밀도가 달라진다는 것이다. 곱셈에서는 두 피연산자의 scale을 더하고 나눗셈에서는 뺀다. 덧셈과 뺄셈에서는 둘 중에서 자리수가 높은 쪽으로 맞추기 위해서 두 scale중 큰 쪽이 결과가 된다.

반올림 모드 - divide()와 setScale()
다른 연산과 달리 나눗셈을 처리하기 위한 메서드는 다음과 같이 다양한 버전이 존재한다. 나눗셈의 결과를 어떻게 반올림(roundingMode)처리 할 것인가와 몇 번째 자리(scale)에서 반올림할 것인지를 지정할 수 있다. 나눗셈에서는 어쩔 수 없이 오차가 발생한다.

BigDecimal divide(BigDecimal divisor)
BigDecimal divide(BigDecimal divisor, int roundingMode)
BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode)
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
BigDecimal divide(BigDecimal divisor, MathContext mc)​

roundingMode는 반올림 처리방법에 대한 것으로 BigDecimal에 정의된 ROUND_로 시작하는 상수들 중 하나를 선택해서 사용하면 된다. RoundingMode는 이 상수들을 열거형으로 정의한 것으로 나중에 추가되었다. 가능하면 열거형 RoundingMode를 사용하자

상수 설명
 CEILING  올림
 FLOOR  내림
 UP  양수일 때는 올림, 음수일 때는 내림
 DOWN  양수일 때는 내림, 음수일 때는 올림
 HALF_UP  반올림(5이상 올림, 5미만 버림)
 HALF_EVEN  반올림(반올림 자리의 값이 짝수면 HALF_DOWN, 홀수면 HALF_UP)
 HALF_DOWN  반올림(6이상 올림, 6미만 버림)
 UNNECESSARY  나눗셈의 결과가 딱 떨어지는 수가 아닐 경우 예외(ArithmeticException)

나눗셈의 결과가 무한소수인 경우 반올림 모드를 지정해주어야 한다. 그렇지 않을 경우 ArithmeticException이 발생한다.

java.math.MathContext
이 클래스는 반올림 모드와 정밀도를 하나로 묶어 놓은 것일 뿐 별다른 것은 없다. 한ㅋ 가지 주의할 점은 divide()에서 scale이 소수점 이하의 자리수를 의미하는데, MathContext에서는 precision이 정수와 소수점 이하를 모두 포함한 자리수를 의미한다는 것이다.

scale의 변경
BigDecimal을 10으로 곱하거나 나누는 대신 scale의 값을 변경함으로써 같은 결과를 얻을 수 있다. scale을 변경하려면 setScale()을 이용하면 된다.

BigDecimal setScale(int newScale)
BigDecimal setScale(int newScale, int roundingMode)
BigDecimal setScale(int newScale, RoundingMode mode)​

setScale()로 scale 값을 줄이는 것은 10의 n제곱으로 나누는 것과 같으므로 오차가 발생할 수 있고 반올림 모드를 지정해주어야 한다.

 

6번과 7번 두개의 Big 시리즈들은 이해하지 못했다. 새로운 것들이 너무 많아서인지 아니면 그냥 멍청해서인지 아무튼 이해가 안된다. 일단은 읽어만 보고 나중에 필요할 때 공부를 하든 해야겠다.

728x90
반응형

댓글