[자바의 정석] 9장 java.lang패키지와 유용한 클래스


1. java.lang 패키지

java.lang패키지는 자바 프로그래밍에 가장 기본이 되는 클래스들을 포함하고 있다. 그래서 java.lang패키지의 클래스들은 import문 없이 사용 가능하다.

1.1 Object클래스

Object클래스는 모든 클래스의 최고 조상이기 때문에 Object클래스의 멤버들은 모든 클래스에서 바로 사용 가능하다.

Object클래스는 멤버변수는 없고 오직 11개의 메서드만 가지고 있다. 이 메서드들은 모든 인스턴스가 가져야 할 기본적인 것들이며, 이 중 6개는 쓰레드와 관련된 것들이다.

equals(Object obj)

매개변수로 객체의 참조변수를 받아서 비교하여 그 결과를 boolean값으로 알려주는 역할

public boolean equals(Object obj) {
	return (this == obj);
}
  • 두 객체의 같고 다름을 참조변수의 값으로 판단한다. 그렇기 때문에 서로 다른 두 객체를 equals메서드로 비교하면 항상 false를 결과로 얻는다.
  • Object클래스로부터 상속받은 equals메서드는 두 참조변수에 저장된 값(주소값)이 같은지를 판단하는 기능밖에 할 수 없다.
  • equals메서드로 클래스의 인스턴스가 가지고 있는 값을 비교하려면 해당 클래스에서 equals메서드를 오버라이딩 하여 주소가 아닌 객체에 저장된 내용을 비교하도록 변경해야 한다.
class Person {
	long id;

	public boolean equals(Object obj) {
		if (obj instanceof Person) {
			return id == ((Person)obj).id;  //obj가 Object타입이므로 id를 참조하기 위해서 형변환이 필요
		else
			return  false;		  //타입이 Person이 아니면 비교할 필요도 없다.
	}
}
  • String클래스도 Object클래스의 equals메서드를 그대로 사용하는 것이 아니라 이처럼 오버라이딩을 통해서 String인스턴스가 갖는 문자열 값을 비교하도록 되어있다.
    -> 그래서 같은 내용의 문자열을 같는 두 String인스턴스에 equals메서드를 사용하면 항상 true값을 얻는 것

hashCode()

해싱 기법에 사용되는 ‘해시함수’를 구현한 것

