도도한 개발자

[Java] #20-2. java.lang - System 클래스 본문

Backend/Java

[Java] #20-2. java.lang - System 클래스

Kiara Kim 2022. 4. 4. 20:00

* 각종 정보를 확인하기 위한 System 클래스

 

System 클래스의 가장 큰 특징은 생성자가 없다는 것이다. System 클래스의 3개의 static 변수가 선언되어 있는데 타입과 변수 명은 다음과 같다.

 

선언 및 리턴 타입 변수명 설명
static PrintStream err 에러 및 오류를 출력할 때 사용한다.
static InputStream in 입력값을 처리할 때 사용한다.
static PrintStream out 출력값을 처리할 때 사용한다.

 

System은 클래스 이름이다. 그 다음의 out은 위의 staitic으로 선언된 변수 이름이다. 위의 표를 보면 알 수 있듯 out은 PrintStream 타입이다. 그러므로 println() 메소드는 PrintStream 클래스에 선언되어 있으며, static 메소드다.

out이라는 클래스 변수와 println()이라는 메소드가 모두 static으로 선언되어 있기 때문에 별도의 클래스 객체 생성이 필요 없었던 것이다. 따라서 출력 관련 메소드는 System 클래스 말고 PrintStream 클래스에서 찾아야 한다. 이때 PrintStream과 InputStream은 모두 java.io 패키지에 선언되어 있다.

 

실제 System 클래스에 선언되어 있는 메소드들을 살펴보면 출력과 관련된 메소드들은 없다. 이름 그대로 시스템에 대한 정보를 확인하는 클래스이며, 이 클래스에서 제공되는 메소드를 분류해보면 다음과 같이 다양한 역할을 한다는 것을 알 수 있다.

 

º 시스템 속성(Property)값 관리

º 시스템 환경(Environment)값 조회

º GC 수행

º JVM 종료

º 현재 시간 조회

º 기타 관리용 메소드

 

이 중 "GC 수행"과 "JVM 종료" 관련 메소드들은 절대로 수행해서는 안 되는 메소드이다. 

 

** 시스템 속성(Property)값 관리

 

리턴 타임 메소드 이름 및 매개 변수 설명
static String ckearProperty(String key) key에 지정된 시스템 속성을 제거한다.
static Properties getProperties() 현재 시스템 속성을 Properties 클래스 형태로 제공한다.
static String getProperty(String key) key에 지정된 문자열로 된 시스템 속성값(value)을 얻는다.
static String getProperty(String key, String obj) key에 지정된 문자열로된 시스템 속성값(value)을 얻고, 만약 없으면, def 에 지정된 값을 리턴한다.
static void setProperties(Properties props) Properties 타입으로 넘겨주는 매개 변수에 있는 값들을 시스템 속성에 넣는다.
static String setProperty(String key, String value) key에 지정된 시스템 속성의 값을 value로 대체한다.

 

Property와 관련된 메소드에 대해, Propertires라는 클래스를 알아야 한다. Properties는 java.util 패키지에 속하며, Hashtable의 상속을 받은 클래스다. 컬렉션 관련 클래스에 대해 배우면 Hashtable이 어떤 클래스인지 알 수 있다. 

 

필요 여부와 상관 없이 자바 프로그램을 실행하면 Properties 객체가 생성되며, 그 값은 언제, 어디서든지 같은 JVM 내에서는 꺼내서 사용할 수 있다. 다음의 코드를 보자.

 

public class JavaLangSystem {

	public static void main(String[] args) {
		JavaLangSystem sample = new JavaLangSystem();
		sample.systemPropertiesCheck();
	}

	public void systemPropertiesCheck() {
		System.out.println("java.version = " + System.getProperty("java.version"));
	}
}

 

위 코드는 System.getProperty()라는 메소드를 사용하여 "java.version"이라는 키에 있는 값을 출력하도록 했다. main() 메소드에서 이 메소드만을 호출하도록 한 후 결과를 확인해보자.

 

java.version = 11.0.6

 

그런데 이 키를 어떻게 알 수 있을까? 이 부분에 대해선 24장에서 다룰 예정이다.

 

** 시스템 환경(Environment) 값 조회

 

리턴 타입 메소드 이름 및 매개 변수 설명
static Map<String, String> getenv() 현재 시스템 환경에 대한 Map  형태의 리턴 값을 받는다.
static String getenv(String name) 지정한 name에 대항다는 값을 받는다.

 

Properties라는 것은 추가할 수도 있고, 변경할 수도 있다. 그러나 환경값 enc라는 것은 변경하지 못하고 읽기만 할 수 있다. 대부분 OS나 장비와 관련된 것들이기 때문이다. 위의 systemPropertiesCheck() 메소드 마지막 줄에 다음의 한줄을 추가하고 결과를 확인해보자.

 

