▒ 자바 API를 뜯어보자! (1)
1_자바 API의 개념
API란 Application Programming Interface의 약자로서, 직역하여 간결한 한국어로 표현한다면 프로그래밍을 위한 매개체, 규칙 정도로 번역할 수 있다. 이를 다시 JAVA언어에 대입해본다면 'JAVA언어로 어플리케이션을 개발하기 위해 미리 정의해둔 도구'라고 할 수 있다. 하지만 이러한 설명만으로는 API라는 것이 뭔지 쉽사리 와닿지 않을 것이다. 그럼 아래의 코드를 보자.
JAVA공부를 시작할 때 가장 처음 만나는 System.out.println();이라는 코드는 JAVA API의 대표적인 예시이자 API 개념을 설명하기 위한 가장 효과적인 코드다. 프로그래밍 언어를 처음 접하는 사람에게 System.out.println();이라는 문장은 출처가 모호한 단순한 명령어 정도로 인식될 뿐이나, 이는 엄연히 JAVA언어로 구현된 일련의 코드이다. 단지 이를 JAVA언어 개발도구가 미리 구현해서 클래스 형태로 제공하기에 우리는 직관적인 단어의 조합만을 가지고도 원하는 기능을 구현할 수 있는 것이다.
2_System.out.println("Hello World") 뜯어보기
이해를 돕기 위해 System.out.println();이라는 코드를 뜯어보기로 한다. 이 코드가 작동하는 과정을 나눠보면 아래와 같다. (이하는 class의 기본적인 개념들에 대해 이해하고 있는 것을 전제로 설명한다.)
(1). System클래스에 접근한다.
(2). out이라는 이름으로 정의된 PrintStream타입의 클래스 변수를 찾아낸다.
(3). PrintStream타입인 out이 가지고 있는 println()메서드를 실행한다.
2-1_System.out.println("Hello World") 의 정체
System클래스를 이해하기 위해서는 먼저 java.lang패키지에 대해 알아야한다. JAVA의 데이터 형식에 대해 공부하다보면 기본형 데이터와 참조형 데이터에 대해서 배우게 되는데, 참조형 데이터의 대표라고 할 수 있는 String계열의 데이터들과, 기본형 데이터를 참조형 타입으로 구현한 Integer, Double 같은 클래스들이 담겨진 폴더가 바로 java.lang패키지이다.
이런 참조형 데이터의 타입들은 코드 작성시 별도의 import 과정 없이 자연스럽게 타이핑하여 사용하기 때문에 JAVA언어를 공부하는 초기에는 이것들이 별개의 class 형태로 존재한다는 사실을 인식하기 어렵다. 하지만 이것들도 엄연히 별개의 클래스이며, 단지 사용빈도가 높은 java.lang패키지에 대해서는 별도의 import과정 없이도 사용할 수 있도록 설정 되어있을 뿐이다.
(참고 : import없이 사용할 수 있는 API는 java.lang 패키지가 유일하며, 그 외에는 import해야만 사용할 수 있다.)
System클래스는 이러한 java.lang패키지에 속하는 클래스로서, 기본적인 입/출력에서부터 시작하여 자바어플리케이션을 동작시키는데 필요한 기능들을 미리 구현해놓은 클래스라고 요약할 수 있다. 이 클래스가 실제로 사용되는 원리를 풀어보면 아래와 같다.
- java.lang패키지의 특성에 따라 자동으로 import 되어있다
- 접근제어자 public을 취하여 패키지에 관계 없이 어디서든 System클래스에 접근할 수 있다
- final클래스로서 클래스가 가진 정보들을 변조할 수 없으며 상속도 불가하다
- 생성자 System()이 private를 취하여 객체생성도 불가하다
- ★그러나, System클래스의 모든 메서드가 static 키워드로 정의되어있기에 언제 어디서나 System클래스에 정의한 메서드를 불러올 수 있다
이를 다시 한마디로 표현하자면 '중요한 거니까 건드리지는 말고, 가져다 쓰기만 해!' 라는 특성을 갖는 클래스라고 할 수 있겠다. 때문에 같은 java.lang패키지에 속한 String같은 클래스들과는 달리, 별도로 System타입의 객체를 생성하지 않아도 우리가 System.out.println();코드를 작성할 수 있는 것이며, 다른 코드들과는 달리 객체 생성이나 import 없이 마구 불러낼 수 있기에 출신성분이 모호한 명령어의 일종처럼 느껴지는 것이라고 할 수 있겠다.
2-2_System.out.println("Hellow World")의 실행
System클래스를 불러오는 과정을 이해했다면 System클래스의 클래스변수인 out을 거쳐 그 하위 메서드인 println()에 다다르는 방법은 쉽게 파악할 수 있다.
out은 PrintStream타입의 클래스 변수로서 PrintStream은 메시지 출력에 관련된 메서드들을 가지고 있는 클래스이다. System클래스 안에는 out과 유사한 역할을 하는 err 및 입력의 기능을 하는 in라는 이름의 클래스변수 또한 정의되어있으며, 이들의 공통된 특성을 나열하면 아래와 같다.
- public생성자를 갖기에 패키지에 관계 없이 변수 out을 호출할 수 있다
- static키워드를 갖기에 객체 별도의 System객체 생성 없이도 곧바로 호출할 수 있다
- fianl키워드를 갖기에 재정의 할 수 없다.
그런데, 위의 사진에서 클래스변수 out은 null로 초기화 되어있는 것처럼 보인다. final키워드에 대해 이해했다면 이 코드는 우리가 어떤 방법으로 변수 out에 접근하더라도 null값만을 확인할 수 있음을 의미한다는 것을 알 수 있을 것이다. 그런데 우리는 System.out.println()이라는 코드를 통해 null값이 아닌, 우리가 의도한 기능을 실제로 구현할 수 있다.
이는 System클래스가 JAVA어플리케이션의 근간이 되는 클래스로서 JAVA보다 더 저수준의 언어인 네이티브 코드를 거쳐 초기화되기 때문이다. JVM에서 가장 먼저 초기화되는 System클래스 안에서 보다 후순위에 있는 PrintStream 타입의 클래스변수를 온전히 선언하는 것은 논리상 모순되기에, 일단 null로 선언한뒤, 네이티브코드 수준에서 JAVA문법을 우회하여 PrintStream타입을 다시 초기화하는 과정을 거치는 것이라고 설명할 수 있다.
드디어 System.out.println()코드가 완성되었다.
2-2_System.out.println("Hello World")의 실행
일련의 과정을 거쳐 우리는 System클래스에 접근하여 PrintStream타입의 out이 가진 println()메서드를 실행할 수 있게 되었다. 여기에 이르러서는 별 다른 어려움 없이 코드를 읽을 수 있게된다. println()메서드의 원리를 풀어보면 다음과 같다.
- object타입의 매개변수를 받아서 이를 String타입의 s로 변환한다.
- if 부분 : 만약, 지금 이 메서드를 사용하는 객체가 PrintStream타입이라면 자신의 write()메서드로 s를 작성한다.
- else 부분 : 그렇지 않은 경우엔 PrintStream의 객체와 동기화하여 print()메서드에게 s를 넘기고 새로운 행으로 넘어간다.(여러 객체에서 System.out.println() 코드가 실행될 수도 있으므로 PrintStream과 동기화해서 관리)
우리가 평소에 System.out.println()코드를 사용했을 때 console창에 출력되는 내용은 else 부분의 실행 결과이며, if 부분은 PrintStream타입의 객체가 자체적으로 println()메서드를 사용 할 경우를 상정한 코드라고 추정해 볼 수 있다.
이어서, String데이터 s를 넘겨받은 print()메서드는 이를 다시 write()메서드에게 넘겨주어 기본형 데이터인 char의 단위로 콘솔창에 출력되도록 한다. String타입인 "Hello World"가 char로 변환되는 과정은 BufferedWriter클래스에서 확인할 수 있으나 이 내용은 추후 참조형 데이터로서의 String 타입에 대해 설명하면서 다뤄보고자 한다.
2-3_System.out.println("Hello World");
지금까지 System.out.println()라는 코드가 작동하는 과정에 대해서 알아봤다. sout 혹은 syso정도로 기억하고 있던 단순한 코드가 이런 복잡한 과정을 거쳐서 작동한다는 사실을 알아본 것이 우리에게 어떤 인상을 남겼는지는 저마다 다를 것이다. 개인적으로 필자는 System.out.println()라는 코드의 작동 원리에 대해 호기심을 가지고 탐구하기 시작한 시점에서 JAVA언어에 대한 흥미가 짙어짐을 느꼈던 것 같다. 막연하게만 느껴졌던 프로그래밍 언어의 안개 속에서 뭔가 만져진다는 느낌을 받았다고 할까? 그리고 꽤 적절하게 느껴지는 비유도 떠올랐다.
-
"JAVA언어를 다루는 것을 레고를 조립하는 것에 비유 한다면, API는 우리에게 제공된 레고블럭이라고 할 수 있지 않을까?"
-
어릴적 레고를 조립하던 기억을 떠올려보자. 이케아의 조립 메뉴얼을 떠올려도 좋다. 메뉴얼의 첫 페이지는 결과물의 조감도, 그리고 그 다음 페이지는 분명히 패키지에 포함된 도구들에 대해 설명하는 챕터였을것이다. 블럭을 쌓아올리기 위해서는 먼저 내가 다루려는 블럭들에 대해서 알아야 한다. 우리 모두 API을 들여다보는 습관을 길러보자.
'JAVA > API 뜯어보기' 카테고리의 다른 글
[JAVA] Math (0) | 2023.02.28 |
---|---|
[JAVA] 배열에서 indexOf() 사용하려면? (0) | 2023.02.24 |