  • Object클래스에 정의된 hashCode메서드는 객체의 주소값을 해시코드를 만들어 반환한다.
  • 클래스의 인스턴스변수 값으로 객체의 같고 다름을 판단해야하는 경우라면 equals메서드 뿐 아니라 hashCode메서드도 오버라이딩해야 한다.
    -> 같은 객체라면 hashCode메서드를 호출했을 때의 결과값인 해시코드도 같아야 하기 때문
  • String클래스는 문자열의 내용이 같으면, 동일한 해시코드를 반환하도록 hashCode메서드가 오버라이딩되어 있기 때문에, 문자열의 내용이 같은 str1과 str2에 대해 hashCode()를 호출하면 항상 동일한 해시코드값을 얻는다.
  • 반면에 System.identityHashCode(Object x)는 Object클래스의 hashCode메서드처럼 객체의 주소값으로 해시코드를 생성하기 때문에 모든 객체에 대해 항상 다른 해시코드값을 반환할 것을 보장한다.

toString()

인스턴스에 대한 정보를 문자열로 제공할 목적으로 정의한 것. 대부분의 경우 인스턴스 변수에 저장된 값들을 문자열로 표현한다는 뜻

// Object클래스에 정의된 toString()
public String toString() {
	return getclass().getName() + "@" + Integer.tohexString(hashCode());
  • toString()을 오버라이딩하지 않는다면, 클래스이름에 16진수의 해시코드를 얻게 될 것

  • String클래스의 toString()은 String인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩되어 있다.
  • Date클래스의 toString()은 Date인스턴스가 갖고 있는 날짜와 시간을 문자열로 변환하도록 오버라이딩되어 있다.
    -> toString()은 일반적으로 인스턴스나 클래스에 대한 정보 또는 인스턴스 변수의 값을 문자열로 변환하여 반환하도록 오버라이딩 하는 것이 보통이다.
  • Object클래스에 정의된 toString()의 접근 제어자가 public이므로 오버라이딩 한 toString()의 접근제어자도 public으로 해야 한다.

clone()

자신을 복제하여 새로운 인스턴스를 생성하는 일을 한다.

  • Object클래스에 정의된 clone()은 단순히 인스턴스변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다.
    • 배열의 경우, 복제된 인스턴스도 같은 배열의 주소를 갖기 때문에 복제된 인스턴스의 작업이 원래의 인스턴스에 영향을 미치게 된다.
    • 이런 경우 clone메서드를 오버라이딩해서 새로운 배열을 생성하고 배열의 내용을 복사하도록 해야 한다.
  • clone()을 사용하려면, 먼저 복제할 클래스가 Cloneable인터페이스를 구현해야하고, clone()을 오버라이딩하면서 접근 제어자를 protected에서 public으로 변경한다. 그래야 상속관계가 없는 다른 클래스에서 clone()을 호출 할 수 있다.
  • Cloneable인터페이스가 구현되어 있다는 것은 클래스 작성자가 복제를 허용한다는 의미
    -> 데이터를 보호하기 위함
class Point implements Cloneable {	// 1. Cloneable인터페이스를 구현한다.
	public Object clone() {		// 2. 접근 제어자를 Protected에서 public으로 변경
		Object obj = null;
		try {
			obj = super.clone();		// 3. try-catch내에서 조상클래스의 clone()을 호출
		} catch (CloneNotSupportedException e) {}
		return obj;
	}
}

공변 반환타입

JDK1.5부터 추가되었는데, 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것

class Point implements Cloneable {
	public Point clone() {		// 반환 타입을 자손 클래스 타입으로 변경
		Object obj = null;
		try {
			obj = super.clone();
		} catch (CloneNotSupportedException e) {}
		return (Point)obj;	// return 문에서도 Point타입으로 형변환한다.
	}
}

‘공변 반환타입’을 사용하면, 조상의 타입이 아닌, 실제로 반환되는 자손 객체의 타입으로 반환할 수 있어서 번거로운 형변환이 줄어든다.
Point copy = (Point)original.clone(); -> Point copy = original.clone();

int[] arr = {1, 2, 3, 4, 5};
int[] arrClone = arr.clone();
  • 배열도 객체이기 때문에 Object클래스를 상속받으며, 동시에 Cloneable 인터페이스와 Serialiazable인터페이스가 구현되어 있다.
  • public으로 오버라이딩하였기 때문에 호출 가능하고, 원본과 같은 타입을 반환하므로 형변환이 필요 없다.
  • 배열을 복사할 때, 같은 길이의 새로운 배열을 생성한 다음에 System.arraycopy()를 이용해서 내용을 복사하지만, 이처럼 clone()을 이용해서 간단하게 복사할 수 도 있다.

얕은 복사와 깊은 복사

clone()은 단순히 객체에 저장된 값을 그대로 복제할 뿐, 객체가 참조하고 있는 객체까지 복제하지는 않는다.

얕은 복사(shallow copy)

  • 원본을 변경하면 복사본도 영향을 받는다.
  • 객체배열을 clone()으로 복제하는 경우에는 원본과 복제본이 같은 객체를 공유하므로 완전한 복제라고 보기 어렵다.

깊은 복사(deep copy)

  • 원본이 참조하고 있는 객체까지 복제하는 것
  • 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않는다.
class Point {
	int x;
	int y;

	Point (int x, int y) {
		this.x = x;
		this.y = y;
}

class Circle implements Cloneable {
	Point p;
	double r;

	public Circle clone() {
		Object obj = null;
		try {
			obj = super.clone();
		} catch (CloneNotSupportedException e) {}

		Circle c = (Circle)obj;			//깊은 복사를 하기 위한 코드
		c.p = new Point(this.p.x, this.p.y);
		return c;
}
  • Object클래스의 clone()은 원본 객체가 가지고 있는 값만 그대로 복사한다. 즉 얕은 복사를 한다.
  • 깊은 복사는 아래 3줄을 추가하여, 복제된 객체가 새로운 Point인스턴스를 참조하도록 했다.
    -> 원본이 참조하고 있는 객체까지 복사

getClass()

자신이 속한 클래스의 Class객체를 반환하는 메서드

  • Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재한다.
  • 클래스 파일이 클래스 로더에 의해서 메모리에 올라갈 때 자동으로 생성된다.
  • 파일 형태로 저장되어 있는 클래스를 읽어서 사용하기 편한 형태로 저장해 놓은 것이 클래스 객체이다.

클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.

Class객체를 얻는 방법

클래스의 정보가 필요할 때, 먼저 Class객체에 대한 참조를 얻어 와야 하는데, 방법이 여러 가지가 있다.

  1. Class cObj = new Card().getClass(); // 생성된 객체로 부터 얻는 방법
  2. Class cObj = Card.class; // 클래스 리터럴(*.class)로 부터 얻는 방법
  3. Class cObj = Class.forName("Card"); // 클래스 이름으로 부터 얻는 방법
    -> Class 객체를 이용하면 클래스에 대한 모든 정보를 얻을 수 있기 때문에 Class 객체를 통해 객체를 생성하고 메서드를 호출하는 등 보다 동적인 코드를 작성할 수 있다.

1.2 String 클래스

String 클래스는 문자열을 저장하고 이를 다루는데 필요한 메서드를 함께 제공한다.

변경 불가능한 클래스

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

문자열의 비교

  1. 문자열 리터럴을 지정하는 방법
    String str1 = "abc";
  2. String클래스의 생성자를 사용해서 만드는 방법
    String str2 = new String("abc");
  • String클래스의 생성자를 이용한 경우에는 new연산자에 의해서 메모리할당이 이루어지기 때문에 항상 새로운 String 인스턴스가 생성된다.
  • 그러나 문자열 리터럴은 이미 존재하는 것을 재사용하는 것이다.
  • equals()를 사용했을 때는 문자열의 내용을 비교하기 때문에 두 경우 모두 true를 결과로 얻지만, ‘==’연산자를 사용했을 때는 인스턴스의 주소를 비교하기 때문에 생성자를 사용해서 만들었을 경우에는 false를 결과로 얻는다.

문자열 리터럴

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

빈 문자열

  • 길이가 0인 배열이 존재할 수 있다. char형 배열도 길이가 0인 배열을 생성할 수 있고, 이 배열을 내부적으로 가지고 있는 문자열이 빈 문자열이다.
  • String은 기본값인 null보다는 빈 문자열로, char형은 기본값인 ‘\u0000’ 대신 공백으로 초기화 하는 것이 보통이다.

String클래스의 생성자와 메서드

  • String(String s), String(char[] values), String(StringBuffer buf)
    -> 주어진 문자열을 갖는 String 인스턴스를 생성한다.
  • char charAt(int index)
    -> 지정된 위치(index)에 있는 문자 1개를 반환한다.(index는 0부터 시작)
  • int compareTo(String str)
    -> 문자열과 사전순서로 비교한다. 같으면 0을, 사전 순으로 이전이면 -1을, 사전 순으로 이후면 1을 반환한다.
  • String concat(String str)
    -> 문자열을 뒤에 덧붙인다.
  • boolean contains(CharSequence s)
    -> 지정된 문자열이 포함되었는지 검사한다.
  • boolean endsWith(String suffix)
    -> 지정된 문자열로 끝내는지 검사한다.
  • boolean equals(Object obj)
    -> obj가 문자열이 아니거나 문자열이 다르면 false를 반환한다.
  • boolean equalsIgnoreCase(String str)
    -> 문자열을 대소문자 구분없이 비교한다.
  • int indexOf(int ch), int indexOf(String str)
    -> 주어진 문자나 문자열이 존재하는지 확인해서 그 위치(index)를 알려준다. 없으면 -1을 반환한다.
  • int indexOf(int ch, int pos)
    -> 주어진 문자가 문자열에 존재하는지 지정된 위치부터 확인하여 위치(index)를 알려준다. 못 찾으면 -1을 반환한다.
  • int lastIndexOf(int ch), int lastIndexOf(String str)
    -> 주어진 문자 또는 문자열을 오른쪽 끝에서부터 찾아서 위치를 알려준다. 못 찾으면 -1을 반환한다.
  • String intern()
    -> 문자열을 constant pool에 등록한다. 이미 같은 내용의 문자열이 있을 경우 그 문자열의 주소값을 반환한다.
  • int length()
    -> 문자열의 길이를 반환한다.
  • String replace(char old, char new), String replace(CharSequence old, CharSequence new)
    -> 문자열 중의 문자(or 문자열)를 새로운 문자(or 문자열)로 모두 바꾼 문자열을 반환한다.
  • String replaceFirst(String regex, String replaceMent)
    -> 문자열 중에서 지정된 문자열과 일치하는 것 중, 첫 번째 것만 새로운 문자열로 변경한다.
  • String[] split(String regex), String[] split(String regex, int limit)
    -> 문자열을 지정된 분리자로 나누어 문자열 배열에 담아 반환한다. limit을 정하면 전체를 지정된 수로 자른다.
  • boolean startsWith(String prefix
    -> 주어진 문자열로 시작하는지 검사한다.
  • String substring(int begin), String substring(int begin, int end)
    -> 주어진 시작위치부터 끝 위치 범위에 포함된 문자열을 얻는다. 시작 위치의 문자는 범위에 포함되지만, 끝 위치 문자는 포함되지 않는다.
  • String toLowerCase(), String toUpperCase()
    -> 주어진 문자열을 모두 소문자 또는 대문자로 변환하여 반환한다.
  • String toString()
    -> String인스턴스에 저장되어 있는 문자열을 반환한다.
  • String trim()
    -> 문자열의 왼쪽 끝과 오른쪽 끝에 있는 공백을 없앤 결과를 반환한다. 문자열 중간에 있는 공백은 제거되지 않는다.
  • static String valueOf(기본형 변수타입), static String valueOf(Object obj)
    -> 지정된 값을 문자열로 변환하여 반환한다. 참조변수의 경우, toString()을 호출한 결과를 반환한다. (static method라서 클래스 이름으로 호출해야 한다)

join과 StringJoiner

join()은 여러 문자열 사이에 구분자를 넣어서 결합한다. split()과 반대의 작업을 한다.

String animals = "dog, cat, bear";
String arr[] = animals.split(",");  // 문자열을 ','를 구분자로 나눠서 배열에 저장
String str = String.join("-", arr); // 배열의 문자열을 '-'로 구분해서 결합
System.out.println(str);	    // dog-cat-bear

java.util.StringJoiner클래스를 사용해서 문자열을 결합할 수도 있는데, 사용법은 다음과 같다.

StringJoiner sj = new StringJoiner(",", "[", "]");
String[] strArr = { "aaa", "bbb", "ccc" };

for(String s : strArr) {
	sj.add(s.toUpperCase());
}
System.out.println(sj.toString()); 	// [AAA,BBB,CCC]

유니코드의 보충문자

  • 유니코드는 원래 16비트 문자체계인데, 이걸로도 모자라서 20비트로 확장하게 되었다.
  • 그래서 하나의 문자를 char타입으로 다루지 못하고, int타입으로 다룰 수밖에 없다.
  • 확장에 의해 추가된 문자들을 보충 문자라고 하는데, String클래스 메서드 중에서는 보충 문자를 지원하는 것이 있고 지원하지 않는 것도 있다.
  • 매개변수가 ‘int ch’인 것들은 보충문자를 지원하는 것이고, ‘char ch’인 것들은 지원하지 않는 것들이다.

문자 인코딩 변환

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

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

String.format()

format()은 형식화된 문자열을 만들어내는 간단한 방법이다. printf()하고 사용법이 완전히 같다.

기본형 값을 String으로 변환

  • 숫자에 빈 문자열 ““을 더해주거나 valueOf()를 사용한다.
int i = 100;
String str1 = i + "";				// 100을 "100"으로 변환하는 방법1
String str2 = String.valueOf(i);		// 100을 "100"으로 변환하는 방법2

-> 성능은 valueOf()가 더 좋지만, 빈 문자열을 더하는 방법이 간단하고 편하기 때문에 성능향상이 필요한 경우에만 valueOf()를 쓰자.

String을 기본형 값으로 변환

  • parseInt()를 사용하거나 valueOf()를 사용한다.
  • parseInt()나 parseFloat()같은 메서드는 문자열에 공백 또는 문자가 포함되어 있는 경우 변환 시 예외가 발생할 수 있으므로 주의해야 한다. -> 문자열 양끝의 공백을 제거해주는 trim()을 습관적으로 같이 사용하기도 한다.
  • 부호를 의미하는 ‘+’나 소수점을 의미하는 ‘.’와 float형 값을 뜻하는 ‘f’와 같은 자료형 접미사는 허용된다. 단, 자료형에 알맞은 변환을 해야한다.
int i = Integer.parseInt("100");		// "100"을 100으로 변환하는 방법1
int i2 = Integer.valueOf("100");		// "100"을 100으로 변환하는 방법2

1.3 StringBuffer클래스와 StringBuilder클래스

String클래스는 인스턴스를 생성할 때 지정된 문자열을 변경할 수 없지만 StringBuffer클래스는 변경이 가능

StringBuffer의 생성자

  • StringBuffer클래스는 인스턴스를 생성할 때, 적절한 길이의 char형 배열이 생성되고, 이 배열은 문자열을 저장하고 편집하기 위한 공간(buffer)으로 사용된다.
  • StringBuffer을 생성할 때는 문자열의 길이를 고려하여 충분히 여유있는 크기로 지정하는 것이 좋다.
    -> 편집 중인 문자열이 버퍼의 길이를 넘어서게 되면 버퍼의 길이를 늘려주는 작업이 추가로 수행되어야하기 때문에 작업효율이 떨어짐
  • StringBuffer인스턴스를 생성할 때, 버퍼의 크기를 지정해주지 않으면 16개의 문자를 지정할 수 있는 크기의 버퍼를 생성
  • StringBuffer인스턴스를 생성할 때, 문자열을 지정해주면 지정한 문자열의 길이보다 16이 더 크게 버퍼를 생성

StringBuffer의 변경

StringBuffer sb = new StringBuffer("abc");
StringBuffer sb2 = sb.append("123");
System.out.println(sb);		//abc123
System.out.println(sb2);	//abc123
  • append()는 반환타입이 StringBuffer인데 자신의 주소를 반환한다. 그래서 sb에 새로운 문자열이 추가되고 sb자신의 주소를 반환하여 sb2에는 sb의 주소인 0x100이 저장된다.
  • sb와 sb2가 모두 같은 인스턴스를 가리키고 있으므로 같은 내용이 출력된다. 그래서 연속적으로 append()를 호출하는 것이 가능하다.
    -> sb.append("123").append("zz");

StringBuffer의 비교

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

StringBuffer클래스의 생성자와 메서드

  • StringBuffer(), StringBuffer(int length), StringBuffer(String str)
    -> 문자열을 매개변수로 지정하면 해당 문자열 값을 가진 StringBuffer인스턴스를 생성하고, 크기를 지정하거나 매개변수가 없으면 지정된 개수의 문자를 담을 수 있는 버퍼를 가진 StringBuffer인스턴스를 생성한다.
  • StringBuffer append(Anytype a)
    -> 매개변수로 입력된 값을 문자열로 변환하여 StringBuffer인스턴스가 저장하고 있는 문자열의 뒤에 덧붙인다.
  • int capacity()
    -> StringBuffer인스턴스의 버퍼크기를 알려준다. length()는 버퍼에 담긴 문자열의 크기를 알려준다.
  • char charAt(int index)
    -> 지정된 위치에 있는 문자를 반환한다.
  • StringBuffer delete(int start, int end)
    -> 시작위치부터 끝 위치 사이에 있는 문자를 제거한다. 단, 끝 위치 문자는 제외.
  • StringBuffer deleteCharAt(int index)
    -> 지정된 위치의 문자를 제거한다.
  • StringBuffer insert(int pos, Anytype a)
    -> 두 번째 매개변수로 받은 값을 문자열로 변환하여 지정된 위치에 추가한다.
  • StringBuffer replace(int start, int end, String str)
    -> 지정된 범위에 있는 문자들을 주어진 문자열로 바꾼다.
  • StringBuffer reverse()
    -> 저장되어 있는 문자열의 순서를 거꾸로 나열한다.
  • void setCharAt(int index, char ch)
    -> 지정된 위치의 문자를 주어진 문자로 바꾼다.
  • void setLength(int newLength)
    -> 지정된 길이로 문자열의 길이를 변경한다.
  • String toString()
    -> StringBuffer인스턴스의 문자열을 String으로 반환
  • String subString(int start), String subString(int start, int end)
    -> 지정된 범위 내의 문자열을 String으로 뽑아서 반환한다.

StringBuilder란?

  • StringBuffer는 멀티쓰레드에 안전하도록 동기화되어 있다.
  • 멀티쓰레드로 작성된 프로그램이 아닌 경우, StringBuffer의 동기화는 불필요하게 성능만 떨어트리게 된다.
  • 그래서 StringBuffer에서 쓰레드의 동기화만 뺀 StringBuilder가 새로 추가되었다.
  • 동기화를 제외하고는 StringBuilder와 StringBuffer는 완전히 똑같은 기능으로 작성되어 있다.

1.4 Math클래스

  • Math클래스의 생성자는 접근 제어자가 private이기 때문에 다른 클래스에서 Math인스턴스를 생성할 수 없도록 되어있다.
    -> Math클래스의 메서드는 모두 static이며, PI와 E 2개의 상수만 정의해 놓았기 때문에 인스턴스를 생성할 필요가 없기 때문이다.

올림, 버림, 반올림

  • 소수점 n번째 자리에서 반올림한 값을 얻기 위해서는 round()를 사용해야 하는데, 이 메서드는 항상 소수점 첫째자리에서 반올림을 해서 정수값을 결과로 돌려준다.
  • 원하는 자리 수에서 반올림된 값을 얻기 위해서는 아래의 방법대로 하면 된다.
    1. 원래 값에 100을 곱한다.
    2. 위의 결과에 Math.round()를 사용한다.
    3. 위의 결과를 다시 100.0으로 나눈다.
  • rint()도 round()처럼 소수점 첫 째자리에서 반올림하지만, 반환값이 double이다.
  • 그리고 rint()는 두 정수의 한가운데 있는 값은 가장 가까운 짝수 정수를 반환한다.
    -> round()는 rint()에 비해 정밀도가 조금 떨어진다.
  • 음수에서는 양수와 달리 -1.5를 버림하면 -2.0이 된다.

예외를 발생시키는 메서드

  • 메서드 이름에 ‘Exact’가 포함된 메서드들이 JDK1.8부터 새로 추가되었다.
  • 이들은 정수형간의 연산에서 발생할 수 있는 오버플로우를 감지하여, 오버플로우 발생 시 예외를 발생시킨다.

StrictMath클래스

  • Math클래스는 최대한의 성능을 얻기 위해 JVM이 설치된 OS의 메서드를 호출해서 사용한다.
  • 부동소수점 계산의 경우, 반올림의 처리방법 설정이 OS마다 다를수 있기 때문에 자바로 작성된 프로그램임에도 불구하고 컴퓨터마다 결과가 다를 수 있다.
    -> 성능을 포기하는 대신, 어떤 OS에서 실행되어도 항상 같은 결과를 얻도록 Math클래스를 새로 작성한 것이 StrictMath클래스이다.

Math클래스의 메서드

  • static double abs(double a), static float abs(float a), static int abs(int a), static long abs(long a)
    -> 주어진 값의 절대값을 반환한다.
  • static double ceil(double a)
    -> 주어진 값을 올림하여 반환한다.
  • static double floor(double a)
    -> 주어진 값을 내림하여 반환한다.
  • static double max(double a, double b) // 매개변수 float, int, long도 가능
    -> 둘 중에 큰 값을 반환한다.
  • static double min(double a, double b) // 매개변수 float, int, long도 가능
    -> 둘 중에 작은 값을 반환한다.
  • static double random()
    -> 0.0~1.0범위의 임의의 double값을 반환한다.
  • static double rint(double a)
    -> 주어진 double값과 가장 가까운 정수값을 double형으로 반환한다. 단 두 정수의 정가운데 있는 값은 짝수로 반환
  • static long round(double a), static long round(float a)
    -> 소수점 첫째자리에서 반올림한 정수값을 반환한다.

1.5 래퍼(wrapper) 클래스

자바에서는 8개의 기본형을 객체로 다루지 않는데 이것이 바로 자바가 완전한 객체지향 언어가 아니라는 얘기를 듣는 이유이다. 그 대신 보다 높은 성능을 얻을 수 있었다.

  • 때로는 기본형 변수도 어쩔 수 없이 객체로 다뤄야 하는 경우가 있다. ex) 매개변수로 객체를 요구할 때, 기본형 값이 아닌 객체로 저장해야할 때, 객체간의 비교가 필요할 때 등등의 경우에는 기본형 값들을 객체로 변환하여 작업해야 한다.
  • 이 때 사용되는 것이 래퍼클래스. 8개의 기본형을 대표하는 기본 클래스가 있는데, 이 클래스들을 이용하면 기본형 값을 객체로 다룰 수 있다.
  • char형과 Int형을 제외한 나머지는 자료형 이름의 첫 글자를 대문자로 한 것이 각 래퍼 클래스의 이름이다.
  • 래퍼 클래스의 생성자는 매개변수로 문자열이나 각 자료형의 값들을 인자로 받는다.
    -> 이 때 주의해야할 것은 생성자의 매개변수로 문자열을 제공할 때, 각 자료형에 알맞은 문자열을 사용해야한다는 것
  • 래퍼 클래스들은 모두 equals()가 오버라이딩되어 있어서 주소값이 아닌 객체가 가지고 있는 값을 비교한다.
  • 오토박싱이 된다고 해도 Integer객체에 비교연산자를 사용할 수 없다. 대신 compareTo()를 제공한다.
  • toString()도 오버라이딩되어 있어서 객체가 가지고 있는 값을 문자열로 변환하여 반환한다.

Number클래스

추상클래스로 내부적으로 숫자를 멤버변수로 갖는 래퍼 클래스들의 조상이다.
-> 객체가 가지고 있는 값을 숫자와 관련된 기본형으로 변환하여 반환하는 메서드들을 정의하고 있다.

문자열을 숫자로 변환하기

// 아래의 방법 중 하나를 선택해서 사용하면 된다.
int i = new Integer("100").intValue();	// floatValue(), longValue(), ...
int i2 = Integer.parseInt("100");	// 주로 이 방법 많이 사용
Integer i3 = Integer.valueOf("100");
  • ‘타입.parse타입(String s)’형식의 메서드는 반환값이 기본형이고, ‘타입.valueOf(String s)’메서드는 반환값이 래퍼 클래스 타입이라는 차이가 있다.
  • 오토박싱 기능 때문에 반환값이 기본형일 때와 래퍼클래스일 때의 차이가 없어졌다. 단, 성능은 valueOf()가 조금 더 느리다.
  • 문자열이 10진수가 아닌 다른 진법의 숫자일 때도 변환이 가능하도록 메서드가 제공된다.
    • static int parseInt(String s, int radix) //문자열 s를 radix진법으로 인식
    • static Integer valueOf(String s, int radix)
    • ex) Integer.parseInt(“100”, 2) -> 4

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

  • JDK1.5이전에는 기본형과 참조형 간의 연산이 불가능했기 때문에, 래퍼 클래스로 기본형을 객체로 만들어서 연산해야 했다.
  • 이제는 컴파일러가 자동으로 변환하는 코드를 넣어주기 때문에 기본형과 참조형 간의 덧셈이 가능하다.
int i = 5;
Integer iObj = new Integer(7);
int sum = i + iObj;	        //컴파일 전의 코드
int sum = i + iObj.intValue();	//컴파일 후의 코드
* 이 외에도 내부적으로 객체 배열을 가지고 있는 Vector클래스나 ArrayList클래스에 기본형 값을 저장해야할 때나 형변환이 필요할 때도 컴파일러가 자동적으로 코드를 추가해준다.
  • 기본형 값을 래퍼클래스의 객체로 자동 변환해주는 것을 ‘오토박싱’이라고 하고, 반대로 변환하는 것을 ‘언박싱’이라고 한다.
  • 기본형과 참조형간의 형변환도 가능할 뿐만 아니라, 참조형 간의 연산도 가능하다.
    -> 이 기능은 컴파일러가 제공하는 편리한 기능일 뿐 자바의 원칙이 바뀐 것은 아니다.

2. 유용한 클래스

2.1 java.util.Objects클래스

Object클래스의 보조 클래스로 Math클래스처럼 모든 메서드가 ‘static’이다. 객체의 비교나 널 체크에 유용하다.

  • isNull()은 객체가 null이면 true를 반환하고, 아니면 false를 반환한다. nonNull()은 정반대의 일을 한다.
  • requireNonNull()은 해당 객체가 널이 아니어야 하는 경우에 사용한다. 만일 객체가 널이면, NullPointerException을 발생시킨다. 두 번째 매개변수로 지정하는 문자열은 예외의 메시지가 된다.
  • compare()는 두 비교대상이 같으면 0, 크면 양수, 작으면 음수를 반환한다. 두 객체를 비교하는데 사용할 비교 기준이 필요하다. 그 역할을 하는 것이 Comparator이다.
  • Objects클래스에서 정의된 equals()는 null검사를 하지 않아도 된다는 장점이 있다.
    -> equals()의 내부에서 a와 b의 널 검사를 하기 때문이다.
  • 두 2차원 문자열 배열을 비교할 때, deepEquals()를 쓰면 간단하게 해결할 수 있다.
  • toString()도 equals()처럼, 내부적으로 널 검사를 한다. 널이라면 두 번째 매개변수를 대신 사용할 값으로 지정할 수 있다.
  • hashCode()역시 내부적으로 널 검사를 한 후에 Object클래스의 hashCode()를 호출할 뿐이다. 단, 널일 때는 0을 반환한다.
  • static import문을 사용해도 Object클래스의 메서드와 이름이 같은 것들은 충돌이 난다. 즉, 컴파일러가 구별을 못한다.
    -> 클래스의 이름을 붙여줄 수밖에 없다.

2.2 java.util.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;	    // 위의 문장과 동일
  • Math.random()과 Random의 가장 큰 차이점은 종자값(seed)을 설정할 수 있다는 것이다.
  • 종자값이 같은 Random인스턴스들은 항상 같은 난수를 같은 순서대로 반환한다.

Random클래스의 생성자와 메서드

  • 생성자 Random()은 종자값을 System.currentTimeMillis()로 하기 때문에 실행할 때마다 얻는 난수가 달라진다.
  • 주로 사용하는 메서드는 double nextDouble (), int nextInt (int n)이 있다.
  • int nextInt (int n)는 0 ~ n의 범위에 있는 Int값을 반환한다. (n은 범위 포함 x)

Math.random()을 이용해서 실제 프로그래밍에 유용할만한 메서드

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

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

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

2.3 정규식 - java.util.regex패키지

정규식이란 텍스트 데이터 중에서 원하는 조건(패턴, pattern)과 일치하는 문자열을 찾아내기 위해 사용하는 것으로 미리 정의된 기호와 문자를 이용해서 작성한 문자열을 말한다.

Pattern - 정규식을 정의하는데 사용
Matcher - 정규식을 데이터와 비교하는 역할

String[] data = {"bat", "baby", "bonus", "cA", "ca", "co", "c.", "c0", "car", "combat", "count", "data", "disc"};
Pattern p = Pattern.compile("c[a-z]*");	// c로 시작하는 소문자영단어

for (int i=0; i<data.length; i++) {
	Matcher m = p.matcher(date[i]);
	if(m.matches())
		System.out.print(data[i] + ", "); //결과 : ca, co, car, combat, count,
  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())x

자주 쓰일 만한 패턴들

  • c[a-z]*
    -> c로 시작하는 영단어
  • c[a-z]
    -> c로 시작하는 두 자리 영단어
  • c[a-zA-Z]
    -> c로 시작하는 두 자리 영단어 (a~z 또는 A~Z, 즉 대소문자 구분안함)
  • c[a-zA-Z-0-9], c\w
    -> c로 시작하고 숫자와 영어로 조합된 두 글자
  • .*
    -> 모든 문자열
  • c.
    -> c로 시작하는 두 자리 문자열
  • c.*
    -> c로 시작하는 모든 문자열
  • c\.
    -> c.와 잋리하는 문자열’.’은 패턴작성에 사용되는 문자이므로 escape문자인 ‘'를 사용해야한다.
  • c\d, c[0-9]
    -> c와 숫자로 구성된 두 자리 문자열
  • c.*t
    -> c로 시작하고 t로 끝나는 모든 문자열
  • [b|c].*, [bc].*, [b-c].*
    -> b나 c로 시작되는 문자열
  • ^[b|c].*, ^[bc].*, ^[b-c].*
    -> b나 c로 시작되지 않는 문자열
  • .*a.*
    -> a를 포함하는 모든 문자열. *는 0 또는 그 이상의 문자
  • .*a.+
    -> a를 포함하는 모든 문자열. +는 1 또는 그 이상의 문자. +는 *과 달리 반드시 하나 이상의 문자가 있어야 하므로 a로 끝나는 단어는 포함되지 않음
  • [b|c].{2}
    -> b나 c로 시작되는 세 자리 문자열

2.4 java.util.Scanner 클래스

Scanner는 화면, 파일, 문자열과 같은 입력소스로부터 문자데이터를 읽어오는데 도움을 줄 목적으로 추가되었다.

  • 정규식 표현을 이용한 라인단위의 검색을 지원하며 구분자에도 정규식 표현을 사용할 수 있어서 복잡한 형태의 구분자도 처리가 가능하다.
    Scanner useDelimeter(Pattern pattern)
Scanner s = new Scanner(System.in);
String input = console.readLine();
  • 입력받을 값이 숫자라면 nextLine()대신 nextInt()또는 nextLong()과 같은 메서드를 사용할 수 있다.
    -> 입력받은 문자를 변환하는 수고를 덜어 준다.

2.5 java.util.StringTokenizer클래스

StringTokenizer는 긴 문자열을 지정된 구분자(delimiter)를 기준으로 토큰이라는 여러 개의 문자열로 잘라내는 데 사용한다.

  • 정규식 표현에 익숙하지 않은 경우 StringTokenizer을 사용하는 것이 간단하면서도 명확한 결과를 얻을 수 있을 것이다.
  • 그러나 구분자로 단 하나의 문자 밖에 사용하지 못하기 때문에 보다 복잡한 형태의 구분자로 문자열을 나누어야 할 때는 어쩔 수 없이 정규식을 사용하는 메서드를 사용해야 할 것이다.

StringTokenizer의 생성자와 메서드

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

split()메서드와의 차이점

  • split()은 빈 문자열도 토큰으로 인식하는 반면 StringTokenizer는 빈 문자열을 토큰으로 인식하지 않는다.
  • split()은 데이터를 토큰으로 잘라낸 결과를 배열에 담아서 반환하기 때문에 데이터를 토큰으로 바로바로 잘라서 반환하는 StringTokenizer보다 성능이 떨어질 수밖에 없다.

2.6 java.math.BigInteger클래스

  • 정수형으로 표현할 수 있는 값의 한계가 있으므로, 더 큰 값을 다뤄야할 때 사용하면 좋은것이 BigInteger이다.
  • 내부적으로 int배열을 사용해서 값을 다뤄서 long타입보다 훨씬 큰 값을 다룰 수 있다. 대신 성능은 long타입보다 떨어진다.

BIgInteger생성

생성하는 방법은 여러 가지가 있는데, 문자열로 숫자를 표현하는 것이 일반적이다. 정수형 리터럴로는 표혈할 수 있는 값의 한계가 있기 때문이다.

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

다른 타입으로의 변환

String toString()		// 문자열로 변환
String toString(int radix)	// 지정된 진법의 문자열로 변환
byte[] toByteArray()	    // byte배열로 변환

BigInteger의 연산

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

BigInteger add(BigInteger val)			// 덧셈(this + val)
BigInteger subtract(BigInteger val)		// 뺄셈(this - val)
BigInteger multiply(BigInteger val)		// 곱셈(this * val)
BigInteger divide(BigInteger val)		// 나눗셈(this / val)
BigInteger remainder(BigInteger val)	    // 나머지(this % val)
  • BigInteger는 불변이므로, 반환타입이 BigInteger이란 얘기는 새로운 인스턴스가 반환된다는 뜻

비트 연산 메서드

  • 워낙 큰 숫자를 다루기 위한 클래스이므로, 성능을 향상시키기 위해 비트단위로 연산을 수행하는 메서드들을 많이 가지고 있다.
  • 예를 들어 짝수는 제일 오른쪽 비트가 0일 것이므로, testBit(0)으로 마지막 비트를 확인하는 것이 더 효율적이다.

2.7 java.math.BigDecimal클래스

BigDecimal은 실수형과 달리 정수를 이용해서 실수를 표현한다. 실수의 오차는 10진 실수를 2진 실수로 정확히 변환할 수 없는 경우가 있기 때문에 발생하는 것이므로, 실수를 정수와 10의 제곱의 곱으로 표현한다.

BigDecimal의 생성

생성하는 방법은 여러 가지가 있는데, 문자열로 숫자를 표현하는 것이 일반적이다. 기본형 리터럴로는 표혈할 수 있는 값의 한계가 있기 때문이다.

BigDecimal val = new BigDecimal("123.4567890");	//문자열로 생성
BigDecimal val = new BigDecimal("123.456");	//double타입의 리터럴로 생성
BigDecimal val = new BigDecimal("123456");	//int, long타입의 리터럴로 생성가능
BigDecimal val = BigDecimal.valueOf(123.456);	//생성자 대신 valueOf(double)사용
BigDecimal val = BigDecimal.valueOf(123456);	//생성자 대신 valueOf(int)사용
  • 주의할 점은, double타입의 값을 매개변수로 갖는 생성자를 사용하면 오차가 발생할 수 있다는 것

다른 타입으로의 변환

String toPlainString()	//어떤 경우에도 다른 기호없이 숫자로만 표현
String toString()	//필요하면 지수형태로 표현할 수도 있음
  • 대부분 두 메서드 반환결과가 같지만, BigDecimal을 생성할 때 ‘1.0e-22’와 같은 지수형태의 리터럴을 사용했을 때 다른 결과를 얻는 경우가 있다.

태그:

카테고리:

업데이트: