본문 바로가기
Study/JAVA

[Java] 9-1. java.lang패키지

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

 

java.lang패키지

java.lang패키지는 자바프로그래밍에 가장 기본이 되는 클래스들을 포함하고 있다. 그렇기 때문에 java.lang패키지의 클래스들은 import문 없이 사용할 수 있다. 자주 사용되는 클래스 몇가지를 학습해보자.

 

1. Object클래스

Object클래스는 모든 클래스의 최고 상위 클래스이기 때문에 모든 클래스에서 바로 사용할 수 있다.

Object클래스의 메서드 설 명
protected Object clone() 객체 자신의 복사본을 반환한다.
public boolean equals(Object obj) 객체 자신과 객체 obj가 같은 객체인지 알려준다.
protected void finalize() 객체가 소멸될 때 가비지 컬렉터에 의해 자동으로 호출된다. 이 때 수행되어야 하는 코드가 있을 경우 오버라이딩한다.(거의 사용 안함)
public Class getClass() 객체 자신의 클래스 정보를 담고 있는 Class인스턴스를 반환한다.
public int hashCode() 객체 자신의 해시코드를 반환한다.
public String toString() 객체 자신의 정보를 문자열로 반환한다.
public void notify() 객체 자신을 사용하려고 기다리는 쓰레드를 하나만 깨운다.
public void notifyAll() 객체 자신을 사용하려고 기다리는 모든 쓰레드를 깨운다.
public void wait()
public void wait(long timeout)
public void wait(long timeout, int nanos)
다른 쓰레드가 notify()나 notifyAll()을 호출할 때까지 현재 쓰레드를 무한히 또는 지정된 시간(timeout, nanos)동안 기다리게 한다.(timeout은 1000분의 1초, nanos는 10⁹분의 1초)

equals(Object obj)
매개변수로 객체의 참조변수를 받아 비교하고 그 결과를 boolean값으로 알려준다. 두 개의 참조변수가 같은 객체를 참조하고 있는지, 즉 두 참조변수에 저장된 값(주소값)이 같은지를 판단하는 기능을 한다. 하지만 오버라이딩하여 주소값이 아닌 저장된 내용을 비교하도록 변경하여 사용할 수 있다.

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

toString()
이 메서드는 인스턴스에 대한 정보를 문자열(String)로 제공할 목적으로 정의한 것이다.

public String toString() {
  return getClass().getName() + "@" + Integer.toHexString(hashcode());
}​

Object클래스에서는 위의 코드와 같이 정의되어 있기 때문에 적절하게 오버라이딩해서 사용하자.

clone()
이 메서드는 자신을 복제하여 새로운 인스턴스를 생성하는 일을 한다. 원래의 인스턴스는 보존하고 clone메서드를 이용해 새로운 인스턴스를 생성하여 작업을 하면 작업 이전의 값이 보존되므로 작업에 실패해도 원래의 상태로 되돌리거나 변경되기 전의 값을 참고하는데 도움이 될 것이다. Object클래스에 정의된 clone()은 단순히 인스턴스변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다. 예를 들면 배열의 경우 복제된 인스턴스도 같은 배열의 주소를 갖기 때문에 원래의 인스턴스에 영향을 준다. 따라서 clone메서드를 오버라이딩해서 새로운 배열을 생성하고 배열의 내용을 복사하도록 해야한다.

공변 반환타입
JDK1.5부터 공변 반환타입(covariant return type)이라는 것이 추가되었다. 이 기능은 오버라이딩할 때 상위 클래스 메서드의 반환타입을 하위 클래스의 타입으로 변경할 수 있게 한다.

얕은 복사와 깊은 복사
clone()은 단순히 객체에 저장된 값을 그대로 복제할 뿐, 객체가 참조하고 있는 객체까지 복제하지 않는다. 객체배열을 clone()으로 복제하는 경우에는 원본가 복제본이 같은 객체를 공유한다. 이를 얕은 복사라 하고 얕은 복사에서는 원본을 변경하면 복사본도 영향을 받는다. 반면에 원본이 참조하고 있는 객체까지 복제하는 것을 깊은 복사라하고 원본의 변경이 복사본에 영향을 미치지 않는다.

getClass()
자신이 속한 클래스의 Class객체를 반환하는 메서드이다. Class객체는 이름이 Class인 클래스의 객체이다.

public final class Class implements ... {
  ...
}​

위와 같이 정의되어 있다.
Class객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재한다. 클래스 파일이 클래스 로더(ClassLoader)에 의해서 메모리에 올라갈 때 자동으로 생성된다. 클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다. 먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고, 있으면 객체의 참조를 반환하고 없으면 클래스 패스(classpath)에 지정된 경로를 따라 클래스 파일을 찾는다. 찾지 못하면 ClassNotFoundException이 발생하고 찾으면 해당 클래스의 파일을 읽어 Class객체로 반환한다. 클래스 파일을 읽어서 사용하기 편한 상태로 저장해 놓은 것이 클래스 객체이다.

Class객체를 얻는 방법
Class객체에 대한 참조를 얻는 방법은 여러가지가 있다.

Class cObj = new Card().getClass(); // 생성된 객체로부터 얻는 방법
Class cObj = Card.class;	  // 클래스 리터럴(*.class)로 부터 얻는 방법
Class cObj = Class.forName("Card"); // 클래스 이름으로부터 얻는 방법​

특히 forName()은 특정 클래스 파일, 예를 들면 데이터베이스 드라이버를 메모리에 올릴 때 주로 사용한다. Class객체를 이용하면 클래스에 정의된 멤버의 이름이나 개수 등 클래스에 대한 모든 정보를 얻을 수 있기 때문에 Class객체를 통해서 객체를 생성하고 메서드를 호출하는 등 보다 동적인 코드를 작성할 수 있다.

Card c = new Card();			// new연산자로 객체 생성
Card c = Card.class.newInstance();	// Class객체를 이용하여 객체 생성​

 

2. String 클래스

String클래스는 아주 중요하므로 자세히 공부하자.

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

문자열의 비교
문자열을 만들 때는 문자열 리터럴을 지정하는 방법과 String클래스의 생성자를 사용해서 만드는 방법 두가지가 있다.

String str1 = "abc";			// 문자열 리터럴 "abc"의 주소가 저장됨
String str2 = "abc";			// 문자열 리터럴 "abc"의 주소가 저장됨
String str3 = new String("abc");	// 새로운 String인스턴스 생성
String str4 = new String("abc");	// 새로운 String인스턴스 생성​

String클래스의 생성자를 이용한 경우에는 new연산자에 의해 메모리할당이 이루어지기 때문에 항상 새로운 String인스턴스가 생성된다. 그러나 문자열 리터럴은 이미 존재하는 것을 재사용하는 것이다.

public class StringEx1 {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";
        String str3 = new String("abc");
        String str4 = new String("abc");

        System.out.println("str1 == str2 ? " + (str1 == str2)); // true
        System.out.println("str1 equals str2 ? " + str1.equals(str2)); // true
        System.out.println("str3 == str4 ? " + (str3 == str4)); // false
        System.out.println("str3 equals str4 ? " + str3.equals(str4)); // true
    }
}​

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

빈 문자열(empty string)
길이가 0인 배열이 존재할 수 있다. char형 배열도 길이가 0인 배열을 생성할 수 있다. 이 배열을 내부적으로 가지고 있는 문자열이 바로 빈 문자열이다.

join()과 StringJoiner
join()은 여러 문자열 사이에 구분자를 넣어서 결합한다. split()과 반대의 작업을 한다. 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]​

유니코드의 보충문자
String클래스의 메서드 중 매개변수의 타입이 int인 것들이 있다. 유니코드는 원래 2byte, 즉 16비트 문자체계인데 모자라서 20비트로 확장하게 되었다. 그래서 하나의 문자를 char타입으로 다루지 못하고 int타입으로 다룰 수 밖에 없다. 확장에 의해 새로 추가된 문자들을 보충 문자(supplementary characters)라고 한다. 모든 메서드가 보충 문자를 지원하는 것은 아니다. 매개 변수가 int인 것들은 보충문자를 지원하는 것이고 그렇지 않은 것은 지원하지 않는것이다. 보충 문자를 사용할 일이 거의 없기 때문에 이정도만 알아두자.

문자 인코딩 변환
getBytes(String charsetName)를 사용하면 문자열의 문자 인코딩을 다른 인코딩으로 변경할 수 있다. 자바가 UTF-16을 사용하지만 문자열 리터럴에 포함되는 문자들은 OS의 인코딩을 사용한다.

byte[] utf8_str = "가".getBytes("UTF-8");	// 문자열을 UTF-8로 변환
String str = new String(utf8_str, "UTF-8");	// byte배열을 문자열로 변환​

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

String str = String.format("%d 더하기 %d는 %d입니다.", 3, 5, 3 + 5);
System.out.println(str); // 3 더하기 5는 8입니다.​

기본형 값을 String으로 변환
숫자로 이루어진 문자열을 숫자로, 또는 그 반대로 변환하는 경우가 자주 있다. 기본형을 문자열로 변경하는 방법은 간단하다. 숫자에 빈 문자열을 더해주면 된다. 이 외에도 valueOf()를 사용하는 방법도 있다. 성능은 valueOf()가 더 좋다. 성능향상이 필요한 경우에만 valueOf()를 쓰자.

String을 기본형 값으로 변환
valueOf()를 쓰거나 parseInt()를 사용하면 된다. 예전에는 parseInt()와 같은 메서드를 많이 사용했는데 메서드의 이름을 통일하기 위해 valueOf()가 나중에 추가되었다. 두 메서드는 반환 타입만 다르지 같은 메서드다. parseInt()나 parseFloat()같은 메서드는 문자열에 공백 또는 문자가 포함되어 있는 경우 변환 시 예외(NumberFormatException)가 발생할 수 있으므로 주의해야 한다. 그래서 문자열 양 끝의 공백을 제거해주는 trim()을 습관적으로 같이 사용하기도 한다. 그러나 부호를 의미하는 +나 소수점을 의미하는 . float형 값을 뜻하는 f와 같은 자료형 접미사는 허용된다. 단 자료형에 알맞은 변환을 하는 경우에만 허용된다.

 

3. StringBuffer클래스와 StringBuilder클래스

StringBuffer클래스는 앞서 말했듯이 인스턴스를 생성할 때 지정된 문자열을 변경할 수 있다. 내부적으로 문자열 편집을 위한 버퍼(buffer)를 가지고 있으며, StringBuffer인스턴스를 생성할 때 그 크기를 지정할 수 있다. 편집할 문자열의 길이를 고려하여 버퍼의 길이를 충분히 잡아주는 것이 좋다. 편집 중인 문자열이 버퍼의 길이를 넘어서면 버퍼의 길이를 늘려주는 작업을 추가로 수행해야해서 작업 효율이 떨어진다.

StringBuffer의 생성자
생성자 StringBuffer(int length)를 사용해서 인스턴스에 저장될 문자열의 길이를 여유있게 지정하자. 버퍼의 크기를 지정해주지 않으면 16개의 문자를 저장할 수 있는 크기의 버퍼를 생성한다.

StringBuffer의 변경
append()를 사용하여 문자열을 편집한다. 여러번 호출이 가능하다. append()는 반환타입이 StringBuffer이고 자신의 주소를 반환한다.

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

StringBuilder란?
StringBuffer는 멀티쓰레드에 안전(thread safe)하도록 동기화되어 있다. 아직 멀티쓰레드나 동기화에 대해서 배우지 않았지만 동기화가 StringBuffer의 성능을 떨어뜨린다는 것만 이해하면 된다. 멀티쓰레드로 작성된 프로그램이 아닌 경우 StringBuffer의 동기화는 불필요하게 성능만 떨어뜨린다. 그래서 StringBuffer에서 쓰레드의 동기화만 뺀 StringBuilder가 새로 추가되었다. StringBuilder는 StringBuffer와 완전히 똑같은 기능으로 작성되어 있다.

 

4. Math클래스

Math클래스는 기본적인 수학계산에 유용한 메서드로 구성되어 있다. Math클래스의 메서드는 모두 static이고 자연로그의 밑과 원주율 두가지만 상수로 정의해 놓았다.

올림, 버림, 반올림
round()메서드는 항상 소수점 첫째자리에서 반올림을 하여 정수값(long)을 결과로 돌려준다. 원하는 자리 수에서 반올림된 값을 얻기 위해서는 10의 n제곱으로 곱한 후 다시 곱한 수로 나눠주기만 하면된다.

1. 원래 값에 100을 곱한다.
	90.7552 * 100 -> 9075.52
2. 위의 결과에 Math.round()를 사용한다.
	Math.round(9075.52) -> 9076
3. 위의 결과를 다시 100.0으로 나눈다
	9076 / 100.0 -> 90.76
	9076 / 100 -> 90​

ceil()은 올림 floor()는 버림이다. rint()도 소수점 첫 째자리에서 반올림하지만 반환값이 double이다. 그리고 rint()는 두 정수의 정 가운데에 있는 값은 가장 가까운 짝수 정수를 반환한다. round(-1.5) -> -1, rint(-1.5) -> -2.0

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

int addExact(int x, int y)	// x + y
int subtractExact(int x, int y)	// x - y
int multiplyExact(int x, int y)	// x * y
int incrementExact(int a)	// a++
int decrementExact(int a)	// a--
int negateExact(int a)		// -a
int toIntExact(long value)	// (int)value - int로의 형변환

위의 메서드들은 오버플로우가 발생하면 예외(ArithmeticException)를 발생시킨다.

삼각함수와 지수, 로그
예제를 통해 자주 쓰이는 메서드들의 사용방법만 확인하고 넘어가자.

import static java.lang.Math.*;

public class MathEx3 {
    public static void main(String[] args) {
        int x1 = 1, y1 = 1;
        int x2 = 2, y2 = 2;

        double c = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
        double a = c * sin(PI / 4); // PI / 4 rad = 45degree
        double b = c * cos(PI / 4);
//        double b = c * cos(toRadians(45));

        System.out.printf("a=%f%n", a);
        System.out.printf("b=%f%n", b);
        System.out.printf("c=%f%n%n", c);
        System.out.printf("angle=%f rad%n", atan2(a, b));
        System.out.printf("angle=%f degree%n", atan2(a, b) * 180 / PI);
        System.out.printf("angle=%f degree%n%n", toDegrees(atan2(a, b)));
        System.out.printf("24 * log10(2) = %f%n", 24 * log10(2));
        System.out.printf("53 * log10(2) = %f%n%n", 53 * log10(2));
    }
}​

StrictMath클래스
Math클래스는 최대한의 성능을 얻기 위해 JVM이 설치된 OS의 메서드를 호출해서 사용한다. OS에 의존하여 계산을 하고 있는 것이다. 이는 자바로 작성된 프로그램임에도 컴퓨터마다 결과가 다를 수 있게 된다. 이러한 차이를 없애기 위해 성능은 다소 포기하는 대신 OS에 상관없이 같은 결과를 얻도록 Math클래스를 새로 작성한 것이 StrictMath클래스이다.

 

5. 래퍼(wrapper) 클래스

객체지향 개념에서 모든 것은 객체로 다루어져야 한다. 그러나 자바에서는 8개의 기본형을 객체로 다루지 않는데 이것이 바로 자바가 완전한 객체지향 언어가 아니라는 얘기를 듣는 이유다. 그 대신 보다 높은 성능을 얻을 수 있었다.
때로는 기본형 변수도 어쩔 수 없이 객체로 다뤄야 하는 경우가 있다. 예를 들면 매개변수로 객체를 요구할 때, 기본형 값이 아닌 객체로 저장해야할 때, 객체간의 비교가 필요할 때 등등의 경우가 있다. 이 때 사용 되는 것이 래퍼(wrapper)클래스이다. 8개의 기본형을 대표하는 8개의 래퍼클래스가 있는데 이 클래스들을 이용하면 기본형 값을 객체로 다룰 수 있다.

