본문 바로가기
Book/Java의 정석

[Chapter 9] java.lang 패키지와 유용한 클래스

by slchoi 2022. 4. 1.
728x90
SMALL

'자바의 정석 3rd Edition'를 공부하며 정리한 내용입니다.

1. java.lang 패키지


  • 자바프로그래밍에 가장 기본이 되는 클래스들을 포함하기 때문에 import문 없이도 사용 가능

1.1 Object 클래스

  • 멤버변수는 없고 11개의 메서드만 가짐
    • 메서드들은 모든 인스턴스가 가져야할 기본적인 것들

1. equals(Object obj)

public boolean equals(Object obj){
    return (this==obj);
}
  • 매개변수로 객체의 참조변수를 받아 비교하여 그 결과를 boolean값으로 알려 주는 역할
  • 서로 다른 두 객체를 equals 메서드로 비교하면 항상 false를 결과로 얻음
    • 이유: 두 객체의 같고 다름을 참조변수의 값으로 판단
    • 객체를 생성할 때, 메모리의 비어있는 공간을 찾아 생성하므로 서로 다른 두 개의 객체가 같은 주소를 갖을 수 없음
    • 두 개 이상의 참조변수가 같은 주소값을 갖는 것(한 객체를 참조하는 것)은 가능
  • equals 메서드는 결국 두 개의 참조변수가 같은 객체를 참조하고 있는지, 즉 두 참조변수에 저장된 값(주소값)이 같은지를 판단하는 기능만 함
    • equals를 사용해 저장된 값을 비교하고 하고자 하나면 equals 메서드를 오버하이딩해 주소가 아닌 객체에 저장된 내용을 비교하도록 변경해주어야 함
    • String, Date, File, wrapped 클래스의 equals메서드는 주소값이 아닌 내용을 비교하도록 오버라이딩 되어 있음
    • StringBuffer 클래스는 오버라이딩 되어있지 않음

2. hashCode()

  • 해싱(hashing) 기법에 사용되는 '해시함수(hash function)'를 구현한 것
    • 해싱: 데이터관리기법 중 하나. 다량의 데이터를 저장하고 검색하는 데 유용
    • 해시함수: 찾고자하는 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시코드(hash code)를 반환
  • 해시코드가 같은 두 객체가 존재하는 것은 가능하지만, Object 클래스에 정의된 hashCode 메서드는 객체의 주소값으로 해시코드를 만들어 반환하기 때문에 32bit JVM에서는 서로 다른 두 객체는 결코 같은 해시코드를 가질 수 없없지만, 64bit JVM에서는 8byte 주소값으로 해시코드(4byte)를 만들기 때문에 해시코드가 중복될 수 있음
  • 클래스의 인스턴스변수 값으로 객체의 같고 다름을 판단해야하는 경우라면 equals 메서드와 hashCode 메서드도 적절히 오버라이딩해야 함
    • 같은 객체라면 hashCode 메서드를 호출했을 때의 결과값인 해시코드도 같아야하기 때문

3. toString()

public String toString() {
    return getClass().getName()+"@"+Integer.toHexString(hashcode());
}
  • 인스턴스에 대한 정보를 문자열(String)로 제공할 목적으로 정의한 것
  • 인스턴스 정보를 제공한다는 것은 대부분의 경우 인스턴스 변수에 저장된 값들을 문자열로 표현한다는 의미
  • 클래스 작성시 toString()을 오버라이딩하지 않으면 클래스이름에 16진수의 해시코드를 얻게 됨
    • String 클래스의 toString()은 String 인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩됨
    • Date 클래스의 경우 Date 인스턴스가 갖고 있는 날짜와 시간을 문자열로 변환하여 반환하도록 오버라이딩됨

4. clone()

  • 자신을 복제하여 새로운 인스턴스를 생성
  • 어떤 인스턴스에 대해 작업할 때, 원래의 인스턴스는 보존하고 clone 메서드를 이용해 새로운 인스턴스를 생성하여 작업하면, 작업이전의 값이 보존되므로 작업에 실패해서 원래의 상태로 되돌리거나 변경되기 전의 값을 참고하는데 도움이 됨
  • Object 클래스에 정의된 clone()은 단순히 인스턴스변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전히 인스턴스 복제가 이루어지지 않음
  • clone()을 사용하려면
    • 복제할 클래스가 Cloneable 인터페이스를 구현해야 하고
    • clone()을 오버라이딩하면서
    • 접근 제어자를 protected에서 public으로 변경 (상속관계가 없는 다른 클래스에서 clone() 호출 가능하도록)
    • 조상클래스의 clone()을 호출하는 코드가 포함된 try-catch문 작성

공변 반환타입

  • 공변 반환타입(covariant return type): 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것
  • 장점: 조상의 타입이 아닌 실제로 반환되는 자손 객체의 타입으로 반환할 수 있어 형변환이 줄어듦

얕은 복사와 깊은 복사

  • 얕은 복사(shallow copy): 원본을 변경하면 복사본도 영향을 받음
  • 깊은 복사(deep copy): 원본이 참조하고 있는 객체까지 복제하는 것
    • 원본과 복사본이 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않음

5. getClass()

  • 자신이 속한 클래스의 Class 객체를 반환하는 메서드
    • Class 객체는 이름이 'Class'인 클래스 객체
  • Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재. 클래스 파일이 클래스 로더에 의해 메모리에 올라갈 때 자동으로 생성됨
    • 클래스 로더: 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 수행
      • 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고, 있으면 객체의 참조를 반환하고 없으면 클래스 패스에 지정된 경로를 따라 클래스 파일을 찾음
      • 못 찾으면 ClassNotFoundException이 발생하고, 찾으면 해당 클래스 파일을 읽어서 Class 객체로 변환
    • 파일 형태로 저장되어 있는 클래스를 읽어서 Class 클래스에 정의된 형식으로 변환하는 것
    • 클래스 파일을 읽어서 사용하기 편한 형태로 저장해 놓은 것이 클래스 객체

Class 객체를 얻는 방법

  • 클래스 정보가 필요할 때, 가장 먼저 Class 객체에 대한 참조를 얻어와야 함
  • forName()은 특정 클래스 파일(예를 들어 데이터베이스 드라이버를 메모리에 올릴 때)에 주로 사용
  • Class 객체를 이용하면 클래스에 정의된 이름이나 개수 등 클래스에 대한 모든 정보를 얻을 수 있기 때문에 Class 객체를 통해 객체를 생성하고 메서드를 호출하는 등 동적인 코드 작성이 가능

1.2 String 클래스

  • 문자열을 저장하고 다루는데 필요한 메서드를 제공

변경 불가능한(immutable) 클래스

  • 문자열을 저장하기 위해 문자형 배열 참조변수(char[]) value를 인스턴스 변수로 정의해놓음
    • 인스턴스 생성 시 생성자의 매개변수로 입력받는 문자열은 이 value에 문자형 배열로 저장됨
  • 한번 생성된 String 인스턴스가 갖고 있는 문자열은 읽어 올 수만 있고, 변경은 불가능
    • '+'를 사용해서 문자열을 결합하는 것은 매 연산 시 마다 새로운 문자열을 가지는 String 인스턴스가 생성되어 메모리 공간을 차지하게 되므로 결합횟수를 줄이는 것이 좋음
  • 문자열간의 결합이나 추출 등 문자열을 다루는 작업이 많이 필요한 경우에는 String 클래스 대신 StringBuffer 클래스를 사용하는 것이 좋음
    • StringBuffer 인스턴스에 저장된 문자열은 변경이 가능하므로 하나의 StringBuffer 인스턴스만으로도 문자열 다루는 것이 가능

문자열의 비교

  • 문자열을 만드는 두 가지 방법: 문자열 리터럴을 지정하는 방법, String 클래스의 생성자를 사용해서 만드는 방법

    String str1 = "abc";    // 문자열 리터럴 "abc"의 주소가 str1에 저장됨
    String str2 = "abc";    // 문자열 리터럴 "abc"의 주소가 str2에 저장됨
    String str3 = new String("abc");    // 새로운 String 인스턴스 생성
    String str4 = new String("abc");    // 새로운 String 인스턴스 생성
  • String 클래스의 생성자를 이용한 경우에는 new 연산자에 의해서 메모리 할당이 이루어지기 때문에 항상 새로운 String 인스턴스가 생성됨

  • 문자열 리터럴은 이미 존재하는 것을 재사용하는 것

    • 문자열 리터럴은 클래스가 메모리에 로드될 때 자동적으로 미리 생성됨
  • equals()를 사용했을 때눈 문자열의 내용을 비교하기 때문에 두 경우 모두 true를 결과로 얻지만, String 인스턴스의 주소를 '=='로 비교했을 때는 리터럴을 사용할 경우 true, String 인스턴스의 경우 false가 나옴

문자열 리터럴

  • 자바 소스파일에 포함된 모든 문자열은 컴파일 시에 클래스 파일에 저장됨
  • 같은 내용의 문자열 리터럴은 한번만 저장됨
    • 이유: 문자열 리터럴도 String 인스턴스이고, 한번 생성하면 내용을 변경할 수 없으므로 하나의 인스턴스를 공유하면 되기 때문
  • 클래스 파일에는 소스파일에 포함된 모든 리터럴의 목록이 있음
    • 해당 클래스 파일이 클래스 로더에 의해 메모리에 올라갈 때, 이 리터럴의 목록에 있는 리터럴들이 JVM 내에 있는 상수 저장소(constant pool)에 저장됨

빈 문자열(empty string)

  • 변수를 선언할 때, 각 타입의 기본값으로 초기화하지만 String은 참조형 타입의 기본값인 null 보다는 빈 문자열로 초기화하는 것이 일반적