java.version = 11.0.6
JAVA_HOME = C:\Program Files\Java\jdk-17.0.2

 

만약 JAVA_HOME이 설정되어 있지 않다면 null로 나올 수도 있다.

 

** GC 수행

 

리턴 타입 메소드 이름 및 매개 변수 설명
static void gc() 가비지 컬렉터를 실행한다.
static void runFinalization() GC 처리를 기다리는 모든 객체에 대하여 finalize()메소드를 실행한다.

 

자바는 메모리 처리를 개발자가 별도로 하지 않기 때문이 위의 메소드는 개발 코드에 넣어서는 안된다.

 

** JVM 종료

 

리턴 타입 메소드 이름 및 매개 변수 설명
static void exit(int status) 현재 수행중인 JVM을 멈춘다.

 

이 메소드는 절대 호출되선 안 되는 것 중 하나이다. 지금 예제로 사용하는 소스들은 어차피 이 메소드를 호풀하지 않아도 죽지만, 안드로이드 앱이나 웹 애플리케이션에서 이 메소드를 사용하면 해당 애플리케이션의 JVM이 죽어버린다.

 

** 현재 시간 조회

 

리턴 타입 메소드 이름 및 매개 변수 설명
static long currentTimeMillis() 현재 시간을 밀리초 단위로 리턴한다.
static long nanoTime() 현재 시간을 나노초 단위로 리턴한다.

 

currentTimeMillis() 메소드는 현재 시간을 나타낼 때 매우 유용한 메소드다. UTC라는 Universal time 기준으로 1970년 1월 1일 00:00부터 지금까지 밀리터 단위의 차이를 출력한다. 밀리토는 1/1,000초다. 즉, 1.000 밀리초는 1,000ms 라고 표시하며 1초와 동일한다.

nanoTime() 메소드는 시간의 차이를 측정하기 위한 용도의 메소드다. 여기서 제공하는 시간은 나노초이며, 1/1,000,000,000초를 의미한다.

 

JavaLangSystem 클래스에 numberMinMaxCheck() 메소드의 속도를 점검하는 메소드를 다음과 같이 만들자.

 

public void numberMinMaxElapsedCheck() {
    JavaLangNumber numberSample = new JavaLangNumber();
    long startTime = System.currentTimeMillis();
    long startNanoTime = System.nanoTime();
    numberSample.numberMinMaxCheack();
    System.out.println("Milli second = " + (System.currentTimeMillis() - startTime));
    System.out.println("Nano second = " + (System.nanoTime() - startNanoTime));
}

 

각 타입별로 최대 최소값을 확인해서 출력하는 작업이 얼마나 소요되는지를 확인하고 있다. 결과를 보자.

 

Byte min = -128 max = 127
Short min = -32768 max = 32767
Integer min = -2147483648 max = 2147483647
Long min = -9223372036854775808 max = 9223372036854775807
Float min = 1.4E-45 max = 3.4028235E38
Double min = 4.9E-324 max = 1.7976931348623157E308
Character min = 0 max = 65535
Milli second = 1
Nano second = 1059500

 

가장 아래의 밀리초는 0으로 출력되지만, 나노초는 매우 큰 숫자로 출력된다. 실제로 나노초를 밀리초로 확인하기 위해 1/1,000,000으로 나누면 0.5ms다. 어떤 값이 더 정확하다고 말하기 어렵지만, 시간을 측정할 필요가 있을 때 나노초를 이용하는 것이 권장된다.

 

 

* System.out을 살펴보자

 

System 클래스에 선언되어 있는 out(System.out)과 err(System.err) 변수는 PrintStream이라는 동일한 클래스의 객체다. PrintStream 클래스는 static하게 사용하므로, 지금을 생성자를 살펴볼 필욘 없고 출력을 위한 메소드들에 어떤 것들이 있는지 알아보자.

 

º print()

º println()

º format()

º printf()

º write()

 

가장 많이 사용되는 메소드가 print()와 println() 메소드다. print() 메소드는 매개 변수에 있는 내용들을 출력하고 줄 바꿈을 하지 않고, println() 메소드는 매개 변수에 있는 내용들을 출력하고 줄바꿈 처리를 한다는 점이 다르다. println() 메소드는 매개 변수가 없는 메소드가 존재한다.두 메소드 모두 기본 자료형과 참조 자료형을 매개 변수로 사용할 수 있다.

 

print()와 println()가 어떤 매개 변수를 받는 한 번 보자.

 

print(booelan b) print(char c) print(char[] s) print(double d) print(float f)
print(int i) print(long l) print(Object obj) print(String s)  
println(boolean b) println(char c) println(char[] s) println(double d) println(float f)
println(int i) println(long l) println(Object obj) println(String s) println()

잘 보면 byte 타입이나 short 타입을 매개 변수로 받는 메소드가 선언되어 있지 않다. 그러나 두 개 모두 정수형이기 때문에 전혀 문제 없이 출력된다. 왜냐하면 byte나 short 타입을 print()나 println() 메소드에 넘겨주면 int 타입을 매개 변수로 받는 메소드에서 알아서 처리해주기 때문이다. 다음의 예제를 보자.

 

public class JavaLangSystemPrint {

	public static void main(String[] args) {
		JavaLangSystemPrint sample = new JavaLangSystemPrint();
		sample.printStreamCheck();
	}
	
	public void printStreamCheck() {
		byte b = 127;
		short s = 32767;
		System.out.println(b);
		System.out.println(s);
		printInt(b);
		printInt(s);
	}
	
	public void printInt(int value) {
		System.out.println(value);
	}
}

 

byte와 short 변수를 선언하고 출력한다. 그리고 int 타입을 매개 변수로 받는 printInt()라는 메소드를 만들었다. 컴파일이 제대로 될까?

 

127
32767
127
32767

 

출력하고자 하는 127과 32767이 문제없이 출력된 것을 볼 수 있다. 한 가지, 다음과 null을 출력하는 메소드가 있다.

 

public void printNull() {
    Object obj = null;
    System.out.println(obj);
    System.out.println(obj + "is object's value");
}

 

여기서 중요한 것은, obj 객체가 null이라는 점이다. 객체를 출력할 때 toString() 메소드를 단순히 호출한다고 생각하고 있다면, null인 obj의 toString()을 호출하는 셈이다. null인 obj는 아무 할당이 안되어 있기 때문에 메소드를 호출할 수 없다. 따라서 컴파일은 되지만 실행시 예외가 발생한다. 그러나 이 printNull() 메소드는 컴파일도 잘 되고 실행하면 다음과 같이 출력한다.

 

null
nullis object's value

 

이런 결과가 출력된 이유는 print() 메소드와 println() 메소드에는 단순히 toString() 메소드 결과를 출력하지 않기 때문이다. String의 valueOf() 라는 static 메소드를 호출하여 결과를 받은 후 출력한다. 즉, String.valueOf(obj)가 호출된 것이다. 다음과 같이 toString()으로 처리한 메소드를 실행해보자.

 

Exception in thread "main" java.lang.NullPointerException
	at chapter20.JavaLangSystemPrint.printNullToString(JavaLangSystemPrint.java:33)
	at chapter20.JavaLangSystemPrint.main(JavaLangSystemPrint.java:9)

 

위와 같은 예외가 발생한다.. obj 자체가 null이고, 이 객체의 toString()이라는 메소드를 불러버렸으니, 예외가 발생할 수밖에 없다. 참고로, 객체를 출력할 때에는 toString()을 사용하는 것보다는 valueOf() 메소드를 사용하는 것이 훨씬 안전하다.

 

첫 번째 출력문을 주석 처리하고 실행해보면 두 번째 출력문은 예외를 발생시키지 않는다. 두 번째 출력문은 null과 문자열을 합쳤는데도 왜 예외가 발생하지 않을까? 그 이유는 컴파일러에서 이 더하기 문장을 StringBuilder로 변한하기 때문이다. 두 번째 출력문의 괄호 안에 있는

 

obj + "is object's value"

 

내용은 다음과 동일하다.

 

new StringBuilder().append(obj).append(" is object's value")

 

 

* 실습

 

1.

public class NumberObjects {

	public static void main(String[] args) {
		NumberObjects sample = new NumberObjects();
		sample.parseLong("r1024");
	}
	
	public long parseLong(String data) {
		try {
			long l = Long.parseLong(data);
			System.out.println(l);
			return l;
		} catch (Exception e) {
			System.out.println(data + " is not a number");
			return -1;
		}		
	}
}

[실행결과]

r1024 is not a number

 

2. 

[소스코드]

public class NumberObjects {

	public static void main(String[] args) {
		NumberObjects sample = new NumberObjects();
		sample.printOtherBase(1024);
	}
	
	public void printOtherBase(long value) {
		System.out.println("Original : " + value);
		System.out.println("Binary   : " + Long.toBinaryString(value));
		System.out.println("Hex      : " + Long.toHexString(value));
		System.out.println("Octal    : " + Long.toOctalString(value));
	}
}

[실행결과]

Original : 1024
Binary   : 10000000000
Hex      : 400
Octal    : 2000

 

 

* 정리

 

1. 같은 패키지에 있는 클래스를 제외하고, 별도로 import 하지 않아도 되는 패키지는 무엇인가요?
java.lang 패키지는 별도로 import하지 않아도 된다. 

2. 자바의 메모리가 부족해서 발생하는 에러는 무엇인가요?
자바의 메모리가 부족하여 발생하는 에러는 OutOfMemoryError이다. 
이 에러에 대한 보다 자세한 설명은 "자바 개발자와 시스템 운영자를 위한 트러블 슈팅 이야기"를 참조하기 바란다.

3. 메소드 호출관계가 너무 많아서 발생하는 에러는 무엇인가요?
자신의 메소드를 자기가 다시 부르는 재귀 호출 메소드와 같은 것을 잘못 구현하면 StackOverflowError가 발생한다.

4. java.lang 패키지에 선언되어 있는 3개의 어노테이션에는 어떤 것들이 있고, 각각의 역할은?
java.lang 패키지에 선언되어 있는 어노테이션은 다음과 같다.
- Deprecated : 더 이상 사용하지 않는 다는 것을 명시
- Override : Override 명시
- SuppressWarnings : 경고 무시

5. Double과 Integer 같은 숫자 타입에서 처리할 수 있는 최대, 최소값을 알 수 있는 상수의 이름은?

기본 자료형을 참조자료형으로 만든 클래스들의 MIN_VALUE(최소값)와 MAX_VALUE(최대값) 를 사용하면, 각 타입의 최대 최소값을 확인할 수 있다.


6. Integer값을 2진법으로 표현하려면 어떤 메소드를 사용해야 하나요?
 Integer클래스의 toBinaryString() 메소드를 호출하면 매개변수의 값을 2진법으로 나타낸다.


7. Integer값을 16진법으로 표현하려면 어떤 메소드를 사용해야 하나요?
Integer클래스의 toHexString() 메소드를 호출하면 매개변수의 값을 2진법으로 나타낸다.

8. 속성(Properties)과 환경(Environment) 값의 차이는 무엇인가요?
Properties는 JVM에서 사용하는 속성 값을 제공하며, Environmemt는 시스템(장비)에서 사용하는 환경 값을 제공한다.

9. System.out과 System.err 에서 사용할 수 있는 메소드들은 어떤 클래스의 API를 봐야 하나요?
System.out과 System.err는 모두 java.io.PrintStream 클래스를 의미한다. 

10. System 클래스에서 현재 시간을 조회하는 용도로 사용하는 메소드 이름은 무엇인가요?
System.currentTimeMillis() 메소드를 호출하면 현재 시간을 밀리초(1/1000)단위로 제공한다. 이 시간은 1970년 1월 1일 00:00 부터 현재까지의 시간이다.


11. System 클래스에서 시간 측정 용도로 사용하는 메소드 이름은 무엇인가요?
System.nanoTime() 메소드는 나노초 단위로 결과를 제공하며, 이 메소드에서 제공하는 시간은 오직 소요 시간을 측정하기 위해서 사용된다.

12. System.out.print() 메소드와 System.out.println() 메소드의 차이는 무엇인가요?
System.out.print() 메소드는 데이터를 출력후 줄바꿈을 하지 않으며, System.out.println()메소드는 데이터를 출력후 줄바꿈을 수행한다.

13. System.out.println() 메소드에 객체가 매개변수로 넘어 왔을 때 String의 어떤 메소드가 호출되어 결과를 출력하나요? 그리고, 그 메소드를 사용하는 이유는 무엇인가요?

 System.out.println() 메소드에서 출력을 할 때에는 String 클래스에 선언된 valueOf()메소드가 수행된다. toString()메소드가 수행되는 것이 아니다. 


14. 숫자 계산을 위해서 필요한 메소드들을 모아 놓은 클래스는 무엇인가요?
숫자 계산을 위해서 Math라는 클래스가 존재한다. 

15. 위의 문제의 답인 클래스에 있는 메소드는 객체를 생성해서 사용해야 하나요?
 Math 클래스에 있는 상수와 메소드는 모두 static 으로 선언되어 있기 때문에 별도의 객체를 선언할 필요가 없다.

16. 숫자의 절대값을 구하는 메소드는 무엇인가요?
숫자의 절대값은 Math 클래스의 abs() 메소드를 사용하면 된다.

17. 숫자의 반올림을 하는 메소드는 무엇인가요?
반올림을 하는 Math 클래스의 메소드는 round()와 rint() 이다. 

18. 각도를 라디안으로 변환하는 메소드와 라디안을 각도로 변환하는 메소드는 각각 무엇인가요?
Math클래스에서 Radian으로 변환하는 메소드는 toRadians()메소드이며, Degree로 변환하는 메소드는 toDegrees() 메소드이다. 


19. 5의 4 제곱 값을 구하려고 하면 어떤 메소드를 사용해야 하나요?

Math 클래스의 pow() 메소드는 제곱값을 구하는 데 사용한다. 5의 4제곱은 Math.pow(5,4)과 같이 사용하면 된다. 


그럼 이만.