기본형 래퍼클래스 생성자 활용 예
boolean Boolean Boolean(boolean value)
Boolean(String s)
Boolean b = new Boolean(true);
Boolean b = new Boolean("true");
char Character Character(char value) Character c = new Character("a");
byte Byte Byte(byte value)
Byte(String s)
Byte b = new Byte(10);
Byte b = new Byte("10");
short Short Short(short value)
Short(String s)
Short s = new Short(10);
Short s = new Short("10");
int Integer Integer(int value)
Integer(String s)
Integer i = new Integer(100);
Integer i = new Integer("100");
long Long Long(long value)
Long(String s)
Long l = new Long(100);
Long l = new Long("100");
float Float Float(double value)
Float(float value)
Float(String s)
Float f = new Float(1.0);
Float f = new Float(1.0f);
Float f = new Float("1.0f");
double Double Double(double value)
Double(String s)
Double d = new Double(1.0);
Double d = new Double("1.0");

래퍼 클래스들은 모두 equals()가 오버라이딩되어 있어서 주소값이 아닌 객체가 가지고 있는 값을 비교한다. 오토박싱이 된다고 해도 Integer객체에 비교연산자를 사용할 수 없다. 대신 compareTo()를 제공한다. 그리고 toString()도 오버라이딩되어 있어 객체가 가지고 있는 값을 문자열로 변환하여 반환한다. 이 외에도 래퍼 클래스들은 MAX_VALUE, MIN_VALUE, SIZE, BYTES, TYPE 등의 static상수를 공통으로 가지고 있다.

Number클래스
이 클래스는 추상클래스로 내부적으로 숫자를 멤버변수로 갖는 래퍼 클래스들의 상위 클래스이다. 기본형 중에서 숫자와 관련된 래퍼 클래스들은 모두 Number클래스의 하위 클래스이다. 그 외에도 Number클래스의 하위 클래스로 BigInteger와 BigDecimal 등이 있는데 BigInteger는 long으로도 다룰 수 없는 큰 범위의 정수를, BigDecimal은 double로도 다룰 수 없는 큰 범위의 부동 소수점수를 처리하기 위한 것으로 연산자의 역할을 대신하는 다양한 메서드를 제공한다.

문자열을 숫자로 변환하기

// 문자열 -> 기본형
byte	b = Byte.parseByte("100");
short	s = Short.parseShort("100");
int	i = Integer.parseInt("100");
long	l = Long.parseLong("100");
float	f = Float.parseFloat("3.14");
double	d = Double.parseDouble("3.14");

// 문자열 -> 래퍼 클래스
Byte	b = Byte.valueOf("100");
Short	s = Short.valueOf("100");
Integer	i = Integer.valueOf("100");
Long	l = Long.valueOf("100");
Float	f = Float.valueOf("3.14");
Double	d = Double.valueOf("3.14");​

JDK1.5부터 도입왼 오토박싱(autoboxing) 기능 때문에 반환값이 기본형일 때와 래퍼 클래스일 때의 차이가 없어졌다. 그래서 그냥 구별없이 valueOf()를 쓰는 것도 괜찮은 방법이다. 단 성능은 valueOf()가 조금 더 느리다.

오토박싱 & 언박싱(autoboxing & unboxing)
JDK1.5이전에는 기본형과 참조형 간의 연산이 불가능했기 때문에 래퍼 클래스로 기본형을 객체로 만들어 연산해야 했다. 그러나 이제는 기본형과 참조형 간의 덧셈이 가능하다. 컴파일러가 자동으로 변환하는 코드를 넣어주기 때문이다. 이 외에도 내부적으로 객체 배열을 가지고 있는 Vector클래스나 ArrayList클래스에 기본형 값을 저장해야 할 때나 형변환이 필요할 때도 컴파일러가 자동으로 코드를 추가해준다. 기본형 값을 래퍼 클래스의 객체로 자동 변환해주는 것을 오토박싱(autoboxing)이라고 하고, 반대로 변환하는 것을 언박싱(unboxing)이라 한다.

 

728x90
반응형

댓글