문자 인코딩 변환

  • getBytes(String charsetName)를 사용하면, 문자열의 문자 인코딩을 다른 인코딩으로 변경할 수 있음

    byre[] utf8_str = "가".getBytes("UTF-8");    // 문자열을 UTF-8로 변환
    String str = new String(utf8{_str, "UTF-8";)    // byte 배열을 문자열로 변환
  • 서로 다른 문자 인코딩을 사용하는 컴퓨터 간에 데이터를 주고받을 때는 적절한 문자 인코딩이 필요

기본형 값을 String으로, String 값을 기본형 값으로 변환

  • 기본형을 문자열로 변환하는 방법은 숫자에 빈문자열("")을 더해주면 됨. valueOf()을 사용하는 방법도 있음
  • String을 기본형으로 변환하는 방법은 valueOf()를 쓰거나 parseInt()을 사용하면 됨

1.3 StringBuffer 클래스와 StringBuilder 클래스

  • StringBuffer 클래스는 인스턴스를 생성할 때 지정된 문자열을 변경할 수 있음
    • 내부적으로 문자열 편집을 위한 버퍼를 가지고 있고, StringBuffer 인스턴스를 생성할 때 크기를 지정할 수 있음
    • StringBuffer 인스턴스가 생성될 때, char형 배열이 생성되며 이 때 생성된 char형 배열을 인스턴스 변수 value가 참조하게 됨

StringBuffer의 생성자

  • StringBuffer 클래스의 인스턴스를 생성할 때, 적절한 길이의 char형 배열이 생성되고, 이 배열은 문자열을 저장하고 편집하기 위한 공간으로 사용됨
  • 생성자 StringBuffer(int length)를 사용해서 StringBuffer 인스턴스에 저장될 문자열의 길이를 고려해 여유있는 크기로 지정하는 것이 좋음
    • 크기를 지정해주지 안흥면 16개의 문자를 저장할 수 있는 크기의 버퍼를 생성
  • StringBuffer 인스턴스로 문자열을 다룰 때, 버퍼의 크기가 작업하려는 문자열의 길이보다 작은 경우 내부적으로 버퍼의 크기를 증가시키는 작업이 수행됨
    • 배열의 길이는 변경될 수 없으므로 새로운 길이의 배열을 생성한 후에 이전 배열의 값을 복사해야 함

StringBuffer의 변경

  • append(): 반환타입이 StringBuffer로 자신의 주소를 반환

StringBuffer의 비교

  • StringBuffer 클래스는 equals 메서드를 오버라이딩하지 않아서 StringBuffer 클래스의 equals 메서드를 사용해도 '=='로 비교한 것과 같은 결과를 얻음
  • toString()은 오버라이딩되어 있어 StringBuffer 인스턴스에 toString()을 호출하면, 담고있는 문자열을 String으로 반환
  • StringBuffer 인스턴스에 담긴 문자열 비교를 위해선 StringBuffer 인스턴스에 toString()을 호출해서 String 인스턴스를 얻은 다음, 여기에 equals 메서드를 사용해서 비교해야 함

StringBuilder란?

  • StringBuffer는 멀티쓰레드에 안전(thread safe)하도록 동기화되어 있음
    • 동기화는 StringBuffer의 성능을 떨어트림
    • 멀티쓰레드로 작성된 프로그램이 아닌 경우, StringBuffer의 동기화는 불필요하게 성능만 떨어트림
  • StringBuffer에서 쓰레드의 동기화만 뺀 StringBuilder가 새로 추가됨
    • StringBuilder은 StringBuffer와 완전치 똑같은 기능으로 작성되어 있어, 소스코드에서 StringBuffer대신 StringBuilder를 사용하도록 바꾸기만 하면 됨

1.4 Math 클래스

  • Math 클래스는 기본적인 수학계산에 유용한 메서드로 구성되어 있음
  • Math 클래스의 경우 생성자는 접근 제어자가 private이기 때문에 다른 클래스에서 Math 인스턴스를 생성할 수 없도록 되어 있음
    • 이유: 클래스 내에 인스턴스변수가 하나도 없어서 인스턴스를 생성할 필요가 없기 때문
  • Math 클래스의 메서드는 모두 static

올림, 버림, 반올림

  • round(): 소수 첫째자리에서 반올림해서 정수값(long)을 결과로 돌려줌
  • rint(): 소수 첫째자리에서 반올림해서 double형으로 결과를 돌려줌
    • 두 정수의 정가운데 있는 값은 가장 가까운 짝수 정수를 반환
  • floor(): 버림 연산을 수행

예외를 발생시키는 메서드

  • JDK 1.8부터 메서드 이름에 'Exact'가 포함된 메서드들이 추가됨. 정수형간의 연산에서 발생할 수 있는 오버플로우를 감지하기 위한 것
  • 연산자는 단지 결과를 반환할 뿐, 오버플로우의 발생여부에 대해 알려주지 않음. Exact가 붙은 메서드들은 오버플로우가 발생하면 예외를 발생시킴

1.5 래퍼(wrapper) 클래스

사진 출처: https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=heartflow89&logNo=220975218499

  • 기본형 변수를 객체로 다뤄하는 경우 사용하는 것이 래퍼(wrapper) 클래스
  • 8개의 기본형을 대표하는 8개의 래퍼 클래스가 있고, 이 클래스들을 이용해 기본형 값을 객체로 다룰 수 있음
  • 래퍼 클래스 생성자는 매개변수로 문자열이나 각 자료형의 값들을 인자로 받음
    • 생성자의 매개변수로 문자열을 제공할 때, 각 자료형에 알맞는 문자열을 사용해야 함
  • 래퍼 클래스들은 객체생성 시에 생성자의 인자로 주어진 각 자료형에 알맞은 값을 내부적으로 저장하고 있고, 이와 관련된 여러 메서드가 정의되어 있음
  • 래퍼 클래스는 equals()가 오버라이딩되어 있어 주소값이 아닌 객체가 가지고 있는 값을 비교
  • 래퍼 클래스는 toString()도 오버라이딩되어 있어 객체가 가지고 있는 값을 문자열로 변환하여 반환

Number 클래스

  • 추상 클래스로 내부적으로 숫자를 멤버변수로 갖는 래퍼 클래스들의 조상
    • BigInteger: long으로도 다룰 수 없는 큰 범위의 정수를 처리하기 위한 것
    • BigDecimal: double로도 다룰 수 없는 큰 범위의 부동소수점수를 처리하기 위한 것

문자열을 숫자로 변환하기

  • 타입.parse타입(String s) 형식과 타입.valueOf() 메서드를 사용
    • parse는 반환값이 기본형이고 valueOf()는 반환값이 래퍼 클래스 타입
  • 오토박싱(autoboxing) 기능 때문에 반환값이 기본형일 때와 래퍼 클래스일 때의 차이가 없어짐

오토박싱 & 언박싱 (autoboxing & unboxing)

  • JDK 1.5 이전에는 기본형과 참조형 간의 연산이 불가능했기 때문에, 래퍼 클래스로 기본형을 객체로 만들어서 연산했어야 함
  • 하지만 이제는 기본형과 참조형 간의 덧셈이 가능
    • 이유: 컴파일러가 자동으로 변환하는 코드를 넣어주기 때문
  • 내부적으로 객체 배열을 가지고 있는 Vector 클래스나 ArrayList 클래스에 기본형 값을 저장해야할 때나 형변환이 필요할 때도 컴파일러가 자동적으로 코드를 추가해 줌

기본형 값을 래퍼 클래스의 객체로 자동변환해주는 것을 '오토박싱(autoboxing)', 반대로 변환하는 것을 '언박싱(unboxing)'이라 함

예시

  • 컴파일 전의 코드

    Integer intg = (Integer) i;
    Object obj = (Object) i;
    Long lng = 100L;
  • 컴파일 후의 코드

    Integer intg = Integer.valueOf(i);
    Object obj = (Object) Integer.valueOf(i);
    Long lng = new Long(100L);

2. 유용한 클래스


2.1 java.util.Objects 클래스

  • Object 클래스의 보조 클래스로 모든 메서드가 'static'
  • 객체의 비교나 null check에 유용
종류 설명
isNull() 해당 객체가 널인지 확인해서 널이면 true를 반환하고 아니면 false를 반환
nonNull() isNull()과 반대
requireNonNull() 해당 객체가 널이 아니어야 하는 경우에 사용. 객체가 널이면 예외를 발생시킴
compare() 두 비교대상이 같으면 0, 크면 양수, 작으면 음수를 반환
equals() Object 클래스의 equals()와 같은 역할을 하지만 내부적으로 널 검사를 진행하기 때문에 따로 널 검사를 할 필요가 없음 (두 비교대상이 모두 널인 경우 참을 반환)
deepEquals() 객체를 재귀적으로 비교하기 때문에 다차원 배열의 비교가 가능
toString() Object 클래스의 toString()과 같은 역할을 하지만 내부적으로 널 검사를 진행하기 때문에 따로 널 검사를 할 필요가 없음
hashCode() 내부적으로 널 검사를 한 후 Object 클래스의 hashCode()를 호출 (널일 경우 0을 반환)

2.2 java.util.Random 클래스

// 1. Math.random() 사용
double randNum = Math.random();
// 2. Random 클래스 사용
double randNum = new Random().nextDouble();
  • Math.random()은 내부적으로 Random 클래스의 인스턴스를 생성해서 사용하는 것이므로 난수를 얻고 싶은 경우 두 방법 중 하나를 선택해서 사용하면 됨
  • Math.random()과 Random의 차이점은 종자값(seed)를 설정할 수 있다는 것
    • seed를 설정하면 seed가 갖은 Random 인스터스들은 항상 같은 난수를 같은 순서대로 반환
    • seed는 난수를 만드는 공식에 사용되는 값으로 같은 종자값을 넣으면 같은 난수를 얻게 됨

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

  • 정규식: 텍스트 데이터 중에서 원하는 조건(패턴, pattern)과 일치하는 문자열을 찾아내기 위해 사용하는 것으로 미리 정의된 기호와 문자를 이용해 작성한 문자열을 의미
  • 정규식에 사용되는 기호와 작성방법 참고
  • Pattern은 정규식을 정의하는데 사용되고 Matcher는 정규식(패턴)을 데이터와 비교하는 역할을 함

정규식을 정의하고 데이터를 비교하는 과정

  1. 정규식을 매개변수로 Pattern 클래스의 static 메서드인 Pattern compile(String regex)을 호출하여 Pattern 인스턴스를 얻음
    Pattern p = Pattern.compile("c[a-z]*");
  2. 정규식으로 비교할 대상을 매개변수로 Pattern 클래스의 Matcher matcher(CharSequence input)를 호출해서 Matcher 인스턴스를 얻음
    Matcher m = p.matcher(data[i]);
  3. Matcher 인스턴스에 boolean matches()를 호출해서 정규식에 부합하는지 확인
    if(m.matches())
  • group(int i): 정규식의 일부를 괄호로 묶어서 그룹화할 수 있는데 그룹화된 부분은 하나의 단위로 묶이게 되어 한 번 또는 그 이사의 반복을 의미하는 '+'나 '*'가 뒤에 오면 그룹화된 부분이 적용 대상이 됨. 그룹화된 부분은 group(int i)를 이용해서 나눌 수 있음
  • find(): 주어진 소스 내에서 패턴과 일치하는 부분을 찾아내면 true를 반환하고 찾지 못하면 false를 반환
    • find()를 호출해서 패턴과 일치하는 부분을 찾아낸 다음, 다시 find()를 호출하면 이전에 발견한 패턴과 일치하는 부분의 다음부터 다시 패턴매칭을 시작
    • find()로 정규식과 일치하는 부분을 찾으면, 그 위치를 start()와 end()로 알아낼 수 있고 appendReplacement(StringBuffer sb, String replacement)를 이용해서 원하는 문자열로 치환할 수 있음

2.4 java.util.Scanner 클래스

  • Scanner는 화면, 파일, 문자열과 같은 입력소스로부터 문자데이터를 읽어오는데 도움을 줌
  • Scanner는 정규식 표현을 이용한 라인단위의 검색을 지원하며 구분자(delimiter)에도 정규시 표현을 사용할 수 있어 복잡한 형태의 구분자도 처리가 가능
  • 입력받을 값이 숫자라면 nextLine() 대신 nextInt() 또는 nextLong()과 같은 메서드 사용 가능

2.5 java.uril.StringTokenizer 클래스

  • StringTokenizer는 긴 문자열을 지정된 구분자(delimiter)를 기준으로 토큰(token)이라는 여러 개의 문자열로 잘라내는데 사용됨
  • String의 split(String regex) 혹은 Scanner의 useDelimiter(String pattern)를 사용할 수도 있지만, 이 두가지 방법은 정규식을 사용해야하므로 정규식에 익숙하지 않은 경우 StringTokenizer를 사용하는 것이 간단
  • 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() 다음 토큰을 반환

2.6 java.math.BigInteger 클래스

final int signum;    // 부호. 1(양수), 0, -1(음수) 셋 중 하나
final int[] mag;    // 값
  • BigInteger는 내부적으로 int 배열을 사용해서 값을 다룸
  • BigInteger는 String처럼 불변이고 2의 보수 형태로 표현
    • 부호를 따로 저장하고 배열에는 값 자체만 저장
    • 부호만 다른 두 값의 mag는 같고 signum은 다름

2.7 java.math.BigDecimal 클래스

private final BigInteger intVal;    // 정수(unscaled value)
private final int scale;        //  지수(scale)
private transient int precision;    // 정밀도(precision) - 정수의 자릿수
  • BigDecimal은 실수형과 달리 정수를 이용해서 실수를 표현
  • 실수를 정수와 10의 제곱의 곱으로 표현 (정수 X 10^(-scale))
    • scale: 0부터 Integer.MAX_VALUE 사이의 범위에 있는 값
  • BigDecimal은 정수를 저장하는데 BigInteger를 사용
  • scale은 소수점 이하의 자리수를 의미하고, precision은 정수의 전체 자리수를 의미
728x90
LIST

댓글