2011년 5월 29일 일요일

자바의 네트워크 프로그래밍(Network Programming)

자바의 네트워크 프로그래밍에 대한 전반적인 개요를 파악할수 있는 시간을 마련하였다. 자바에서 전체 단원을 통틀어서 핵중의 핵이라고 할수 있는 단원이 바로 네트워크 프로그래밍이다. 그만큼 이제는 네트워크를 통하지 않고는 뭔가를 이룰수 없는 시대에 살고 있으니까 말이다. 그렇다면 프로그래밍에서 네트워크란 무엇인가? 한마디로 얘기하자면 서로 다른 컴퓨터를 통신망을 통해 연결한 것으로 대표적인 것으로는 인터넷 통신망을 들수 있겠다. 네트워크를 얘기할때 빼놓지않고 얘기하는 것이 서버(Server)와 클라이언트(Client) 개념일 것이다.


한쪽은 서버(Server)로 한쪽은 클라이언트(Client)로 지칭하는데 정보를 제공하는 컴퓨터나 응용프로그램은 서버라고 하고 정보가 필요해서 요청하는 컴퓨터나 응용프로그램은 클라이언트가 되는 것이다. 즉, 여러분이 클라이언트라고 생각하면 된다. 정보가 필요해서 인터넷을 떠돌아 다니는 쪽은 보통 여러분들이 아니겠는가? 바로 이런 서버와 클라이언트가 서로 정보를 주고 받을수 있게끔 다시 말해서 통신할수 있도록 만들어주는 선로나 연결망이 필요한데 이를 네트워크라 한다.


당연하지만 이런 네트워크를 기반으로 통신이 이루어지게 하기 위해선 최소한 두대 이상의 컴퓨터가 필요할 것이다. 더 쉽게 설명하자면 여러분이 인터넷을 쓸때 그냥 컴퓨터가 있다고 바로 인터넷을 쓸수 있는 것이 아니다. 여러분이 인터넷 회사에 응분의 대가(?)를 지불하고 그쪽 서버 컴퓨터에서 여러분이 접속할수 있는 선로를 개통해줘야 인터넷을 이용할수 있는 것이다. 여러분이 인터넷에서 어디를 가든 반대쪽 관련 서버에서 응답을 해줘야 쓸수 있는 것이고 컴퓨터가 서로 올바른 메세지를 주고 받기 위해서 규칙을 정해놓았는데 이런 규칙으로 만든 통신 규약을 프로토콜(Protocol)이라고 한다.

인터넷에서는 이를 IP(Internet Protocol)라는 이름의 프로토콜을 사용한다. 이제는 워낙 익숙한 용어가 되어버린지라 뭔지는 몰라도 막연히 자신의 고유한 인터넷 주소라는 것쯤은 알고 있는이가 많을 것이다. IP는 4바이트로 이루어져 있는데 .으로 번호를 네개 공간으로 나누어 192.168.1.100 이런식으로 쓰며 IP 주소라고 불린다. 여러분의 컴퓨터가 있는 집주소를 말한다고 생각하면 될것이다. 이런 IP 주소로 인해서 여러분의 컴퓨터가 다른 여러 컴퓨터들과 섞여서 몰래 통신을 하고 있어도 알게 모르게 여기저기 기록과 흔적이 남는 것이다. 따라서 안보인다고 함부로 나쁜짓(?) 하다가 잡히는게 바로 이런 이유 때문이다.ㅎㅎ


네트워크에 관련된 그림은 그려졌으리라 보고 이제 조금더 깊이 들어가보겠다. 중간중간에 용어가 생소하더라도 끝까지 읽어내려가기 바란다. 가면 갈수록 더 쉽게 설명해서 종국에 가서는 뿌듯한 성취감(?)을 맛보게끔 만들 것이다.ㅎㅎ 클라이언트쪽 프로그램에서 서버쪽 프로그램으로 정보를 요청하면 이를 수락하고 관련 정보를 제공하는등의 네트워크 서비스가 이루어지는데 이는 인터넷 소프트웨어 스택(Internet Software Stack)의 일부분인 트랜스포트 레이어(Transport Layer)를 통해 이루어진다.


우리는 이를 TCP/IP(Transport Control Protocol/Internet Protocol)라고 부른다. Internet Software Stack의 일부분이니 엄밀히 표현하면 TCP/IP stack이 되겠다. 이런 네트워크 서비스를 가능케 해주는 트랜스포트 레이어는 두가지 프로토콜로 나뉜다. 하나는 TCP(Transport Control Protocol)고 다른 하나는 UDP(User Datagram Protocol)다. 이를 프로그래밍상에서는 소킷(socket)이라고 부르는데 순우리말(?)로 소켓이다.ㅎㅎ 이 정도 왔으면 네트워크 프로그래밍에 대해서 좀 아는 이들이라면 감을 잡았을 것이다. 왜 네트워크 얘기만 하면 소켓 소켓하는지 말이다.^^


TCP는 연결지향 프로토콜(Connection-Oriented Protocol)로 두 컴퓨터간의 데이터 흐름의 신뢰성을 보장한다. 즉 클라이언트와 연결이 되었다면 통신은 물론이고 데이터 교환시 손실없이 주고받을수 있다는 것을 보장하는 것이다. TCP는 파일이나 데이터를 네트워크에서 여러개의 작은 조각으로 나누어서 보내거나 반대로 이를 재조립하는 기능을 한다. 네트워크에서 이렇게 파일이나 데이터를 여러개로 쪼개는 것을 패킷(packet)이라 한다. 관련 서비스 프로그램의 예로는 ftp나 telnet을 들수 있겠다.

UDP는 데이터의 독립적인 패킷을 보내는 프로토콜인데 한 컴퓨터에서 다른 컴퓨터로 데이터를 주고받을때 TCP와는 다르게 연결성도 없고 신뢰성도 없는 한마디로 데이터 교환시 손실여부를 보장할수 없는 패킷을 말하는데 이를 데이터그램(datagrams)이라 부른다. 관련 서비스 프로그램의 예로는 Clock server나 Ping을 들수 있는데 그냥 폼잡고 쓰는 것이니 괜히 이런거 외우지 말아라.ㅎㅎ 자바는 TCP(Socket 클래스)와 UDP(DatagramSocket 클래스)를 모두 지원한다. 요런걸 신경써야쥐~


TCP와 UDP 프로토콜 모두 컴퓨터의 특정 프로세스를 통해 전송되는 데이터는 포트(Port)를 이용한다. 포트는 16비트 정수로 표현되며 값은 0에서 65535까지 지정가능하다. 대표적으로 알려져있는 포트 넘버는 1024까지 예약되어 사용되고 있으며 구체적인 예로 ftp는 포트넘버로 21/tcp을 telnet은 포트넘버로 23/tcp을 그리고 http는 포트넘버 80/tcp,udp으로 사용된다. 따라서 우리가 포트넘버를 설정할 일이 있을때는 이미 예약되어 사용되고 있는 1024까지의 포트넘버는 충돌방지를 위해 사용하지 말고 1025부터 그 이상의 포트넘버를 사용하는 것이 신상(?)에 좋을 것이다.^^


그림을 참조하면서 패킷이 어떻게 전달되는지 살펴보자. 우리가 말로는 ftp에 연결한다 하지만 클라이언트에서 서버로 ftp를 사용하겠다고 요청할때는 포트 21로 연결을 시도한다. http를 사용하려고 하면 포트넘버 80번으로 연결한다. 즉 서버에서는 포트넘버로 클라이언트가 ftp를 사용할건지 http를 사용할건지 알수 있는 것이다. 클라이언트에서 포트넘버 21로 연결요청을 하면 ftp 서버와 연결시켜주고 80으로 요청하면 http 서버로 연결하는 식이다.


자 이렇게 설명해도 이해가 되지 않았다면 지금 위에 있는 그림 한장이 여러분의 갈증을 풀어주리라 믿는다.^^ 백문이 불여일견인지라 설명만 줄창 하는것보다는 한번 보는게 나을것이라는 생각에 올렸는데 이제 막힌 속이 뻥뻥 뚫리고 있길 바란다. 사실 조금전에 이와같은 계층구조를 도표로 언급하였지만 아마 그때는 그냥 무심코 지나친 이가 태반이었을 것이다. 그런 이들은 후에 본 단원을 한번 더 읽어보기 바란다.^^


방금 우리가 배운 포트와 응용 프로그램들을 연결시켜주는게 아까 말했던 소켓(socket)이다. 너무 자세히 해서 혼란스럽다면 조금 위로 올라가서 소켓 관련 부분을 다시 읽어보기 바란다. 이제는 머리 회전이 팍팍 될것이다.^^ 왜 이렇게 같은 얘기를 이리치고 저리치고 했느냐면 바로 우리가 자바에서 공부할 부분이 바로 Socket 클래스이기 때문이다. 자바에서는 통신 네트워크 프로그램을 Socket 클래스로 제작가능하다. 쉽게 말해서 서버와 클라이언트를 이어 서로 정보를 주고 받을수 있는 통신 프로그램을 우리가 Socket을 이용해 만들수 있다는 것이다.

프로그래밍 관점에서 소켓을 간단히 설명하자면 클라이언트의 소켓(Socket)과 서버의 소켓(Socket)이 있는데 중간에 이를 연결시켜주는 서버소켓(ServerSocket)이 있다. 서버소켓이 하는 일은 클라이언트가 접속하기를 기다리고 접속을 시도하면 서버의 소켓을 만들어서 클라이언트의 소켓과 연결해주고 다시 계속해서 다른 클라이언트가 접속하기를 기다리고 접속 시도를 하면 위와같이 연결해주는 일을 반복한다. 아래 그림에서 특히 화살표들을 유심히 쳐다보고 있으면 알기 싫어도 알게 될것이다.^^


아직까지 이해가 안된 이들을 위해 간략하게 덧붙이자면 소켓끼리 놀도록 만들면 연결된다는 것이다. 아래 그림을 보면 이해가 더 빠를지도 모르겠다.


자바에서 이렇게 Socket 클래스를 이용해서 프로그래밍하는 것을 거창한 말(?)로 TCP 프로그래밍이라고 하는데 네트워크에 관련해서 집중적으로 알아야되는 자바 패키지가 있다. 무엇일까나? ㅎㅎ 바로~ 바로~~ java.net 패키지이다. 이름에서 뭔가 풍기지 않는가? net? 그렇다 바로 네트워크 냄새를 솔솔 풍기는 것처럼 관련 클래스들도 네트워크에 관한 클래스들로 꽉꽉 차 있다. 그럼 한번 살펴보자.^^


위의 java.net 패키지는 네트워크 프로그래밍에서 여러분이 앞으로 만들 클라이언트 프로그램과 서버 프로그램들을 보다 심플하고 빠르게 발전시킬수 있는 최적의 솔루션을 제공할 것이다. 위의 그림만 봐도 관련 패키지 이해는 더할나위없이 충분할터이지만 필자가 워낙 자상(?)한지라 여러분을 위해 더 간결하게 해당 패키지에 대해서 정리하며 오늘은 여기서 마칠터이니 다음 시간까지 java.net 패키지는 이해가 되던 안되던 라이브러리에서 한번쯤은 꼭 훓어보고 오는 정성을 보여주기 바란다.^^

java.net 패키지 구성

Interfaces
* ContentHandlerFactory
* FileNameMap
* SocketImplFactory
* URLStreamHandlerFactory

Classes
* ContentHandler
* DatagramPacket
* DatagramSocket
* DatagramSocketImpl
* HttpURLConnection
* InetAddress
* MulticastSocket
* ServerSocket
* Socket
* SocketImpl
* URL
* URLConnection
* URLEncoder
* URLStreamHandler

Exceptions
* BindException
* ConnectException
* MalformedURLException
* NoRouteToHostException
* ProtocolException
* SocketException
* UnknownHostException
* UnknownServiceException

2011년 5월 23일 월요일

자바 애플릿(Java Applet)

자바는 몰라도 애플릿을 아는 사람은 상당수 있을 것이다. 그만큼 웹브라우저상에서 널리 쓰이고 있어서인데 자바로 개발된 애플릿은 우리가 쓰는 일반 브라우저를 통해서 실행할수 있는 프로그램으로 웹페이지 양식인 HTML 문서내에 포함되어 사용되며 비교적 구성이 간단한 기능의 소규모 프로그램을 수행하는 언어를 말한다. 그래서 이름도 application을 뜻하는 App과 어원상 매우 작다는 뜻의 접미사 -let이 만나 app+let=applet으로 불리운다. 하지만 일각에서는 매킨토시로 유명했던 Apple사에서 쓰인 AppleScript의 영향을 받아서 Applet이라고 쓴다는 얘기도 있다.

자바 애플릿은 이용자가 서버에 별도의 요청을 하지 않아도 수행할수 있는데 단순한 애니메이션이나 시계나 달력같은 간단한 계산을 이용한 작업들을 웹상에서 실현할수 있어 응용 프로그램으로 상당히 폭넓게 쓰이고 있다. 애플릿을 만들기 위해서는 두가지 파일이 필요한데 자바 애플릿의 .class 파일과 웹상에서 해당소스를 구현할 .html 파일이 필요하다. 하지만 .class 파일을 만들기 위해서는 컴파일을 해야하니 원소스인 .java 파일이 필요하므로 결국 애플릿을 만들려면 총 세개의 파일을 만들어야된다고 볼수 있다.

자바 애플릿을 실행하려면 자바런타임(JRE: Java Runtime Environment) 프로그램을 다운받아서 설치하여야 되는데 자바 프로그램을 만들고 있는 우리로서는 JDK 소프트웨어에 이미 포함되어 있으므로 Java Plug-In에 의해서 자동으로 요청하여 다운받을수 있으니 신경쓰지 않아도 된다. 자바 프로그램을 개발이 아닌 그냥 이용하는 목적으로 프로그램이 필요하다면 Java SE Runtime Environment 6 Update 25 버젼만 설치하면 웹브라우저에서 애플릿을 사용하는데 큰 무리가 없을 것이다.


오래전에는 마이크로소프트사의 익스플로러 브라우저에 인스톨 패키지로 자바 프로그램을 구동시키는 자바가상머신(JVM)이 기본적으로 포함되어 있어서 신경쓸 필요가 없었으나 자바개발회사인 썬마이크로 시스템즈에서 소송을 제기한 이후부터는 더이상 허가없이 마음대로 탑재할수 없게 되어서 플랫폼에 개별적으로 따로 설치해야 사용할수 있게 되었다. 위에 링크 걸어놓았다시피 오라클 사이트에서 무료로 프로그램을 제공하고 있으니 쓰는데 특별한 어려움은 없을 것이다.


자 이제 자바 애플릿이 어떤 것인지 이 정도 설명했으면 개요는 충분하리라 본다.ㅎㅎ 실전으로 들어가서 애플릿을 어떻게 사용하는지 배워보기로 하자. 애플릿은 웹상에서 구현되는데 여러분이 보고 있는 웹페이지들은 웹브라우저라는 프로그램으로 html 파일을 로딩한다. 따라서 html 파일 내용에 해당 애플릿 파일이름을 지정해주고 실행하면 관련 애플릿이 작동될 것이다. html 파일 내부를 들여다보면 아래와 같은 형태를 지닌다.

<html>
<head>
</head>
<body bgcolor="000000">
<center>
<applet
code = "Test56.class"
width = "500"
height = "300"
>
</applet>
</center>
</body>
</html>

다른건 몰라도 자바를 배웠으니 Test56.class 라는 문구는 들어올 것이다. 웹페이지에서 Test56.class 자바파일을 구동하라는 것이다. 시작을 알리는 <...>과 끝을 알리는 </...>는 홈페이지를 만들어본 이들이라면 아주 친근할 것인데 html 태그(tag) 보통 홈페이지 태그라고 부르며 html 페이지에서 사용하는 일종의 명령어이다. 여러분이 쓰고있는 대부분의 자바에디터에서는 굳이 관련 태그를 위처럼 손수 작성할 필요없이 html applet 파일 만들기 버튼만 눌려도 자동으로 기본적인 코드들이 나와 쉽게 쓸수 있을 것이다. 이런 홈페이지 태그들은 말그대로 해석하면 어려울 것이 없을 것이다.

우리가 집중해야되는 부분은 중간에 <applet 으로 시작해서 code 에 관련 자바파일을 써주고 웹상에서 어느정도 크기로 관련 애플릿을 실행할지 정해주면 되는데 예제에서는 가로 500 이랑 세로 300 사이즈로 해당 애플릿을 웹페이지에서 작동시킬수 있도록 만들어주었다. 우리가 알아야될 문구는 <applet...>으로 시작해서 </applet>으로 끝난다. 따옴표는 어떤때에 써야되는지 숙지하기 바란다. html 파일이름은 아무거나 해도 되겠지만 분류를 위해서 해당 자바파일의 이름을 따서 예제처럼 Test56.html 로 만들어서 웹브라우저로 실행시키면 된다. 즉 웹브라우저 없는 컴퓨터는 없을테니 그냥 더블클릭하면 될것이다.^^ 그런데 웹브라우저의 특성인 임시파일 저장기능으로 인해 애플릿 수정후에 다시 작동해도 반영이 잘 되지 않을때가 있다. 그러므로 자바 애플릿을 만드는 중에는 자바 자체의 애플릿 실행프로그램(appletviewer.exe)으로 소스를 확인하면서 수정보완해 가는것이 훨씬 편리하다. 이 프로그램은 자발개발툴(JDK)에 포함되어 있으니 따로 설치할 필요는 없다. 실행방법은 아래와 같다.

javac Test56.java
appletviewer Test56.html

그럼 본격적으로 프로그래머의 본분으로 돌아가서 자바 애플릿 프로그램을 만들어 보기로 하자. 일반 자바 프로그램보다는 단순해서 그런지 그 구조에도 약간의 차이가 있다. 일단 메인메소드가 없다. 따라서 자바소스를 보고 이게 애플릿인지 아닌지 바로 판단이 될것이다. 또한 애플릿을 사용하기 위해서는 말그대로 Applet 클래스를 상속받아서 써야하고 해당클래스는 public으로 그리고 상속받은 메소드들도 오버라이드 해주어야 하므로 접근지정자를 public으로 만들어서 쓴다. 상속에 대해서 이미 공부하였으니 코드를 보면 바로 이해가 될것이나 아직 자바의 상속에 대해서 가물가물한 이들은 저번 강좌를 다시 한번 들춰보려할지도 모르겠지만 조금이라도 기억이 난다면 그리 복잡한 애플릿 프로그램을 만드는 것이 아니니 그냥 따라와도 괜찮을 것이다.ㅎㅎ

자바애플릿을 쓰려면 관련 패키지를 import 해야되는데
import java.applet.*;
import java.awt.*;
java.applet 패키지와 java.awt 패키지가 주로 쓰인다.

애플릿의 생명주기(Life Cycle of an Applet)
자기자신을 초기화시키고, public void init( ) {...}
실행을 시작하고, public void start( ) {...}
실행을 중지하고, public void stop( ) {...}
흔적없이 깨끗이 정리한다. public void destroy( ) {...}

Applet 클래스에서 상속받은 메소드를 오버라이드해서 사용
public void init( )   //웹페이지를 처음 로딩해서 초기화할때 호출되는 메소드
public void start( )   //웹페이지를 처음 방문이나 재방문시 호출되는 메소드
public void stop( )   //다른 웹페이지로 이동할때 호출되는 메소드
public void destroy( )   //웹페이지를 닫을때 호출되는 메소드

자바 애플릿 작동원리
init( ) ==> start( ) ==> paint( )
start( ) <==> stop( )
stop( ) ==> destroy( )
부연설명을 하자면 init( ) 메소드는 일반 자바 프로그램에서 메인메소드처럼 처음 시작하는 곳으로 시스템에서 애플릿을 맨 처음으로 로딩할때 쓰이며 start( ) 메소드 이전에 처리되므로 초기화 명령들은 이곳에 넣고 사용하면 된다. start( ) 메소드는 애플릿을 실행할때 쓰이니 다른 페이지를 갔다가 다시 올때도 작동하며 실행후에 자동으로 paint( ) 메소드를 부른다. paint( ) 메소드는 java.awt 패키지에 있으며 웹페이지에서 문자열을 출력할때 필요하므로 약방에 감초처럼 쓰이니 꼭 기억해두기 바란다. stop( ) 메소드는 start의 반대로 실행중지할때 쓰이니 다른 페이지로 갈때 자동으로 작동된다. destroy( ) 메소드는 말그대로 소멸시키는 것이라 웹페이지를 닫을때 작동되며 그 이전에 자동으로 stop( ) 메소드를 실행한후 destroy( ) 메소드를 처리한다. 따라서 시스템이나 메모리에 반환이나 돌려주어야하는 값이 있을시 destroy( ) 메소드를 이용하면 될것이다. 그리고 Applet 클래스에서 상속받은 메소드들은 추상메소드가 아니므로 필요할시 쓰면되고 아니면 안써도 그만이다. 꼭 구현해야될 이유는 없으나 기본적으로 만들어 놓으면 나중에 써먹기는 좋을 것이다.^^ 이제 예제를 보면서 자바 애플릿의 원리를 깨우치는 시간을 가져보겠다.

Test56.html 파일안에 있어야할 내용
<html>
<head>
</head>
<body bgcolor="000000">
<center>
<applet
code = "Test56.class"
width = "500"
height = "300"
>
</applet>
</center>
</body>
</html>


일단 실행은 위의 예제 Test56.java를 컴파일한후에 얻는 Test56.class과 위의 Test56.html 파일을 같은 곳에 넣고 Test56.html 파일을 더블클릭한후 웹브라우저로 실행시켜서 보기 바란다. 이번 예제는 원리이해를 시키기위해 만들어졌으므로 웹페이지를 왔다갔다 해보면서 어떻게 작동해야되는지 보아야하니 애플릿뷰어가 아닌 웹브라우저를 사용하기 바란다.

예제풀이에 들어가겠다. Test56.html 을 웹브라우저로 오픈하면 맨 처음 init( ) 메소드가 작동되면서 변수 a에 0이라는 값을 넣고 변수 s에 "강이의 자바강좌"라는 문자열을 넣고 setBackground( ) 메소드를 실행한다. 이 메소드는 바탕화면의 색깔을 바꾸어주는 메소드로 Component라는 클래스안에 있다. Applet 클래스는 이미 Component 클래스를 상속받아 이루어진 클래스이므로 따로 정의해서 쓸 필요가 없다. 이어서 start( ) 메소드가 작동되고 바탕색이 CYAN으로 되어있으니 웹페이지의 바탕색깔을 CYAN으로 바꾸고 끝낼줄 알았는데 아니나 다를까 이게 끝이 아니다.* start( ) 메소드가 아까 paint( ) 메소드를 자동으로 호출한다고 하였는데 실행해 보았으면 알겠지만 바탕색깔이 노란색이므로 paint( ) 메소드에서 case 1을 실행하였음을 알수 있다. 예제를 공부하다 보면 자바 애플릿의 작동원리가 저절로 깨우쳐질 것이다.ㅎㅎ

자바 애플릿은 아주 간략하게 단원을 정리하고 넘어가려 했는데 예상밖으로 글이 길어졌다. 예제를 이용해 웹페이지를 띄우고 다른 페이지에 갔다가 다시 돌아와 보기도 하고 새로고침도 눌러보는등 해당 애플릿 소스를 다각도로 응용하면서 심층학습은 본인이 직접하길 바란다. 그럼 이만 글을 마치겠다.^^

2011년 5월 22일 일요일

스레드의 동기화(Synchronization)

자바에서는 쓰레드 자체가 어떻게 움직일지 우리가 예상할수 없으므로 다중쓰레드를 사용하다보면 예기치못한 문제를 야기할수 있다. 보여줄 예제를 설명삼아 미리 얘기해 보겠다. 여러개의 쓰레드에게 각기 다른 출력 메세지를 주고 모든 쓰레드를 작동시킨다. 문제는 한 쓰레드가 일정시간 출력을 하다가 대기모드로 전환하면 이어서 다른 쓰레드가 출력을 할것이고 쓰레드 각자가 맡은 메세지를 다 출력할수도 있고 안하고 다른 쓰레드에게 바턴이 넘어갈수도 있을 것이다. 그렇다면 화면에 출력되는 메세지는 아래처럼 말그대로 뒤죽박죽 두서가 없어질 것은 자명하다.


[강이의 자바강좌[쓰레드의 동기화[Synchronization]
]
]

동시 프로그래밍 영역에서 다중쓰레드는 말그대로 어디로 튈지 모르는 공과 같다. 어느 쓰레드가 먼저 작동할런지는 모르지만 이왕 작동했으면 처음부터 끝까지 맡은바 임무(출력 메세지)는 완수하도록 권한을 부여하는 것이 동기화(Synchronization)라 할수 있겠는데 동기화라는 말자체가 어려워서 많이 애먹는것 같다. 필자는 동기화라는 표현보다는 보호막이라는 표현이 synchronization을 이해하는데 더 도움이 될꺼라 생각한다.


자바에서 다중쓰레드가 공유하고 있는 자원을 어떤 쓰레드가 실행하고 있을때 다른 쓰레드는 접근하지 못하도록 막아주는 "보호막" 같은 장치가 바로 synchronized 라는 키워드다. 쓰는 방법은 쓰레드에서 보호막 치기를 원하는 부분을 { } 블록으로 감싸고 그 위에 synchronized(레퍼런스) 라는 타이틀을 만들어주면 된다. 다시 코드로 표현하면 아래와 같이 될것이다.

synchronized(레퍼런스)
{
   ...
   ......
}


[강이의 자바강좌]
[Synchronization]
[쓰레드의 동기화]

예제에서는 쓰레드에게 권한을 주는 용도로 쓰일 레퍼런스를 key라고 명하고 Object를 이용해 만들어 쓰고 있는데 이 권한을 같이 공유하면서 서로 쓸수 있도록 만들어야되므로 static을 붙였다. 결과에서 보듯이 한 쓰레드가 key(권한)를 얻어 출력을 시작하면 다른 쓰레드가 출력이 끝날때까지 대기했다가 key(권한)를 넘겨받아 자신이 가진 메세지를 출력하고 남아있는 쓰레드도 역시 출력이 끝날때까지 기다렸다가 본인이 가진 메세지를 key(권한)를 넘겨받은 후에야 출력을 한다. 예제에서는 쓰레드가 3개인 다중쓰레드를 작동하였는데 작업환경에 따라 어느 구문이 먼저 출력될지는 장담할수 없겠지만 어느 쓰레드건 출력을 시작했으면 처음부터 끝까지 그 구문은 완전하게 찍히는 것을 확인할수 있을 것이다.

이렇게 일정 구문을 동기화 시켜서(보호막 만들어서) 사용한다고 하여 동기화 구문(Synchronized Statements)이라고 부른다. 말나온김에 동기화 메소드(Synchronized Methods)도 공부해 보자. 말그대로 메소드앞에 그러니까 메소드 리턴타입앞에 synchronized 키워드를 붙여주면 메소드 전체를 동기화 즉 보호막 만들어서 사용할수 있다.

synchronized void print(String message )
{
   ...
   ......
}

이 경우에는 쓰레드가 해당클래스의 객체를 공유하도록 만들어야 정상적으로 작동된다. 이렇게 설명하고 끝내면 비난(?)이 빗발칠걸로 예상되므로 동기화 메소드에 관련된 예제도 아래에 준비하였으니 코드를 보면서 연구해보기 바란다. 결과는 위와 같을 것이나 이번에는 구문이 아닌 메소드를 동기화시켰다는게 차이점이다.


예제를 간략하게 설명하자면 클래스의 레퍼런스로 key를 만들어 쓰레드끼리 공유해서 쓰도록 하고 있고 쓰레드의 실행블록인 run( ) 메소드에서 동기화시킨 print( ) 메소드를 key 레퍼런스를 이용해 불러서 쓰면 특정 쓰레드가 해당블록 실행시 다른 쓰레드가 관련 메소드에 들어오지 못하도록 보호막을 쳐준다. key의 데이터형이 지금 클래스명이라는 것을 여러분이 알아챘기 바란다. 예제를 곰곰히 이리저리 씹어보면 어느순간(?) 이해가 되기 시작할 것이다. 생성자(constructor)를 이용하면 코드가 훨씬 더 간결해질 것이나 이해를 돕고자 기존 예제를 최대한 건드리지 않는 쪽으로 가닥을 잡아서 만든 까닭에 소스가 좀 길다. 마음에 안들면 생성자를 이용해서 여러분이 원하는데로 응용해 보기 바란다.+ 제발 해봐라.ㅎㅎ 또한 synchronized 키워드를 메소드에서 제하고 다시 실행을 하여 무슨 차이가 있는지 비교하기 바란다.^^

쓰레드의 동기화는 동시 프로그래밍 영역에서 매우 유용하게 쓰이는 부분이다. 이보다 더 간단할수는 없다라는 심정으로 쓰레드의 Synchronization에 관한 가장 기본적인 내용만이라도 확실하게 기억하기를 바라면서 본 강좌를 아주 심플하게 구성하였으니 더도 덜도말고 최소한 오늘 배운 내용은 확실하게 습득하기 바란다.^^

2011년 5월 21일 토요일

스레드의 우선순위(Priority)

오늘은 저번 시간에 약간 건드렸지만 쓰레드에 우선순위를 두고 원하는 특정 쓰레드를 빨리 처리하는 법(?)을 배워보도록 하자. 여러개의 쓰레드를 돌려서 동시작업을 진행하더라도 어떤 쓰레드는 가장 빨리 작동하기를 원하는게 있을수 있다. 그럴때는 쓰레드별로 우선순위를 정해서 세팅을 해놓고 프로그램을 돌리면 된다. 저번 시간에 보았던 예제를 오늘 수업에 맞게 일부분 수정하였으니 뭐가 바뀌었는지 어떻게 우선순위를 세팅하는지 저번 시간에 졸지 않았다면 예제를 그냥 보기만 하면 이해하는데 큰 무리가 없을 것이다.


저번 시간에 본 예제와 상당히 흡사하다. 다른점은 sleep( ) 메소드 쓸려고 메인메소드에서 예외처리를 해줬고 중간에 우선순위를 정하기 위해서 setPriority( ) 메소드를 이용하였다. 해당 메소드의 괄호안을 보면 아래와 같은 문구가 있는 것을 볼수 있다.

java.lang.Thread
public static final int MAX_PRIORITY    //기본값은 10 으로 세팅되어 있다
public static final int MIN_PRIORITY     //기본값은 1 로 세팅되어 있다
public static final int NORM_PRIORITY //기본값은 5 로 세팅되어 있다

주석에 달아놓았듯이 괄호안에 1부터 10까지 원하는 우선순위값을 setPriority( ) 메소드에 넘겨주면 값이 높은 쓰레드부터 더 큰 비중을 두고 먼저 처리한다. 정수값 대신에 위에 있는 변수명을 적어주면 기본값으로 정해져있는 해당값을 자동으로 넘겨준다. 우리가 저번시간에 우선순위값을 주지도 않고 값을 출력하라고 요청했을때 5라는 숫자가 나온 것은 쓰레드의 기본우선순위값이 NORM_PRIORITY로 위처럼 5라고 자동세팅되어 있기 때문이다.

혹시나 저번 시간에 왜 5가 찍힐까하는 의문을 가졌던 이들은 그 궁금증이 해소되었길 바란다. 본 예제를 여러번 실행해보면 쓰레드의 우선순위값이 높은 곳에 더 비중을 두고 해당명령이 처리되는 것을 확인할수 있다. 중간에 우선순위가 낮은 쓰레드를 돌리는 중이라도 우선순위가 높은 쓰레드를 발견시 해당 쓰레드를 우선적으로 먼저 처리할려고 동작하는 모습을 볼수 있을 것이다. 여러분의 작업환경에 따라 결과값이 달라질수 있으므로 직접 실행해보면서 공부하기 바란다.

이렇게 다중쓰레드도 개별로 가중치를 다르게 하여 우리가 어느 정도 제어를 할수 있도록 만들어주는게 오늘 배운 강좌의 중심내용이라고 할수 있을 것이다. 별 어려운건 없었을 것이나 눈으로만 보지말고 직접 프로그램을 짜보고 응용도 해보면서 공부하기 바란다. 자신이 직접 해보면 새롭게 깨우치는 부분이 꼭 생기기 마련이다. 그것은 본인을 제외한 어느누구도 채워주고 싶어도 채워줄수가 없다. 이런 것이 진정한 프로그래밍의 묘미인데 강이의 자바강좌를 듣는 모든 이들은 이런 범주에 속하길 기원한다.^^

2011년 5월 20일 금요일

다중스레드(Multi-thread)


자바 프로그램내에서 두개 이상의 쓰레드를 돌릴때 부르는 다중쓰레드 혹은 멀티쓰레드에 대해서 공부해 보기로 하자.  도표에서 보듯이 각 쓰레드에 대한 명령을 일정시간 돌아가면서 실행하여 유저 입장에서는 그것이 동시에 실행되고 있는듯이 보이게 된다. 즉 한 프로그램내에서 각각의 다른 프로그램들이 동시에 개별적으로 움직이듯이 만드는 이와 같은 멀티쓰레드 형태를 자바가상머신(JVM)에서는 허용하고 있다.


방금 언급하였듯이 동시 프로그래밍이라고 하지만 내부적으로는 이것 작동했다가 저것 작동했다가 왔다갔다하면서 쓰레드 내부에 있는 작업 스케줄러가 암암리(?)에 상황따라 각 쓰레드를 일정시간 작동시키고 있는 것이다. 여러분이 워드를 치고 있다고 가정할때 프린트 명령을 쳐놓고 계속 작업할때도 있을테고 아니면 스펠링 체크를 명령한후 안에서 계속 다른 작업을 할수도 있을 것이다. 자바로 따지면 이런 것이 가능한 것은 하나의 프로그램이지만 각각의 쓰레드가 동시간대에 움직이며 서로 리소스를 공유하면서 실행하기에 이런 동시작업이 가능해진다. 엄밀히 따지면 작업들을 쓰레드별로 쪼개서 담당 처리한다고 생각해도 무방할 것이다.

지금까지 필자가 한 얘기를 못알아들었다면 싸그리 잊어버려도 좋다. 이제부터 아주 직접적이고 단순하게 설명하겠다. 자바에서 다중쓰레드는 말그대로 쓰레드가 두개 이상 즉 여러개 움직이는 것을 말한다. 필자가 보여줄 예제는 0부터 100까지 카운트하는 프로그램이다. 쓰레드를 세개 만들고 돌리는데 각각의 쓰레드가 하나의 프로그램인양 0부터 100까지 카운트하기 시작한다. 예제를 실행해보면 알겠지만 실행순서는 쓰레드1, 쓰레드2, 쓰레드3 이렇게 순서대로 실행된다. 그러나 순서대로 실행되지 않고 무작위로 어떤 것이 실행될지 모를뿐더러 어디까지 세고 나중에 다시 셀지도 알수 없다.

프로그램을 실행할때마다 다른 결과값이 나오니 상당히 의아해할지 모르나 다중쓰레드의 개념을 익히기에 부족함이 없으면서도 아주 단순한 예제니 어렵지 않을 것이다. 빠른 이해를 위해서 각 쓰레드별로 이름을 달아놓았고 각 쓰레드의 우선순위가 무엇인지 출력하라고 해놓았다. 예제에서 쓰인 getName( ), setName( ), getPriority( ) 같은 메소드들은 자바 API를 보면 알겠지만 전부 Thread 클래스안에 있는 것들이다. 상속을 받았으니 편안하게 불러쓰고 있는 것인데 모르는 이는 없으리라 본다. 실행해보면 알겠지만 우선순위는 모든 쓰레드가 다 똑같다. 즉 확인한 우선순위 값에서 보듯이 동시에 작업한다고는 하나 자원은 한정되어 있고 처리는 동시에 하라고 하니 이것저것 실행하면서 같이 작업하는 것처럼 보이도록 무난히도 애쓰는 모습(?)이 역력한 것이다.^^


아마 이 프로그램을 보고 그럼 순서대로 쓰레드를 작동하게 만들고 싶으면 어떻게 해야되냐는 의문을 가지는 이가 생길 것이다. 어떻게 만드는지는 여러분이 저번 시간에 이미 배웠다. 기억이 안난다면 여러분 자신의 조인트(join( ))를 알아서 까기 바란다.ㅎㅎ 배운데로 했는데도 안되는 이들은 예외(InterruptedException)없이 조인트를 한번 더 까기 바란다.^^ 오늘 배운 멀티쓰레드는 그냥 예제를 돌려보면 어떤 것인지 알게 될것이다. 단 세번 이상 돌려봐야 이런 것이 멀티쓰레드구나 이해될 것이다. 결과를 비교해보면서 잠시 명상(?)을 취해보기 바란다.^

2011년 5월 18일 수요일

자바의 스레드가 끝날때까지 기다려주는 join( ) 메소드

자바의 쓰레드는 게임과 네트워크 프로그래밍에서 중추적인 역활을 담당하는 파트 중 하나이다. 지겹게 느낄지 모르겠지만 피가 되고 살이 될것이라 믿고 하나하나 습득해 가기 바란다. 오늘은 생판 새로운 메소드를 들고 왔다. 바로 쓰레드가 끝날때까지 친절히 기다려주는 join( ) 메소드다. 이것도 역시 sleep( ) 메소드처럼 예외처리를 해줘야한다. 도표 열심히 봤는데 없는거 들고 나왔다고 째려보는 이는 없겠징? *_*; 일단 예제를 보자.


주석을 상세히 달아놔서 그냥 봐도 이해가 될것이다.^^ 너무 성의없다고 할까봐 조금더 설명을 곁들여 보겠다.* isAlive( ) 메소드는 말그대로 프로그램에서 쓰레드가 살아서 움직이고 있는지 테스트하는 메소드이다. 쓰레드가 작동하고 있는 중이라면 true를 아니라면 false 값을 반환한다. join( ) 메소드는 이런 쓰레드가 작동할 경우 끝날때까지 기다려주고 다음 명령문을 실행한다. 쓰레드만 start( ) 메소드로 작동시키고 join( ) 메소드가 없으면 결과가 제대로 출력되지 않는다. at쓰레드가 미처 계산하기도 전에 메인메소드(main쓰레드)가 다음 명령문을 실행해 버린다. 뭔가 분리되어 움직이는 것 같다. 프로그램은 우리가 지시한 명령 즉 작업 스케줄데로 움직일 뿐이지만 쓰레드를 이용하면 이렇게 동시다발적으로 일어나는듯한 현상(?)이 발생한다. main쓰레드 따로 우리가 만든 at쓰레드 따로 움직이는데 이를 우리가 join( ) 메소드로 제어한다고 생각하면 이해가 쉬울 것이다.

join( ) 메소드는 아래와 같이 세가지 타입이 있다.

void join( ) //쓰레드가 끝날때까지(죽을때까지) 기다린다
void join(long millis) //쓰레드를 1/1000초만큼 기다린다
void join(long millis, int nanos)
// 쓰레드를 (1/1000)+(1/100000000)초만큼 기다린다

지금은 프로그램이 짧으니까 쓸일이 없겠지만 방대한 프로그램일 경우 기다리는 시간까지 제어가 가능하다. 뭔가 하라고 지시를 했는데 시간내에 지시사항을 완료하지 못할경우 다음사항을 진행하게 만드는 기능으로 써먹을수 있을 것이다. 이런 기능이 없다면 평생 기다리는 일이 생길지도 모르니까 말이다.ㅎㅎ

false
true
5050
false

결과를 보면 알겠지만 해당 쓰레드가 작동하는지 안하는지 볼수 있게 소스를 만들었다. 당연한거지만 join( ) 메소드를 실행하고 난후에 isAlive( ) 메소드로 테스트한다면 어떤 값이 나올까? 당연히 false가 나올 것이다. 쓰레드가 끝날때까지 기다리는 명령어가 join( )이니까 말이다. 모두 다 맞췄으리라 기대하면서 오늘은 이만 하겠다.^^

2011년 5월 17일 화요일

자바의 스레드를 이용한 프로그램 일시중지 sleep( ) 메소드

자바 쓰레드의 생성부터 소멸에 이르기까지 그와 관련된 중요 메소드를 표시한 도표이다. 여러분이 저번 시간에 공부한 것도 보일 것이다. start( ) 메소드와 run( ) 메소드인데 어라(?) run( )메소드가 Running 화살표에 있어야 되는데 안보이는구먼. 있다고 생각해라.ㅎㅎ 암튼 자바의 쓰레드에 관련된 메소드가 많이 있긴 하지만 대충이라도 위에 적힌 메소드의 쓰임에 대해서는 여러분이 인지하고 있어야 될것이다. 보다 자세한 사항은 자바 API에 있는 java.lang 패키지의 Thread 클래스를 살펴보면 찾아볼수 있을 것이다. 어렵게 생각하지 말고 자바 라이브러리 보기전에 도표를 보면서 일단 관련 메소드의 기능을 상상해 보라. 이번에도 마찬가지지만 영어의 뜻만 제대로 알아도 반은 먹고 들어간다. 영어공부 열심히 하자.ㅎㅎ 오늘은 그 중에서 가장 간단하고도 재미(?)있는 sleep( )메소드에 대해 알아보기로 하겠다.

도표에서 sleep( ) 메소드가 있는 곳을 보면 뭔가 작동하다가 블록킹(?)했다고 보인다. 즉 뭔가 하다가 일시중지하는 그런 명령어다. sleep( )는 1/1000초(millisecond)로 세팅이 되어있는데 인자에 값을 넣어주면 넣은 값만큼 시간을 지연시킨후에 다음 명령을 실행시킨다. 즉 1초를 지연시키고 싶으면 1000을 넣어주면 될것이고 10초를 지연시키고 싶으면 10000을 넣어주면 될것이다. 곱셈 못하는 이는 없겠지?^^ 그럼 간단한 예제를 보면서 음미해 보기로 하자.


하나

두울
세엣
네엣
다섯

보다시피 프로그램은 아주 간단하다. 배열 하나 만들어놓고 그걸 반복문을 통해 출력하는 예제이나 쓰레드를 이용한 시간차 공격(?)을 한다는게 다를 뿐이다. 여러분이 실행을 해보면 실행속도를 이렇게도 조절할수 있구나라고 감탄을 금치 못할 것이다. 아님 말구 --; 배열이야 배웠으니 패스하고 역시 Thread.sleep(3000); 이 명령문이 신기할 것이다. 앞서 설명하였으니 3000이 3초(3000*1/1000)라는건 알테고 어떻게 Thread안에 있는 sleep 메소드를 객체생성도 없이 불러내서 쓸수 있느냐라는 의문이 들지 않는가? 무작정 그냥 쓰면 되는걸까? ^^ 우리는 이미 예전에 객체생성없이 클래스안의 메소드를 바로 불러서 쓸수 있게 만드는 방법을 배웠었다. 기억이 나지 않는다면 static 관련 강좌를 다시 보기 바란다. 바로 sleep( ) 메소드가 static으로 되어있기 때문에 이렇게 편하게 쓸수 있는 것이다. 앞으로는 이렇게 일일이 설명하지 않을테니 그냥 썼으면 아하 뭔가(?) 있구나 알아서 캐치하기 바란다.^^

신기한 점이 하나 더 있을텐데 보이지 않나? 찾은 사람 손~ 손~~ㅎㅎ 메인메소드 옆에 있는 throws InterruptedException 인데 sleep( ) 메소드 이용시 예외처리를 해야 제대로 작동된다는 소문(?)이 있다. 물론 여러분이 배운 try catch로도 만들수 있을 것이다. 귀찮더라도 직접 throws 관련구문 빼고 try catch 구문으로 바꿔서 실행해 보기 바란다. 오늘은 자바의 쓰레드 워밍업 두번째 시간으로 가볍게 진행했는데 맨위에 있는 쓰레드 도표라도 틈나는데로 자주 쳐다보기 보란다. 너무 느리다고 혹시 불평하고 있는이가 있을지 모르겠지만 자바의 쓰레드에서 떨어져 나가는 이가 상당히 많다. 필자가 이를 방지하고자 최대한 보폭을 조절하고 있음이니 방심하지 말고 긴장하면서 복습 철저히 하기 바란다.^^

2011년 5월 15일 일요일

자바의 쓰레드(Thread)

자바의 스레드는 동시 프로그래밍의 영역에 있다고 보면 된다. 동시 프로그래밍(Concurrent Programming)이 가능하도록 만들어주는 기본 단위로 프로세스(Processes)와 쓰레드(Threads)를 들수 있는데 자바 프로그래밍에서는 쓰레드가 대부분을 차지하므로 우리도 여기에 주안점을 두고 공부할 것이다. 그만큼 자바에서 쓰레드는 핵심적인 부분 중 하나이다. 자바를 공부하는 이들 태반이 쓰레드에 대한 내용을 어려워하고 지긋지긋해 한다. 그러나 이번 강이의 자바강좌를 통해 쓰레드에 대한 기본적인 원리를 확실하게 깨우치고나면 쓰레드가 더이상 여러분을 가로막는 산이 아니라 함께 할수 있는 동반자가 될 것이라 확신한다. 진도는 천천히 충분한 시간차(?)를 두면서 나아갈 생각이니 미루지 말고 주어지는 내용과 예제는 반드시 그날 그날 마스터하도록 하자.^^

프로세스(Process): 컴퓨터에서 연속적으로 실행되고 있는 프로그램을 말하는 것으로 여러개의 프로세서를 사용하면 멀티프로세싱이고 여러개의 프로그램을 같은 시간에 시분할 방식으로 돌리면 멀티태스킹이라 말한다.

쓰레드(Thread): 프로세스내에서 실행되는 흐름의 단위를 말하는 것으로 하나의 프로그램은 최소한 하나의 쓰레드(main thread)를 가진다. 프로그램에 따라 둘 이상의 쓰레드를 동시에 실행할수 있는데 이를 멀티쓰레드라고 부른다.

위에 설명한 것은 보편적으로 하는 얘기라 잘 와닿지 않을테니까 보다 쉽게 설명해 보기로 하겠다. 여러분이 프로그램을 하나 만들었는데 그게 동영상 플레이어라고 치자. 그런데 영상과 동시에 그와 관련된 영상 제목과 줄거리를 영상화면 밑에 자막으로 나오는 기능을 넣고 싶다면 어떻게 프로그램을 짜야될까? 영상을 플레이하는 기능과 영상에 관련된 문서를 디스플레이하는 기능, 이 두가지를 동시에 실행하도록 만들어야할 것이다. 이럴때 우리는 쓰레드가 필요한 것이다. 지금 컴퓨터나 그외 기기로 이 글을 읽으면서 음악같이 듣는 것도 마찬가지라 할수 있겠다. 즉 하나의 프로그램에서 동시에 두개 이상의 일을 수행할 필요가 있으면 쓰레드를 이용하면 된다. 쓰레드는 경량 프로세스(lightweight processes)라고 불리기도 하는데 쓰레드가 프로세스에 비해 리소스를 덜 잡아먹고 더 효율적이다.

지금까지 우리는 프로그램을 짜면서 쓰레드를 전혀 사용하지 않았다고 생각할지 모르겠지만 우리가 직간접적으로 상속받고 쓰는 클래스들의 내부에 보편적으로 쓰레드 기능이 작동하고 있기 때문에 우리가 눈으로 확인이 불가능했을뿐 자바에서 알아서 처리해 주고 있었다고 생각해도 무방하다. 우리가 흔히 써왔던 메인메소드를 메인쓰레드라 생각하면 이해가 쉬울 것이다. 바로 이렇게 자바에서 밀접하게 활동하고 있는 쓰레드를 우리가 직접 제어하고 작업스케줄을 만들어 프로그래머의 통제하에 움직이도록 만드는게 이번 강좌의 목표라 할수 있겠다.


지금부터 쓰레드를 사용하는 방법을 예제를 통해 살펴보기로 하겠다. 쓰레드를 만들고 실행시키려면 run( )이라는 메소드를 오버라이드해서 사용해야 한다는 규약이 있다. 또한 이 run( )이라는 메소드를 작동시키기 위해서 start( )라는 메소드로 작업지시를 해서 run( ) 메소드를 실행하게 만들어야 쓰레드가 작동된다. 예제는 간단한 메시지를 화면에 출력하는 것인데 이미 강좌 초반에 했던것과 결과는 별반 다름이 없을 것이다. 허나 결과는 같더라도 우리는 쓰레드를 이용해서 화면에 메세지를 출력했다는게 이번 장의 핵심이다. 쓰레드를 이용해서 화면에 메세지를 출력하는 예제인데 두가지 방법이 있다. 둘다 java.lang 패키지에 속해있는데 하나는 Runnable이라는 인터페이스를 구현하는 방법이고 다른 하나는 Thread라는 클래스를 상속받아서 쓰레드를 작동시키는 두가지 방법이 있다. Thread 클래스도 Runnable 인터페이스를 구현(implements)하고 있는데 차례대로 어떻게 쓰는지 예제를 보면서 공부해 보기로 하자.


Runnable을 통해 쓰레드를 실행하였습니다

Runnable이라는 인터페이스를 구현하는 예제인데 아까 언급했듯이 인자가 없는 run( ) 메소드를 위처럼 오버라이드해야 한다. 메인메소드에서 쓰레드를 만들고 실행시키기 위해 자체 클래스의 인스턴스를 생성해서 쓰레드 객체생성할때 인자로 넣어주고 쓰레드를 시작하라는 start( ) 메소드를 작동시키는 예제이다. 실행결과는 딸랑 한줄이지만 여러분은 쓰레드를 이용해 작업지시를 내렸다는데 의의(?)가 있다고 하겠다. Thread 클래스가 없는데 난데없이 튀어나와서 이해가 안되는 이가 있을지 모르겠는데 방금전에 java.lang 패키지에 들어있다고 했다. 그래서 쓸수 있는 것이다.ㅎㅎ 이해가 잘되지 않는다면 강좌초반에 설명한 import 부분에 대해서 살펴보면 알겠지만 자바는 자주 쓰이는 기본 클래스에 대해서는 특별히 가져오지 않더라도 쓸수 있도록 배려(?)를 해놓았다. Thread가 이에 속하니 그 중요성을 잠시나마 실감했길 기대한다.^^

그럼 이번에는 Thread라는 클래스를 직접 상속받아서 쓰레드를 만들고 실행해 보기로 하겠다.


Thread를 통해 쓰레드를 실행하였습니다

결과는 위랑 다를게 없다. 다만 인터페이스로 구현해서 간접적으로 Thread 클래스를 사용하느냐 아니면 Thread를 위처럼 직접 상속받아서 쓰레드를 작동하느냐의 차이다. 쓰레드를 작동시키기 위해 아까처럼 start( ) 메소드로 지시하면 run( ) 메소드가 지시받아서 쓰레드를 작동시킨다. 코드가 이게 조금이라도 줄어서 보기 편하기는 하지만 자바에서는 다중상속이 안되므로 이 클래스는 더이상 다른 클래스를 상속받아 쓸수 없는 단점이 있다. 그렇지만 그전 예제는 인터페이스라 다른 클래스를 상속받아 쓸수 있으므로 보통 Runnable을 쓰는게 일반적이다.

오늘은 워밍업(?) 단계로 자바의 쓰레드를 어떻게 만들어서 동작시키는지만 알아보았다. 쓰레드가 눈에 보이는게 아니므로 내용의 난이도를 떠나서 그냥 막연하게 느껴질 것이다. 너무 자연스러운 현상이니 걱정하지말고 본 내용만이라도 확실하게 숙지하고 차근차근 따라오면 여러분은 어느 순간에 쓰레드에 대해서 능수능란하게 논할수 있는 자바 프로그래머가 되어 있을 것이다. 정말? 복습이라도 철저히 한다면 말이다.^^

2011년 5월 10일 화요일

자바의 정적 내부클래스(Static nested classes)

자바의 내부클래스에 대해서 공부하고 있는데 오늘은 그 대미를 장식할 내부클래스중에서도 정적 내부클래스에 대해서 살펴보기로 하자. 비정적 내부클래스를 공부하였으니 정적 내부클래스는 무엇이겠는가? 당연히 비정적 내부클래스에 static을 붙인 형태가 바로 정적 내부클래스(Static nested classes)다. 그럼 어떤 형태인지 한번 보자.

class Outer
{
   ... //static으로 선언된 변수만 정적 내부클래스안에서 쓸수 있다

   static class StaticNestedClass //정적 내부클래스
   {
      ...
   }

   class Inner //비정적 내부클래스
   {
      ...
   }

}

비교하라고 내부클래스 두개 다 적어보았다. static으로 지정되면 값을 공유하며 객체생성없이 접근이 가능하고 또한 static으로 된 일반 멤버들은 그냥 가져다 쓸수 있지만 그렇지 않은 일반 멤버들은 객체를 생성해야만 쓸수 있다. 이미 static에 관한 내용은 예전 강좌에서 다루었으니 잘 떠오르지 않는다면 다시 한번 보고 이 자리에 오기 바란다. 느꼈겠지만 내부클래스를 공부하면서 계속해서 연계되는 부분이 발생하고 있다. 특히 그런 이유는 내부클래스도 클래스이기 때문에 겹치는게 많기 때문일 것이다.


그럼 위의 예를 기준으로 어떻게 정적 내부클래스에 접근해야되는지 자바코드를 살펴보자.

클래스명.정적 내부클래스명
Outer.StaticNestedClass

컴파일후 생성되는 실행파일
Outer$StaticNestedClass.class

그럼 이번에는 정적 내부클래스를 쓰기위한 객체생성 방법을 보기로 하자.

Outer.StaticNestedClass n = new Outer.StaticNestedClass( ); //Outer 밖에서
StaticNestedClass n = new Outer.StaticNestedClass( ); //Outer 안에서 생성

static이 객체없이 접근가능하므로 Outer 클래스의 객체없이도 정적 내부클래스의 객체를 만들수 있다는게 뽀인트(?)다. this 용법을 기억하는지 모르겠다. static에서 this를 쓸수 있는지 없는지 자세한건 여기를 누르고 예제를 살펴보면 알수 있다.^^

나는 정적 내부클래스다
1
2
11
22
22
I'm f()

결과는 위와 같다. static 내부클래스에서 멤버들의 호출을 통한 쓰임새를 자세히 알수 있도록 예제를 구성하였다. 짧은 프로그램이지만 정적 내부클래스의 요지를 파악하는데 부족함이 없을 것이다. 오늘로서 자바의 내부클래스에 대해서 마치겠다. 내부클래스는 쓰는 빈도수가 높은만큼 확실히 알고 넘어가기 바란다. 수고많았다.^^

자바의 익명클래스(Anonymous classes)

자바의 비정적 내부클래스의 일종인 익명클래스에 대해서 공부하는 시간을 갖기로 하겠다. 익명클래스는 말에서 풍기듯 이름이 없는 클래스를 말한다. 익명클래스는 객체를 생성하는 문장 { }; 안에 재정의할 메소드를 오버라이드해서 적어주면 되고 끝낼때는 괄호 닫을때 }; 세미콜론을 반드시 붙여주어야 한다. 쓰는 방식은 아래와 같다.

클래스명 인스턴스명 = new 클래스명( )
{
   ...//원하는 메소드를 오버라이드해서 재정의한다.
};

여기서 클래스명은 재정의할 메소드가 있는 곳의 해당 클래스를 말하는 것이니 오해없기 바란다. 객체생성 한두번 해본거 아니잖냐? ㅎㅎ 익명클래스는 메소드를 오버라이드해서 사용하므로 오버라이드 규칙에 따라서 사용하면 되고 익명클래스안의 메소드를 사용하려면 해당 인스턴스명을 통해야 한다. 익명클래스는 오버라이드에 대해서 알아야하므로 자바의 상속에 대해서 열심히 공부한 이들은 내용의 반은 먹고 들어가는게 익명클래스다. 기억이 안나면 예전 강좌를 찾아보기 바란다. 자바코드 보면서 혹시 세미콜론이 }; 괄호 다음에 붙어있는걸 보고 고개를 갸우뚱한적이 있는지 모르겠다. 소스가 잘못된게 아니고 바로 익명클래스를 쓴것인데 쓰기가 간편해서 구조가 간단한 클래스나 GUI 환경의 이벤트 구문에서 상당히 많이 쓰이는 것이 익명클래스이니 어떻게 쓰는지 이번 시간을 통해 꼭 숙지하기 바란다.


클래스명$번호.class

익명클래스는 컴파일하면 내부클래스이므로 일단 $ 표시가 붙고 그 다음에 갯수에 따라 숫자가 위와같이 붙는다. 아직까지는 익명클래스에 대해 어떻게 쓰는지 불확실할 것이다. 상속을 받아 재정의하고 객체를 만들고 이런 과정을 축약시켜 놓은게 익명클래스라고 생각하면 된다. 익명클래스야말로 백문이 불여일견이다. 일단 보면 안다. 무엇을? 예제를 말이다. 여기를 누르고 예제를 공부해 보면 쉽게 이해가 될것이다. 예제를 응용하면서 연습 많이 해보기 바란다.^^

익명클래스안에 있는 f()
i'm g()
i'm f()
=====강이의 자바강좌=====
익명클래스안에 있는 h()
익명클래스안에 있는 i()

자바의 지역클래스(Local classes)

오늘은 자바의 비정적 내부클래스로 분류되는 지역클래스에 대해서 공부해 보기로 하겠다. 내부클래스에 대한 큰 그림을 그리지 못하는 이들은 "자바의 내부클래스"란 강좌를 통해 내부클래스에 대한 계보도를 확실히 머릿속에 인지시키도록 하기 바란다. 뿌리가 깊으면 기본이 탄탄해진다. 그럼 오늘도 달려보자~ 고고씽 ^^


class Outer
{
   ...
   void f( )
   {
      ...
      class Local
      {
         ...
      }
   } //f()
}

위와같이 지역클래스는 지역변수처럼 메소드 내부에 정의되는 클래스를 말한다. 따라서 지역변수처럼 메소드 내부에서만 사용가능하고 메소드의 실행이 끝나면 지역클래스의 생명(?)도 끝난다. 지역클래스도 또한 해당 메소드 내부에서 객체를 만들어서 사용할수 있고 이 역시 메소드의 실행이 끝나면 자동으로 꼴까닥(?)이다.ㅎㅎ 한마디로 지역클래스의 수명은 그를 감싸고 있는 메소드의 운명과 함께 한다고 할수 있겠다. 그런 까닭에 지역클래스가 있는 메소드의 변수는 final 로 지정하여 메소드가 닫히더라도 값을 참조할수 있도록 해야 지역클래스에서 쓸수 있다.

저번 강좌를 열공했다면 기억하겠지만 내부클래스에서 컴파일을 하면 파일이름에 $표시가 붙는데 지역클래스는 $표시와 함께 숫자도 같이 붙는다.

Outer$1Local.class //Outer클래스안에 Local이라는 지역클래스가 있다.
Outer$2Local.class //Outer클래스안에 Local이라는 지역클래스가 또 있다.
Outer$1Local$1LocalH.class //LocalH라는 클래스가 지역클래스안에 또 있다.

자바의 지역클래스는 어떤 식으로 실행파일이 만들어지는지 이제 감(?)을 잡았으리라 믿는다. 위의 파일 샘플은 본 강좌의 예제를 컴파일하면 얻게 된다. 지역클래스안에 또 지역클래스를 만들 생각은 해보았는가? 자바는 여러분의 생각대로 이루어진다. 여기를 누르고 예제를 통해 심화학습하기 바란다. 저번 시간에 내부클래스의 개념정리를 어느정도 확실히 했다면 지역클래스는 그 연장선에 있다고 볼수 있기 때문에 그리 어려운 점은 없으리라고 본다. 지역클래스도 내부클래스이고 더군다나 메소드안에 위치하므로 변수가 미치는 영역의 범위를 잘 이해하면 그리 어려울건 없을 것이다. 허나 잘 몰라도 된다. 그냥 예제를 보면 해결될테니까 말이다.ㅎㅎ

나는 Outer안에 있는 f() 메소드다
지역클래스안에 있는 h() 메소드다
1
2
11
22
111
222
1111
2222
지역클래스안에 h()안에 있는 LocalH 클래스다
1
2
11
22
111
222
1111
2222
11111
22222
111111
222222
=====강이의 자바강좌=====
나는 Outer안에 있는 g() 메소드다
지역클래스안에 있는 i() 메소드다
120
100
20
200
1
2
130

결과값을 보면 예제를 펼쳐보기도 전에 공포(?)에 휩싸이는 이가 있을지 모르겠지만 빠른 캐치를 위해 출력값이 많을뿐이니 내용만 보고 예제는 빠뜨리는 게으른 이(?)가 없기를 바라면서 오늘 강좌는 여기서 끝내겠다. 다음 강좌도 내부클래스를 다룰 예정이다. 힘들더라도 조금만 참아라~ ㅎㅎ

자바의 비정적 내부클래스(Inner classes)


오늘은 중첩클래스의 가장 일반적인 형태인 자바의 비정적 내부클래스(Inner classes)에 대해서 살펴보기로 하자. 용어부터 정리하자면,

비정적 내부클래스
=Non-static nested classes
=Inner classes
=일반 내부클래스
=멤버클래스

다 같은 말이고 보통 내부클래스를 얘기할때 비정적 내부클래스를 칭한다. 멤버랑 같은 위치에 있다고 하여 멤버클래스라고도 불리는 비정적 내부클래스(여기서는 그냥 내부클래스라고 표기하겠다)를 자바에서는 아래처럼 작성하는데 이해를 돕기 위해 예제를 보자.

class Outer
{
      ...
      class Inner
      {
         ...
      }
}

내부클래스인 Inner 클래스는 외부클래스인 Outer의 멤버들과 같은 위치에 존재하며 외부클래스의 메소드와 필드 모든 요소들에 대해서 외부클래스처럼 바로 접근해서 쓸수 있다. 내부클래스도 클래스이므로 public이나 private 같은 접근지정자를 쓸수 있다. 내부클래스는 객체를 생성하는 방법이 다르니 지금부터 집중하기 바란다. 말그대로 내부클래스는 외부클래스의 내부에 있으므로 혼자서 독단적(?)으로 객체생성이 불가능하고 외부클래스의 객체를 이용해서 내부클래스의 객체를 생성할수 있다.

Outer.Inner oi = new Outer( ).new Inner( );

Outer o = new Outer( );
Outer.Inner oi = o.new Inner( );

내부클래스의 객체를 생성하는 방식인데 두가지 중 아무거나 써도 되지만 나중에 힘을 조금이라도 아낄려면 후자를 쓰는게 좋다. 첫번째 방식은 내부클래스 요소에만 쓸수 있는 객체이나 두번째 방식은 외부랑 내부 객체 모두를 만들어놨으므로 전자와는 다르게 외부 객체가 필요할때도 다시 만들 필요없이 사용가능하니 일석이조가 아니겠는가?

자바는 각 클래스마다 컴파일후에 .class라는 파일을 생성한다. 그럼 내부클래스는 어떻게 될까? 위의 클래스 이름을 예로 들자면 외부에 Outer 클래스가 내부에는 Inner 클래스가 위치하고 있다면 컴파일후 생성되는 파일은 Outer$Inner.class 로 된다. 따라서 생성된 자바파일을 볼때 달러표시가 있으면 내부클래스를 썼구나라고 짐작할수 있다.

그럼 내부클래스(Inner)안에 또 내부클래스(InnerInner)를 만들수 있을까? 직접 해보자.ㅎㅎ 가능하다. 그럴경우 생성되는 파일은 어떻게 될까? Outer$Inner$InnerInner.class 로 생성된다. 어떤 식으로 파일이 생성되는지 이해가 이제 될것이다. 그렇다면 Outer 클래스안에 내부클래스 Inner2 라는게 하나 더 있다면? 당연히 Outer$Inner2.class 가 만들어진다. 이제 우리는 자바의 컴파일된 파일이름만 봐도 내부클래스를 만들었는지 식별할수 있는 고수(?)가 되었다.^^

내부클래스에서는 클래스 필드에 들어가는 상수(static final)를 제외하곤 static을 붙여 멤버들을 사용할수가 없다. 이는 외부클래스의 객체가 있어야만 내부클래스의 멤버를 참조할수 있고 다시 말해서 내부클래스는 자신을 둘러싸고 있는 클래스가 인스턴스화가 되어야 사용가능하므로 위치상 static을 사용하지 못한다. static에 관한 자세한 내용은 저번 강좌를 다시 보기 바란다.

내부클래스는 멤버클래스라고 불리는 것처럼 외부클래스의 멤버들을 자유롭게 불러다 쓸수 있는데 그렇다면 외부클래스는 내부클래스 멤버들을 불러다 쓸수 있을까? 당연히 다른 곳에서 쓰는 것처럼 쓰면 된다. 예제를 보면 알게 될 것이다.

자바에서 같은 변수이름을 구분하기 위해 쓰는 this라는 용법이 있었다. 전역변수와 지역변수를 구분하기 위해서 말이다. 그럼 외부클래스와 내부클래스도 이름이 같은 변수를 쓸수 있을까? 만약 변수 이름이 같다면 어떻게 구별해서 쓸수가 있을까? 내부클래스도 클래스이니 다 가능하고 역시 this 로 구분이 가능하다. 쓰는 방식(외부객체명.this.변수명;)만 조금 알면 된다. 역시 예제를 보면 알게 될 것이다. 예제에 대한 관심을 증폭시키고 있는 중이다. ㅎㅎ

이제 예제를 볼텐데 내부클래스도 일반 클래스와 똑같은 클래스가 안에 하나 더 있다고 생각하기 바란다. 다른게 아니다. 똑같은데 내부클래스이므로 일반 클래스에 비해 위치의 제약이 있으므로 쓰는 방식의 차이가 조금 있을 뿐이다. 주석을 자세히 달았으니 이해하는데 큰 어려움은 없을 것이다. 여기를 누르고 다운받아 실행하기전 결과를 생각해보는 시간을 갖기를 간절히(?) 바란다.^^

I'm 내부클래스 h()
100
600
700
=====강이의 자바강좌=====
I'm 내부클래스 h()
300
I'm 내부클래스 h()
I'm 외부클래스 f()
I'm 외부클래스 static g()

예제의 결과를 출력해 보았다. 오늘 강좌의 내용을 예제에 다 담았다고 해도 과언이 아닐 것이다. 내용을 모르고 예제를 보기만 해도 이해가 퍽퍽 가도록 만들었다. ㅎㅎ 예제를 통해서 당연히 작동됨에도 불구하고 내부클래스에 대한 편견때문에 미처 생각지 못한 기능들을 예제를 통해 재발견(?)하는 시간을 갖게 될것이다. 자 홍보(?)는 충분히 한듯하니 예제보고 마무리 잘하기 바란다.^^

2011년 5월 9일 월요일

자바의 내부클래스(Nested Classes)

class OuterClass
{
    ...
    class NestedClass
    {
        ...
    }
}

위와같이 클래스 안에 클래스가 있는 자바의 중첩클래스(Nested Classes), 즉 내부클래스는 크게 두가지로 나뉜다. static이 선언된 내부클래스와 static이 선언되지 않은 내부클래스로 나뉘고 static이 선언되지 않은 일반 내부클래스는 다시 지역변수와 같이 메소드 내부에 존재하는 지역클래스와 특정 메소드를 오버라이드하여 재정의하는 이름없는 익명클래스로 나뉘므로 중첩클래스는 이렇게 총 4개의 내부클래스로 구성되어 있다. 아래의 설명과 도표를 보면 이해가 쉬울 것이다.

중첩(내부) 클래스(NESTED CLASSES)
일반(비정적) 내부클래스(Non-static nested classes(Inner classes))
   - 지역 (내부) 클래스(Local (Inner) classes)
   - 익명 (내부) 클래스(Anonymous (Inner) classes)
정적 내부클래스(Static nested classes)

탑레벨 클래스(Top Level Classes)는 우리가 일반적으로 지금까지 써온 클래스를 말한다. 내부클래스와 이름이 혼동되기 때문에 그렇게 표현하는 것일뿐 어렵게 생각할 필요는 없다. 클래스중에 가장 바깥에 즉 가장 최상위에 위치하므로 탑레벨 클래스라 한다. 중첩클래스 아니 이제부터는 전체를 지칭할때 내부클래스라고 표현하겠다. 아무래도 의미가 더 잘 통하니까 말이다. 내부클래스는 말그대로 클래스안에 또 클래스가 있는 형태를 말한다. 이런게 될지 예전부터 궁금했을 것이다. 아닌가?^^ 만약 궁금해서 독자중에 실험삼아 내부클래스를 만들어본 경험이 있다면 프로그래머의 기본적인 창의력(?)은 충분하다.ㅎㅎ

이제 슬슬 또다른 궁금증이 생길 것이다. 내부클래스를 언제 써먹어야되는지 말이다. 일반적으로 내부클래스는 탑레벨 클래스와 단단하게 결합되고 얽혀있는 클래스를 사용한다. 예를 들어 차가 종류대로 클래스 형태로 있다고 치자. 클래스로 A자동차, B자동차, C자동차...D트럭, E트럭, F트럭...G버스, H버스, I버스등이 있다면 너무 복잡하지 않은가? 이럴경우 Car 클래스, Truck 클래스, Bus 클래스로 만들고 내부에다가 A, B, C자동차 클래스를 Car 클래스에 D, E, F트럭 클래스를 Truck 클래스에 그리고 G, H, I버스 클래스를 Bus 클래스안에 넣어 내부클래스 형태로 만들면 보기 좋을뿐만 아니라 한곳에 있어 읽기 편하고 여러 클래스들이 논리에 맞게 한군데 모여있으니 유지보수도 쉬울 것이다. 또한 내부클래스는 자동으로 은닉성(Encapsulation)이 강화되니 불필요한 접근을 제한함으로서 보안성을 높이는 효과도 볼수 있다.

자바의 내부클래스는 종류도 여러가지고 표현도 다양한지라 자바를 공부한 이들도 어려워하는 단원중에 하나다. 하지만 자바를 공부하는 이상 내부클래스를 반드시 자기것으로 만들어야 한다. 좀 위협하자면 내부클래스를 모르면 앙꼬없는 찐빵을 들고있는 것이나 다를바 없다. 특히 대부분이 자바소스를 보면서 이해하지 못하는 대표적인 이유가 바로 내부클래스에 대해서 잘 알지 못하기 때문이다. 여러분이 본 강좌를 통해 내부클래스에 대한 내용을 숙지하게 되면 아마 그동안 여러분의 눈앞에 가려져있던 큰 장막이 걷히고 오랫동안 난해하게 느꼈던 많은 부분들이 새롭게 보이기 시작할 것이다.ㅎㅎ

내부클래스에 대해서 이 장에 전부 요약정리하려고 했으나 자바에서 많이 쓰는 방식인데 한꺼번에 했다가 하나도 제대로 기억못하는 최악의 사태(?)가 도래하는걸 방지하고자 각각의 내부클래스에 대한 내용을 총 4번에 걸쳐 강좌를 진행할 예정이니 오늘 내용 짧다고 기뻐말고 다음 시간까지 내부클래스 종류만이라도 확실히 암기해 놓도록 하기 바란다.^^

자바의 정적변수(static variables)와 정적메소드(static methods)


오늘은 static 키워드를 쓰는 자바의 정적변수(Class Variables)와 정적메소드(Class Methods)에 대해서 알아보기로 하자. 자바를 조금 배운 이들이라면 아마 이 단어만 나와도 벌써부터 고개를 설레설레 흔든다던지 잠시 이 장은 그냥 건너뛰고 나중에 보자고 미룰지도 모르겠다. 허나 오늘만큼은 특히 그러는 이가 없기를 바란다. 나중으로 미루는 이들은 먼훗날 두고두고 땅을 치며 후회하는 일이 있을지도 모른다. 왜 조금더 일찍 이 강좌를 보지 않았느냐고 말이다.ㅎㅎ static은 우리가 맨처음(메인메소드?) 접하는 키워드 중에 하나인데도 불구하고 오랜 시간동안 자바를 쓰면서도 생소하고 낯설게 느끼는 이들이 많은데 그 쓰임이 아주 단순하지만은 않기 때문일 것이다. 객체생성없이 접근할수 있게 만드는 명령어가 static이라 메인메소드 앞에 항상 static이란 키워드를 붙여온 것인데 자세한 이유는 오늘 강좌를 공부하면 자연히 알게 될것이다.^^

자바의 static 키워드를 통해 전체적인 프로그램의 구성에 대해서 여러분은 다시 한번 생각하는 기회를 갖게 될것이다. 필자는 모든 강좌의 설명을 요약식이 아닌 서술식으로 풀이하고 있는데 이런 방식을 택한 까닭은 내용이 같을지라도 다양하고 다채로운 표현을 통해 여러분의 사고와 이해를 높이기 위한 것이니 여기에 딴지거는 이(?)가 없길 바란다. 특히 이번 강좌는 static이란 단어부터가 심상치 않으므로 얼마나 내용이 길어질지는 장담할수가 없다. 불만에 대비해 미리 방어벽치는 중이다.ㅎㅎ 일단 static이란 단어를 지칭하는 방법이 여러가지라 더 혼란을 가중시키고 있지 않냐는게 필자의 생각이다. 시작하기에 앞서 자바의 정적변수와 정적메소드에 대한 용어들부터 살펴보자.

정적 변수
=static variables(static fields)
=class variables
=클래스 변수
예제)   static int a;

정적 메소드
=static methods
=class methods
=클래스 메소드
예제)   static void move( ) {...}

정적 변수와 정적 메소드를 통틀어서 Static Members 라고 하기도 한다. 이렇게 정리하고 보니 우리가 흔히 쓰는 일반 변수와 일반 메소드도 여러가지 다른 용어로 많이 쓰이는데 말나온김에 비교할겸 적어보기로 하겠다.

일반 변수
=instance variables
=인스턴스 변수
=멤버 변수
=필드 변수
예제)   int a;

일반 메소드
=instance methods
=인스턴스 메소드
=멤버 메소드
=필드 메소드
예제)   void move( ) {...}

위의 단어들을 몰라서 애를 먹는걸 방지하고자 한번 적어봤다. 필자는 문맥의 통일성을 위해 분류별로 가장 상위에 있는 용어(정적변수,정적메소드,일반변수,일반메소드)만 쓰기로 하겠다. 아무래도 서로 계속 비교를 해가면서 설명을 해야될듯한 묘한 예감(?)이 들기에 오늘 강좌는 용어만 통일해서 설명해도 여러분이 강좌를 이해하는데 많은 시간과 에너지를 절약하게 되리라 본다.^^

정적변수와 정적메소드라고 하니 아주 거창한걸 기대(?)했을지 모르겠지만 정적변수와 정적메소드라 함은 위와 같이 일반변수와 일반메소드 앞에 static 키워드를 써놓은 것을 말한다. 정적변수의 경우 객체를 아무리 많이 만들어도 해당변수는 하나만 존재한다. 일반 변수의 경우 객체가 생성될때마다 새롭게 생성되어 쓰이나 정적변수는 이와는 다르게 처음에 한번만 생성되고 공유해서 사용한다. static이란 클래스가 로딩될때 메모리 공간을 할당하는데 이 결정된 공간이 변하지 않으므로 정적이라는 표현을 쓴다. 메모리에 로딩 즉 메모리 공간을 확보해야 해당멤버를 쓸수가 있는데 자바는 프로그램을 실행하면 클래스가 우선 메모리에 로딩되나 static은 이보다 먼저 메모리에 로딩되어진다. 일반변수는 객체가 생성될때마다 메모리 공간이 할당되나 static의 경우 클래스가 메모리에 로딩되기전 이미 정적변수와 정적메소드를 위한 메모리 공간이 할당되므로 객체가 생성될때마다 메모리 공간이 할당되지 않는다.

이런 까닭에 static에 대한 장점을 크게 두가지로 나눌수 있다. 첫째로, static을 쓴 변수나 메소드는 객체생성없이 모든 객체가 아무런 제약없이 공유할수 있다. 물론 객체생성하고 써도 상관없다. 둘째로, static을 쓴 변수는 값을 공유하므로 연속적으로 그 값의 흐름을 이어갈수 있다는 것이다. 다시 말해서 일반변수는 객체생성시마다 그 값이 초기화되지만 정적변수는 객체생성을 하지 않으므로 다른 객체에서 계속적으로 이어서 그 값을 변화시킬수 있는 것이다. 이에 대한 부분은 예제를 보면 쉬울 것이다. 앞전에 배운 final이란 키워드를 함께 사용하면(static final double PI = 3.141592653589793;) 공유는 하면서 값은 고정시킬수 있는데 보통 상수가 이에 해당한다. 말나온김에 잠깐 상수에 대해서 언급하고 계속 진행하겠다. 자바에서 상수(Constant Value)를 쓸때 보통 아래처럼 static과 final을 함께 사용한다. 상수는 항상 변하지 않는 고유한 속성을 지닌 멤버니까 말이다.

(접근지정자) static final 자료형 상수명=상수값;
public static final double PI = 3.141592653589793;
static final double PI = 3.141592653589793;

상수의 경우 편리성을 위해 예제(PI)처럼 이름을 소문자가 아닌 대문자로 표기하는데 한단어가 아닌 여러단어로 이루어질 경우 전부 대문자이면 분간이 어려우므로 단어 사이마다 _(underscore)로 연결시키는게 관례다.

다시 돌아와서 보충설명을 이어서 해보도록 하겠다. 정적변수나 정적메소드를 쓰는 예제는 아래와 같다.

클래스명.a=100;   //객체생성없이 클래스명으로 정적변수에 접근가능
객체명.a=100;   //물론 객체생성후 얻은 인자로도 정적변수에 접근가능

클래스명.move( );   //객체생성없이 클래스명으로 정적메소드에 접근가능
객체명.move( );   //물론 객체생성후 얻은 인자로도 정적메소드에 접근가능

정적변수나 정적메소드를 호출하는 방법은 위와 같으며 같은 클래스내에서는 클래스명을 생략하고 써도 무방하다. 일반메소드 안에서는 객체생성없이 정적변수나 정적메소드를 호출할수 있으나 static을 쓰는 정적메소드 안에서는 객체생성없이 일반변수나 일반메소드를 호출할수가 없다. 이유는 이미 위에서 메모리에 관련해 설명한 것처럼 static멤버들이 먼저 로딩되므로 아직 만들어지지않은 객체의 변수나 메소드를 참조해서 쓸수는 없기 때문이다. 따라서 this라는 키워드도 정적메소드안에서는 사용이 불가능하며 메소드안에서 쓰이는 지역변수에도 사용할수 없다. 반대로 당연하지만 일반메소드안에서는 정적변수나 정적메소드를 쓸수가 있다. 이미 로딩된 static멤버들을 쓰지 못할 이유가 없기 때문이다.

우리가 저번 시간에 abstract 추상메소드에 대해서 공부한바 있다. 여기에는 static을 쓸수가 있을까? 당연히 불가능하다. static은 객체생성 없이도 호출이 가능한 키워드인데 내용이 없으면 안된다. 우리가 예전에 배운 초기화 블록(클래스안에 {...} 괄호만 있는 형태)을 기억하는가? static도 초기화 블록처럼 정적변수와 정적메소드를 초기화시킬수 있는 공간을 클래스안 어느 위치에서나 갯수에 구애받지않고 아래와 같이 만들수 있는데 이를 정적 초기화 블록(Static Initialization Blocks)이라고 부른다. 정적초기화블록은 클래스 로딩될때 자동으로 작동되며 초기화 블록에다가 아래처럼 static만 첨가한 형태이다.

static
{
... //정적변수나 정적메소드만 쓸수 있다.
...... //초기화시키고 싶은 내용 아무거나 쓰면 된다.
System.out.println("static {...}은 쓰는데 갯수 제한없다");
}

static 키워드가 어떻게 쓰이는지 알아보았는데 아직도 잘 모르겠다하는 이들이 있을지도 모르겠다. 괜찮다. ^^ 그런 이들을 위해 준비했다. 아주 자세하게 그리고 촘촘하게 예제를 만들었다. 여기를 누르고 다운받아 실행하기전에 본인이 생각한 결과를 먼저 적어보고 맞춰보기 바란다. 주석을 아주 풍부하게 실어놨으므로 본 강좌에서 이해못한 부분이 있더라도 자연스럽게 예제를 통해서 이해가 될것이다. static은 이곳저곳에서 많이 쓰이므로 코드 해석을 위해서라도 반드시 쓰는 용법을 알아두어야 한다. 예제의 결과는 아래와 같다.

moveA 메소드입니다
처음에 위치한 static{ }입니다
moveA 메소드입니다
끝에 위치한 static{ }입니다
10
10
moveA 메소드입니다
moveA 메소드입니다
12
12
=====1=====
10
moveB 메소드입니다
11
=====2=====
12
12
0
0
=====3=====
111
111
222
0

잠시 예제에 대한 간략한 설명을 하자면 static 블록이 어디에 위치하든 가장 먼저 실행됨을 알수있다. 그리고 맨밑에 3번 분리선후에 출력되는 결과값은 객체생성에 관련된 정적변수와 일반변수간의 어떤 차이가 있는지 비교가능하게 되어있다. 예제에서 정적변수a는 각 인스턴스마다 값(111)을 공유하므로 출력결과가 같으나 일반변수b는 각 인스턴스마다 새롭게 b를 생성하므로 값이 다름을 알수있다.

위에서 공부한바와 같이 static을 쓰면 객체생성을 하지 않아도 되니 여러모로 편리하고 메소드를 호출하는 시간이 짧아져 효율적이고 속도도 빠르다. 이런 장점이 있기 때문에 메소드안에서 일반변수를 사용하지 않아도 되는 경우나 인자가 공통된 값을 유지할 필요가 있다면 static을 붙이는 것을 고려해볼 필요가 있다. 이렇게 좋은 점이 있긴하나 그렇다고 해서 모든걸 static으로 만들수는 없다. 클래스에서 일반변수중 모든 객체에서 공유하고 유지해야될 값이 없거나 일반변수나 일반메소드를 수시로 쓰는 상황이라면 굳이 static을 써서 가독성을 떨어뜨릴 필요는 없기 때문이다. 모든건 상황에 따라 대처해야하는데 그런건 개발자(?)의 역량에 달려있지 않겠나 생각된다. 힘들겠지만 잠시 쉬었다가 복습하는 착한 프로그래머(?)가 되길 바라면서 오늘은 이만 마치겠다. ^^

2011년 5월 7일 토요일

자바의 추상클래스(abstract class)와 인터페이스(interface)


자바의 상속 개념을 공부할때 빠지지 않고 등장하는 것이 바로 오늘 배울 추상클래스(abstract class)와 인터페이스(interface)다. 무슨 공통점이 있는지 무슨 차이점이 있는지 말도 애매모호하고 서로 비슷비슷하므로 이 부분만 나오면 한숨이 나오는 이들이 많을 것이다. 오늘 강좌를 통해서 언제나 그랬듯이 여러분의 막힌 속을 뻥 뚫리도록 상세한 설명과 함께 본 내용을 진행하겠다.*

추상클래스를 설명하자면 없거나 하나 이상의 추상메소드를 가지고 있는 것이 추상클래스다. 그렇다면 추상메소드는 무엇인가? 추상메소드는 안이 아직 구현되어 있지 않은 abstract로 정의된 메소드를 말한다. 클래스안의 메소드가 단 한개라도 추상메소드가 있다면 그 클래스 앞에는 반드시 abstract 클래스명으로 표기되어야 하며 abstract와 final 키워드를 동시에 표기할수 없다. 추상클래스는 일반적인 메소드도 있을수 있고 추상메소드가 있을수도 있다. 추상클래스는 일반변수들을 가질수 있다. 추상클래스도 인터페이스처럼 추상클래스가 아닌 클래스에서 상속을 받는다면 추상메소드가 있을경우 모두 구현해주어야 한다. 물론 추상클래스에서 추상클래스를 상속받는다면 모두 구현하지 않아도 된다. 추상클래스에서도 인터페이스를 구현할수 있는데 이때는 구현하지 않고 그냥 놔둘수 있다. 추상클래스는 생성자를 가질수 있다. 추상클래스는 인스턴스를 만들수 없지만 추상클래스를 상속받은 클래스를 통하면 인스턴스화가 가능하다. 예를 들자면 "추상클래스명 ab = new 클래스명( )" 이런 형태가 될것이다. 추상클래스의 접근지정자는 어떤 것이나 가능하다. 추상클래스의 궁극적인 목적은 상속하기 위함이다. 어떤 클래스가 추상클래스를 상속받을때는 extends를 쓴다.^^

인터페이스는 안이 비어있는 메소드들의 형태들만 써놓은 것이며 상속하는 클래스들에서 해당 메소드들의 내용을 구현해서 가져야하는 메소드들의 집합이라 할수있다. 따라서 인터페이스에 새로운 메소드를 추가한다고 하고 그 아래에 인터페이스로부터 상속되는 클래스가 있다면 새로운 메소드에 대한 내용을 반드시 구현해야한다. 인터페이스안의 모든 메소드들은 추상메소드이다. 인터페이스는 final을 붙일수 없고 인터페이스 변수들은 static이어야만 한다. 한마디로 인터페이스는 일반변수들을 가질수 없다. 인터페이스는 하나 이상의 인터페이스들을 상속할수 있는데 여러개일때는 콤마(,)를 사용하며 이때는 클래스에서 상속받는게 아니라 인터페이스에서 상속받으니 즉 내용이 없는 메소드들을 그대로 두는것이니 구현이 아니라 인터페이스간의 상속이므로 일반 클래스끼리 상속할때처럼 extends를 쓴다. 인터페이스는 생성자를 가질수 없다. 인터페이스는 인스턴스를 만들수 없지만 인터페이스를 구현한 클래스를 통하면 인스턴스화가 가능하다. 예를 들자면 "인터페이스명 ab = new 클래스명( )" 이런 형태가 될것이다. 인터페이스의 접근지정자는 아예 없거나 public이거나 아님 abstract만 가능하다. 어떤 클래스가 인터페이스를 상속받을때는(구현할때는) implements를 쓴다. 물론 클래스가 다른 클래스를 상속하면서 인터페이스를 구현하는 것도 가능하다. 그럴때는 extends 쓰고 implements를 순서대로 쓰면 된다.^^

상반되는 내용을 많이 비교하였으므로 이 정도면 충분히 무엇이 다른지 알았을거라 본다.ㅎㅎ 조금더 덧붙이자면 추상클래스는 인터페이스보다 속도가 빠르다. 왜냐하면 인터페이스는 관련 메소드들을 찾기 위해 부가적인 일들을 더 처리하기 때문에 그러하다. 추상클래스는 "일반변수(가능)+일반메소드(가능)+추상메소드" 형태이고 인터페이스는 "상수+추상메소드"만 가능하고 일반변수나 일반메소드를 쓰는 것은 불가능하다. 추상메소드가 뭐냐면 아직 구현이 되지않은 다시 말해서 아직 "구"체적으로 표"현"이 되지않은 메소드를 말한다. 이걸 코드상으로 설명하자면 메소드의 제목(리턴타입,메소드명,매개변수)은 있는데 { }안에 내용이 없는 형태이고 물론 { } 이런 괄호도 없는 형태를 말한다. 끝에 세미콜론 붙이는거 까먹지 않아야하고 말이다. 그럼 어떻게 코드로 작성하는지 알아보기로 하자.^^

abstract class Shape
{
   abstract void draw( );   //메소드 앞에 abstract가 있으면 추상메소드다.
   void hit(int x)
   {
      x=3;
   }
}

interface Shape
{
   void draw( );   //인터페이스에서는 abstract 생략가능하다.
   abstract void move(int y);
}

추상클래스는 메소드를 하나라도 abstract로 만들었다면 반드시 클래스명 앞에다 abstract로 정의해야된다. 인터페이스의 모든 변수는 상수(static final)로 모든 메소드는 추상메소드(abstract)로 정의해야된다는 점이 추상클래스와 다르다. 따라서 인터페이스의 경우 변수 앞에 static final이나 메소드앞에 abstract라는 명령어를 안써도 상수나 추상메소드로 자동인식하므로 안써도 상관없으나 빠른 가독성을 위해 생략가능한 부호는 언제나 쓰는 습관을 기르기 바란다. 추상메소드를 코드로 쓸때는 몸통이 없으니 당연히{ } 괄호가 없고 끝에 세미콜론(;)을 붙여야한다. 더불어 인터페이스는 클래스처럼 일반메소드는 쓰지 못하고 모든 메소드가 몸통없이 껍데기만 있는 다른 형태이므로 class 대신에 interface라고 처음에 표기하고 인터페이스 이름을 만든다. 추상클래스와 인터페이스는 (new를 통한) 객체 생성을 하지 못하게 되어있는데 객체생성후에 메소드를 호출하려고 해도 내용이 없어 이용할수 없으므로 생성이 불가능하도록 되어있다. 물론 추상클래스나 인터페이스를 상속받은 자식 클래스에서 해당멤버들이 구현이 되었다면 객체생성이 가능하다. 키워드가 각각 abstract와 interface인데 당연하지만 전부 소문자로 써야 작동되니 클래스나 메소드 앞에 붙일때 첫글자를 대문자로 쓰는 일은 없길 바란다.-;

예제처럼 도형을 그리는 프로그램을 만든다고 치자. 삼각형을 만들때도 그려야하고 사각형을 만들때도 그려야하니 예제와 같이 draw( ) 메소드가 필요하겠지만 도형종류가 다르니 그리는 방식도 다를 것이다. 그리고 상위 클래스인 도형은 그릴수가 없다. 도형이 무슨 모양인지 결정이 안되었으니까 말이다. 이런 상황이 추상화(그림아님--)의 대표적인 예가 아닐까 싶다. 도형을 그리는 프로그램이니 위처럼 draw( ) 메소드를 만든 것이나 도형 종류마다 그리는 방법이 다르니 추상메소드로 선언만 해놓고 안의 내용은 상황에 맞게 자식 클래스에서 알아서 다시 구현해 쓰라고 선언만 해놓고 구현을 기다리는 방식(오버라이드해서 재정의)이 바로 추상클래스와 인터페이스다.

추상메소드만 선언할꺼면 인터페이스를, 다른 일반 메소드나 필드도 필요하면 추상클래스를 쓰면 된다. 어렵게 생각하지 말자. 인터페이스는 한눈에 보면 다 빈껍데기 뿐이기 때문에 어떤 것을 구현해야되는지 한눈에 몽땅 들어오므로 쓰는 빈도가 높고 추상클래스는 필요에 의해서 일반 메소드와 더불어서 추상화 기능을 가미할때 쓴다는 정도로만 이해하면 될듯 싶다.

추상클래스나 인터페이스가 하위 클래스에서 구현을 하려면 상속을 받아야하는데 방식이 다르다. 위의 예제에서 쓰인 Shape 추상클래스(abstract class Shape)와 Shape 인터페이스(interface Shape)가 각각 어떤 방법으로 상속되는지 살펴보기 바란다. 노파심(?)에 한마디 하자면 둘다 같은 추상(껍데기) 메소드를 넘겨받아 안에 내용을 채우는 것이므로 당연히 오버라이드(리턴값,메소드명,입력값 동일한 메소드)해야 된다

class Triangle extends Shape   //abstract클래스는 상속처럼 extends를 쓴다.
{
   void draw( )   //이렇게 일반 메소드 형태로 구현해줘야 한다.
   {
      System.out.println("삼각형을 그린다");
   }
...   //hit( )는 필요시 재정의하고 아니면 상속이니 있는걸로 간주하면 된다.
}

class Triangle implements Shape   //interface는 implements로 상속을 받는다.
{
   public void draw( )   //이렇게 일반 메소드 형태로 전부 구현해줘야 한다.
   {
      System.out.println("삼각형을 그린다");
   }
   public void move(int y)   //접근 지정자를 완화시켜 public으로 구현해준다.
   {
      System.out.println("삼각형을 이동시킨다");      
   }
}

추상클래스던 인터페이스던간에 상속받기로 하였으면 안에 있는 추상메소드는 예제처럼 전부 구현해 주어야한다. 추상클래스는 인터페이스와 달리 클래스이므로 extends로 상속하고 다중상속은 안되지만 추상클래스(abstract인 자식클래스)가 추상클래스(abstract인 부모클래스)를 상속하는 것은 가능하다. 이 경우에는 자식 클래스도 역시 추상클래스라고 알려주어야 하므로 abstract를 class 앞에 표기해야되고 추상 자식클래스이니 상속받았다 할지라도 추상메소드를 꼭 구현할 필요는 없고 다른 추상메소드를 만들어도 상관없다.

abstract class Triangle extends Shape
//추상 abstract와 상속 extends가 함께 있으면 추상 자식클래스를 뜻한다.

인터페이스는 추상클래스와는 달리 클래스가 아니므로 자식 클래스가 상속을 받을때 implements라는 키워드를 쓴다. 예제에서 인터페이스 자식 클래스에서 추상메소드를 구현할때 접근지정자를 public 모드로 썼는데 이는 implements라는 키워드에서 느낄수 있듯이 어떤 상황에서든 해당 멤버들을 반드시 구현해야하므로 관련 메소드에 아무런 제약없이 접근할수 있어야하기 때문이다. 또한 인터페이스는 추상클래스나 일반클래스와는 달리 다중상속이 가능하다. 여러개의 인터페이스를 아래처럼 한꺼번에 상속받을수 있는데 콤마(,)찍고 계속해서 원하는만큼 상속받을 인터페이스 이름을 적어주고 상속받아서 해당멤버들을 인터페이스 자식클래스에서 구현하면 된다.

class Triangle implements Shape, 인터페이스이름, 인터페이스이름

추상클래스와 인터페이스를 어떤 상황에 쓰는지 굳이 구별하자면,
추상클래스는 같은 종류나 행동들을 구현할게 많을때 쓰고 당연하지만 추상메소드를 상속받는 자식클래스에서 구현할때 쓰고 상속에 대한 계층구조를 명확히 표현할때 추상클래스를 이용하는 것만큼 효과적인 방법은 없다하겠다. 추상클래스는 일반변수들과 일반메소드들도 쓸수 있고 아직 구현하지 않아도될 메소드는 그냥 내버려둘수 있어 상황에 따라 편리하다.
인터페이스는 디자인을 구성하는 요소들이 자주 바뀔때 쓰면 유용하고 당연하지만 메소드 형태만 서로 공유해서 구현되는 상황일때 적합하고 클래스 전체가 아닌 메소드들만 쓰고 싶을때 인터페이스를 이용하면 효과적이다. 좀더 깊이 들어가자면 인터페이스의 사용빈도가 상당히 높은데 그 이유중 하나가 동시개발이 가능하므로 시간을 많이 단축시킬수 있다는 점이다. 인터페이스안의 메소드들은 내용이 없는 상태이나 메소드에 대한 결과값은 내용을 만들지 않아도 미리 알수 있으므로 인터페이스의 내용을 누군가가 구현하고 있는 동안에 다른 한사람은 그 메소드가 구현되고 나면 작동할 결과값으로 같은 시간에 다른 일을 할수 있으니 개발작업이 한층더 빨라질수 있는 것이다. 또한 여러사람이 인터페이스를 통해 그런 작업을 동시에 한다고 가정했을때 인터페이스안의 메소드 내용을 변경하더라도 그와 관련된 모든 클래스들을 변경할 필요없이 해당 메소드의 구현되는 내용만 변경하면 모든 처리가 가능해지므로 일거양득(一擧兩得)이라 할수 있겠다.^^

추상클래스와 인터페이스는 여러분이 자바를 배우는 이상 심심치않게 만나는 부분이다. 추상클래스와 인터페이스의 정의만 확실히 알아도 그리 어렵진 않을 것이다. 이제껏 함께 공부한 예제들을 추상클래스나 인터페이스로 만들어 보면서 여러분의 생각대로 프로그램이 잘 돌아가는지 연습해 보기 바란다. 수고많았다. ^^

2011년 5월 6일 금요일

자바의 상속을 방지하는 명령어(final)

자바의 상속에 대해서 저번에 공부했었는데 final이란 키워드를 잠시 언급했었다. final이 어떨때 필요하고 어떻게 쓰이는지 알아보는 시간을 갖기로 하자. final 말그대로 마지막 최종 종결자(?)다. class 앞에 final이 붙으면 이 클래스는 상속할수 없게 된다. 메소드의 경우도 마찬가지로 앞에 final이 붙으면 이 메소드는 오버라이드 해서 재정의할수 없게 된다. 상속시 전부 똑같은 형태 즉 메소드 오버라이드해서 내용은 자기가 원하는 입맛대로 쓸수 있었지만 final이 붙었을 경우 이렇게 재정의해서 쓰는것이 불가능하다.


final class Parent
{
   final int total=0;

   final void pmethod( )
   {
       int a=0;
   }
}

class Child extends Parent  //final 때문에 상속불가능해서 에러남.
{
   int total=100; //final 때문에 값을 변경하지 못해서 에러남.

   void pmethod( )  //final 때문에 오버라이드 불가능해서 에러남.
   {
      int a=100;
   }
}

위의 예제에서 보듯이 설령 class 앞에 final이 없어서 상속까진 되었다고 하더라도 메소드에 final이 있으면 역시 에러난다. 변수에 붙어도 마찬가지로 final이 있으면 초기화된 그 값은 절대 변경할수 없다. 그렇다면 자바에서는 왜 이런 기능을 만들어 놓았을까?

여러분이 프로그램을 만들때 어떤 변수는 그 값을 변경하면 안되는 것이 있을수도 있고 혹은 보안을 위해서 어떤 메소드는 항상 똑같은 기능 이외에 다르게 쓰면 안되는 경우가 있을수 있다. 클래스에 final을 걸어놓으면 아예 상속이 불가능하여 원천적인 봉쇄가 가능해 완벽한 은닉이 가능해진다. 대표적인 final 클래스의 예로 우리가 jdk api에서 불러 쓰고 있는 System이나 String, 혹은 Math 클래스 같은 것들이 있다. 누군가가 System 클래스를 상속받아 재정의해 버린다면 제작자의 의도와는 다르게 치명적인 에러가 날수 있다. 이를 방지하기 위해 이런 중요한 클래스들은 자바에서 이미 final로 만들어 놓았다.

부분적으로도 가능한데 메소드의 예를 들자면, 뭔가 열리는 기능을 갖춘 open( ) 이라는 메소드를 만들었는데 누군가가 이를 오버라이드 해서 open( ) 메소드를 열리는게 아닌 뭔가 닫히는 기능으로 바꿔버린다면 전체 프로그램이 엉망이 될것이다. 이렇게 상속해서 공유해 쓰는것은 가능하지만 부분적으로 변경을 원하지 않는 경우 우리는 그 부분을 final로 만들면 이런 발생가능한 위험을 사전에 차단할수 있다.

또한 final은 프로그램의 성능 향상에 기여한다. 지금 단계에서 할말은 아닌듯 하지만 자바에서는 메소드가 하위 클래스에서(상속받은 클래스에서) 오버라이드 하는지 안하는지 검사를 한다. 그런데 메소드 앞에 final이 붙어있으면 자동으로 이런 검사를 안하게 되어 메소드 호출시 속도를 높여 성능향상에 기여하게 된다.

다시 정리하자면,
final이 붙은 클래스는 상속할수 없다.
final이 붙은 메소드는 오버라이드할수 없다.
final이 붙은 초기화된 변수(final int a=0;)는 값을 변경할수 없다.(a=1; 불가능)
final이 붙은 선언만된 변수(final int a;)는 값을 오직 한번만 넣을수 있다.
(a=1; 가능, 하지만 두번째부터는 a=0; 불가능)
오직 한번이라고 했으니 그 다음부터 1 이외의 다른 값으로 변경불가능하다.

자바의 상속에 대해서 공부하고 느끼겠지만 그에 대한 파장(?)이 얼마나 큰지 몸소 체험하고 있을 것이다. 아직도 갈길이 멀다. 그래서 오늘은 강좌를 본의아니게 짧게 마무리한다. 다음시간을 위해 에너지(?)를 비축해두기 바란다.^^

2011년 5월 5일 목요일

자바의 접근지정자(Access Modifiers)

오늘은 여러분이 자바 소스를 볼때 항상 골아프게 만드는 골치거리들을 공부하고 정리하는 시간을 갖고자 한다. 바로 자바의 접근 지정자(Access Level Modifiers)에 대해서 살펴보기로 하겠다. 저번에 자바의 상속 부분을 긴 예제를 통해 공부하느라 힘(?)이 다 빠졌을 것이니 오늘은 특별히 머리도 식힐겸 예제가 없이 필자의 능란한 화술(?)에 의존해서 옛날 이야기를 통해 본 강좌를 진행해볼까 한다. 그렇다고 아무 생각없이 멍(?) 때리면서 들어서는 안된다. 최소한의 성의(?)를 보이는 자만이 이번 강좌의 과실을 따먹을수 있을 것이라고 강력 선포한다.^^ 농담이다. 설렁설렁 들어도 다 알아먹게끔 만들어 주겠다.ㅎㅎ

자바의 접근 지정자는 아래처럼 멤버의 종류를 나타내는 기호 앞에 위치한다.
public class Parent {......}   //public의 예
protected void move( ) {......}   //protected의 예
int a;
//(no modifier)=(default)=(friendly) 모드는 평상시처럼 쓰지 않았을때 해당
private int a;   //private의 사용예

접근 지정자는 public, protected, (no modifier), private 이라고 불리는 것들로 접근 레벨이 네가지로 나뉘는데 지금 쓴 순서대로 접근 제한의 강도가 세진다. public은 뭐 말 그대로 제한없이 쓸수 있는거고 예제에서 봤겠지만 아직은 있으나 없으나 별 체감이 없을 것이다. 필자가 프로그램 짜면서 특별히 어떤 멤버들을 접근못하게 막은적이 없으니까 말이다. private은 말그대로 사적인거니까 자기자신 빼고는 다 접근못하게 막은거라 추론이 가능하다. 나머지는 중간에 있는걸로 봐서 뭐 중간 형태이지 않겠냐? ㅎㅎ 그럼 접근 지정자가 왜 필요한지 잠시 토픽(영어시험 아님-.-)에 관련된 이야기를 해보겠다.

해외에서 살고있는 한 가족의 이야기이다. 자바섬이라는 곳이 있는데 그곳에 "라이"라고 불리는 한 가장의 가족들이 살고 있었다. 그는 아내를 젊은 나이에 교통사고로 잃고 남겨진 아들 "테디"와 사고후 얼마안되어 그 사고를 잊지못하는 아들을 위해 그의 친구인 "폴"을 불러 외로움을 덜어주게 하고 몇년이 지난후 폴을 양아들로 받아들여 함께 살았다. 라이는 자바섬에서 다른 이들과는 달리 불굴의 노력과 의지로 드물게 성공하여 엄청난 부를 거머쥐었다. 라이는 모든 재산을 자신의 계좌에서 스스로 관리해왔는데 이제 아이들도 장성하여 필요한 일이 있을경우 쓸수있도록 본인의 계좌에 접근가능한 법인카드를 자신의 친아들 테디와 양아들 폴에게 주며 "같은 집에서 사는 이상 너희들은 똑같은 나의 자식들이다"라며 많은 부를 그들에게 물려주었다. 그러나 어느날 폴은 이제 자바섬이 지겨워졌다며 떠나려하자 라이는 떠날 경우 그동안 베풀었던 모든 것을 거둬들이겠다고 한다. 결국 폴은 그에 아랑곳 하지않은채 말없이 떠나고 라이는 떠남을 슬퍼하지만 약속은 약속인지라 계좌접근을 차단시키고 자식이라는 점은 변한게 없으니 침묵한다. 그러기가 수십년, 어느날 "빌리"라는 청년이 찾아와 자바섬을 살리기 위해선 라이의 도움이 필요하다고 요청하고 오랫동안 간청한 끝에 라이는 빌리의 의견을 결국 수락하여 사회에 자신의 전재산을 환원하여 모든 이가 라이의 기부(?) 덕분에 행복하게 살았다는 이야기다.*

출연진: 라이-private, 폴-(default=friendly), 테디-protected, 빌리-public
상속시 오버라이드를 통해 멤버들 재정의할때도 접근지정자를 위의 출연진 차례처럼 접근 강도를 완화시켜가는 것은 가능하지만 반대로 강화시키는건 불가능하다는 점을 기억하기 바란다.

접근 지정자는 이럴때 필요하다. 라이가 은행계좌를 가지고 있는데 당연히 자기자신만 그 계좌에 접근할수 있어야한다. 이런 의미로 쓰는게 private이다. 그런데 상황에 따라 누군가에게는 접근가능하게 만들어 주어야 할때가 있다. 다른 것들은 놔두고 양아들 폴처럼 자식이라도 다른데 있을때는 접근을 제한할때가 생긴다. 이런 의미로 해당하는 것이, 아무것도 쓰지 않았을때 자바에서 자동으로 작동하는 (no modifier) 디폴트에 해당한다. 그리고 남을 제외하고 테디나 폴처럼 관련 가족에게는 모두 접근할수 있도록 권한을 준다. 이런 의미로 쓰는게 protected이다.  마지막으로 빌리가 요청한 것처럼 어디서나 누구나 모든 이가 쓸수 있게 되면 그것은 public에 해당한다. 읽으면 읽을수록 참 훌륭한 필자의 창작 이야기다. ㅎㅎㅎ

그럼 이제는 접근 지정자들의 접근가능 범위를 요약해 보겠다.


1. (자기자신) 같은 클래스(class)에서 접근가능
2. 같은 package에 있는 자식(extends한) 클래스에서 접근가능
3. 같은 package에 있는 다른 클래스에서 접근가능
4. 다른 package에 있는 자식 클래스에서 접근가능
5. 다른 package에 있는 다른 클래스에서 접근가능

public: 1, 2, 3, 4, 5 (모두 가능)
public 명령어는 클래스, 인터페이스,
생성자, 내부 클래스, 메소드, 필드 변수들 이름 앞에 붙여 사용함.
사용예제:   public int a;

protected: 1, 2, 3, 4 (가능하고 나머진 불가능)
protected 명령어는
생성자, 내부 클래스, 메소드, 필드 변수들 이름 앞에 붙여 사용함.
사용예제:   protected int a;

(no modifier): 1, 2, 3 (가능하고 나머진 불가능)
default 디폴트 명령어는 말그대로 쓰지 않았을때 자동으로 인식한다.
자바코드 보다가 아무것도 안써있으면 다 이 모드라고 생각하면 된다.
사용예제:   void add( )
같은 패키지에서는 접근 가능하고 다른 패키지에서는 불가능하다고 하여
"package-private 패키지 프라이빗"이라고도 불린다.
생성자, 내부 클래스, 메소드, 필드 변수들 이름 앞에 아무것도 안붙이면 됨.
사용예제:   int a;

private: 1 (하나만 가능하고 나머진 불가능)
private 명령어는 클래스, 인터페이스,
생성자, 내부 클래스, 메소드, 필드 변수들 이름 앞에 붙여 사용함.
사용예제:   private int a;

여러분이 아직은 package 패키지란 것이 익숙하지 않을테니 그림을 통해 부가 설명을 하겠다. 강좌 초반에 import를 설명하면서 패키지에 대해서 약간 말한적이 있는데 기억날지 모르겠다. 디렉토리 같은 개념으로 생각하면 된다. 디렉토리마다 어떤 분류로 파일들을 모아놓지 않는가? 그것처럼 패키지마다 분류별로 자바의 클래스 파일들을 모아놓은 꾸러미라고 생각하면 이해가 쉬울 것이다. 보다 자세한 사항은 본 강좌의 import 관련 부분을 찾아서 보기 바란다.


패키지1 안에 Alpha 클래스와 Beta 클래스가 들어있다. 패키지2 안에는 AlphaSub 클래스와 Gamma 클래스가 들어있다. 이중 Alpha 클래스는 AlphaSub 클래스의 부모이다. 화살표는 extends한 클래스가 Alpha 즉 부모 클래스라는 것을 가리키고 있다. 이런 상황일때 Alpha 클래스 멤버들을 지금까지 배웠던 네가지 접근 지정자에 따라서 다른 클래스에서 접근이 가능한지 못한지를 알아보기 위한 그림 예제이다.

Alpha 클래스에서는 Alpha 클래스 멤버들이니 너무 당연하지만 private을 포함 어떤 접근 지정자로 해놓든 다 사용할수 있다. 그럼 Beta 클래스의 경우를 보자. Beta 클래스는 같은 패키지안에 있다. 이럴 경우 private만 빼고 어떤 접근 지정자가 쓰였더라도 관계없이 Alpha 클래스 해당멤버를 불러서 쓸수 있다. AlphaSub 클래스는 자식 클래스인데 다른 패키지안에 있다. 다른 패키지이면 설사 자식 클래스라 하더라도 (no modifier) 아무것도 앞에 없는 디폴트일 경우와 private일 경우 접근이 불가능하여 그 해당멤버를 쓸수가 없다. 마지막으로 Gamma 클래스는 Alpha 클래스와 패키지도 다르고 부모 자식인 상속 관계에 있지도 않다. 따라서 Alpha 클래스 멤버가 public 모드가 아니면 모두 접근이 불가능하다.

오케이? ㅎㅎ 자바의 접근 지정자에 대해서 알아보았다. public과 private은 극과 극이므로 비교적 알기 쉬운데 반해 protected와 (default)인 경우에는 어떤 곳에서 접근해 쓸수 있는지 잦은 혼란을 초래하기 때문에 많은 이들이 어려워한다. 다시 정리하자면 protected와 (default) 모드는 방금 그림에 있는 예처럼 다른 패키지에 있는 자식 클래스에서 접근이 가능한지 불가능한지로 구분할수 있다. 접근이 가능하면 모드가 protected 일때고 접근이 불가능하면 (default) 투명모드로 멤버를 만들어놨기 때문이다. 그래서 디폴트 모드를 자식 클래스라도 다른 패키지에 있으면 접근 못하니까 영어로 package-private 패키지 프라이빗이라고 부른다. 지금 보니 이 단락만 이해하면 끝이네.ㅎㅎ 그래도 위의 내용을 읽었으니까 지금 이 단락을 쉽게 이해했다고 생각하자. 아님 고달프다.-* 오늘은 여기까지다.^^

자바의 상속에서 상위 클래스를 지칭하는 명령어(super)

자바의 상속을 배우면서 꼭 알아야할 키워드가 바로 super 이다. 자식 클래스에서 객체 생성을 하면 자동으로 super( )를 이용해 자식 생성자를 실행하기전에 먼저 부모 생성자를 호출하고 실행한다. 따라서 super를 모르고서는 자바의 상속에 대해서 안다고 말하는 것이 어불성설인 것이다.


상위 즉 부모 클래스의 멤버들을 사용하기 원할때 super 라는 명령어를 쓰는데 쓰는 방법은 아래와 같다.

super.변수;                       //부모 클래스의 변수를 호출한다.
super( );                            //부모 클래스의 생성자를 호출한다.
super(매개변수);              //부모 클래스의 생성자(입력값)를 호출한다.
super.메소드( );                //부모 클래스의 메소드를 호출한다.
super.메소드(매개변수);  //부모 클래스의 메소드(입력값)를 호출한다.

보면 알겠지만 예전에 배운 this 대신에 super를 쓴것과 같다. 생성자안에서 다른 생성자를 호출할 경우 this( )나 super( )나 해당구문 맨첫줄에 써야하며 this가 자기자신을 말하는거였다면 super는 자기자신을 있게해준 부모를 말하는 것이라 하겠다. 더 자세하게 설명하자면 super는 바로 한단계 위로 올라간다고 생각하면 된다. 자바에서 부모는 위대하다. 왜? super니까 말이다.ㅎㅎ

super라는 키워드를 공부할때는 다 알것 같은데 막상 쓰다보면 굉장한 혼동이 올것이다. 그 이유중의 하나가 this처럼 단순히 super라는 키워드만 공부하면 되는게 아니라 상속관계에서 오는 파장(?)까지 고려해야하기 때문에 대부분이 안다고는 하나 딱히 자신있게 super에 대해서 논할수 있는 이가 그리 많지 않다. 오늘 이 강좌를 아니 필자가 보여주는 예제 하나만 확실하게 이해한다면 super라는 키워드에 대해서 앞으로 고민하지 않아도 될것이라 확신한다.^^

예제에 들어가기에 앞서 오늘 강좌 제일 첫부분에 언급한 super( )에 대해서 여러분의 이해를 돕기위해 한번더 짚고 넘어가도록 하겠다.^^ 객체생성 강의에서 아직 super에 대해서 설명하지않은 상태라 혼란을 피하기위해 잠시 뛰어넘었지만 여러분이 기본적으로 알아야할 용법이 있다. super( )는 부모클래스의 기본생성자를 호출하는 명령인데 이게 여러분이 new를 이용한 클래스의 객체생성시 해당클래스내의 기본생성자에 super( )라는 구문이 없을 경우에도 자동으로 항상 작동된다는 점이다. 다시 설명하자면 여러분이 객체생성을 할 경우 해당클래스내에 다른 생성자가 없을경우 자동으로 빈껍데기만이라도 있는 기본생성자를 만드는데 이안에 super( )도 자동으로 만들어지고 또한 다른 생성자가 있을 경우에는 기본생성자가 만들어지진 않겠지만 이 경우에도 해당 생성자안에 따로 super구문을 만들지 않았다면 객체생성시 해당 생성자안에서 역시 super( )가 여러분이 실행하는 해당클래스의 객체생성시 처음에 자동으로 작동된다는 점을 기억해야 한다.

그럼 이런 의문이 들것이다. 생성자는 모든 클래스안에 빈껍데기라도 있다고 하였는데 부모클래스가 없는 즉 상속받지 않은 클래스에서도 super( )가 자동으로 작동된다고 보아야할텐데 이건 어떻게 생각해야 되는건지 의문이 들것이다. 자바는 모든 클래스들의 단군 할아버지격(?)인 Object 클래스를 암묵적으로 계승하고 있다. Object가 모든 클래스들의 최상위 클래스라고 부를수 있는 것이 이에 기인한다. 즉 직접적인 상속관계가 없다면 기본적으로 모든 클래스들의 부모클래스인 Object 클래스의 기본생성자를 호출한다고 생각하면 된다. 여기서 또 질문하는 이들이 있을 것이다.ㅎㅎ 그럼 Object 클래스가 최상위 클래스인데 거기서는 누구를 호출한단 말인가? 아주 날카로운 질문이다.ㅎㅎ 그래서 Object 클래스의 기본생성자에는 super( )가 자동으로 호출되지 않는다. 따라서 여러분이 생각하는 부모클래스에서 super 구문이 없을경우 super( )를 자동으로 호출해서 상속관계 여부에 따라 계속적으로 위로 거슬러 올라갈수 있겠지만 결국 최종 종착지는 기본생성자안에 super( )가 없는 Object 클래스가 될것이다. 이 정도면 여러분의 궁금증이 해소되었으리라 생각되니 하던 강의를 계속 진행하겠다.^^

방금 말한 것처럼 예제 딱 하나 더도말고 딱 하나만 준비했다. 상속에서 super와 객체생성에 얽힌 모든 핵심을 이 예제에 담았다. 본 예제는 조금 긴 관계로 여기를 누르고 다운하기 바란다. 예제에 주석을 아주 풍부(?)하게 담았으나 이해를 돕기 위해 한줄 한줄 장인정신(!)으로 똘똘 무장해서 자세한 설명을 곁들이겠으니 잠시나마 집중해서 본 내용을 자기 것으로 만들기 바란다.*

1000
===인자 a가 있는 Parent 생성자===
부모는 자동차를 소유
이제는 자식도 자동차를 소유
자식만 배를 소유
100이 super.money값이다.
300이 this.money값이다.
===나는 Child 기본 생성자===
300
이제는 자식도 자동차를 소유
부모는 기차를 소유
200
자식만 배를 소유
300
===나는 Parent 기본 생성자===
===인자 b가 있는 Child 생성자===
555
==============================
1000
===인자 a가 있는 Parent 생성자===
부모는 자동차를 소유
이제는 자식도 자동차를 소유
자식만 배를 소유
100이 super.money값이다.
300이 this.money값이다.
===나는 Child 기본 생성자===
이제는 자식도 자동차를 소유
부모는 기차를 소유
==============================
===나는 Parent 기본 생성자===
부모는 자동차를 소유
부모는 기차를 소유

예제의 출력 결과가 좀 길지만 긴만큼 여러분이 그동안 아리까리했던 많은 부분들을 좀 더 명확하게 알수있는 계기가 될것이다. 예제의 전체적인 개요를 듣고 코드를 보면 어려울건 없다. 본 예제는 이러하다. 총 3개의 클래스, 부모 클래스(Parent), 자식 클래스(Child), 그리고 메인메소드만 있는 Test41 클래스가 있다. 부모 클래스와 자식 클래스에는 각각 두개의 생성자와 두개의 메소드가 있다.  부모는 차(car)와 기차(train)가 자식은 차(car)와 배(ship)를 메소드 형태로 소유하고 있다. 당연하지만 클래스 이름에서 보듯이 자식은 부모 클래스를 상속받고(extends) 있다.

이제 메인 메소드에서 실행하는 명령들을 살펴보자. 각기 다른 객체 생성을 통해 변수와 생성자 그리고 메소드의 상호관계를 총 3가지 파트로 나누어 살펴보기 위한 예제이다. 보기 좋으라고 "==========" 분리선을 이용해 출력결과를 파트별로 분류하였다.

그럼 이제 예제 프로그램 속으로 들어가보자. 첫번째 파트는 Child t=new Child();이다. 자식 객체 생성이다. 자식(Child)에 인자가 없으니 기본 생성자가 있는 곳으로 간다. 가서 보니 맨 첫줄에 super(1000);을 만난다. super는 부모클래스를 말하고 형태가 1000을 입력값으로 가질수 있는 생성자를 찾아보면 Parent(int a)로 가야됨을 알수 있다.(여기서 super(1000);이라는 부모 생성자 호출기능이 없었다면 기본값인 super( ); 즉 부모 기본 생성자를 자동으로 호출했을 것이다. 자식의 어떤 생성자든지 부모 기본 생성자 호출기능이 눈에 보이지는 않지만 자동으로 삽입된다.)

인자 a를 가진 부모 생성자로 가면 돈을(money 변수)에 1000(원) 받고 그걸 출력하라고 하니 결과에서 첫줄에 1000이 찍히고 다음줄에 "===인자 a가 있는 Parent 생성자==="라는 글이 출력된다. 끝나면 아까 실행하던 자식 생성자로 다시 돌아와서 다음줄을 실행한다. super.car( ); 주석에서 보듯이 부모의 차 메소드로 간다. 돈 100원 받고 "부모는 자동차를 소유"라고 알려주란다.ㅎㅎ

자식생성자의 다음줄을 또 보면 car( ); 차 메소드를 가란다. 그런데 여기서 조금 헤깔릴수가 있다. 리턴값(결과값)과 인자(입력값)가 완전히 똑같은 car 메소드가 부모한테도 있고 자식한테도 있기 때문이다. 이렇게 겉껍데기가 완전히 동일한데 상속받아서 다시 구현한 형태를 우리는 오버라이딩(Overriding)이라고 말한다. 저번에 필자가 상속부분에서 잠깐 이야기했을 것이다.(잠시 이해를 돕기 위해 얘기하자면, 자바의 다형성(polymorphism)에는 크게 오버라이딩과 오버로딩이 있다. 오버라이딩은 방금처럼 매개변수, 리턴값, 메소드 이름까지 전부 동일한 형태를 말하고 오버로딩(Overloading)은 인자 즉 매개변수나 리턴값은 다르지만 메소드 이름이 같은 형태를 말한다. 이렇게 동일한 형태로 다른 형태의 멤버들을 제어할수 있을때 이것을 자바의 다형성이라고 하는 것이다. 따로 단원을 만들어 강좌를 진행하려고 했는데 이미 오버로딩도 전부터 예제에서 계속 써먹고 있으니 모르는 이는 없을 것인즉 그냥 이걸로 퉁치려고 하니 양해바란다.ㅎㅎ )

갈길이 먼데 자꾸 삼천포로 빠지고 있구나. 이제 예제로 다시 돌아가보자. 어디 할 차례더라?? 올커니 car 메소드 때문에 이런 얘기가 나왔구나. 그런데 같던지 다르던지 상관없다. 상속을 받았으니(자식 클래스안에 부모의 car( ) 메소드가 있다고 생각) 자식 클래스가 가지고 있는게 더 확장 발전시킨걸로 간주하고 같을 경우 자식이 소유한 것을 쓴다고 이미 말했었다. 따라서 자식의 car( )를 실행한다. 그래서 돈(money) 200원 받고 "이제는 자식도 자동차를 소유"라인을 출력한다. 다음줄에 ship( )은 자식만 가지고 있으니 가차없이 이걸 실행하면 돈 300원을 받고 "자식만 배를 소유"라인을 출력한다.

이제 super.money와 this.money를 찍을 차례이다. 오늘의 하이라이트라고 해도 괜찮을 것이다. super에는 100원이 this에는 300원이라는 돈이 출력되었다. 왜 그럴까? money 변수는 부모에게도 있고 자식에게도 있다. 이럴 경우 제각기 돌아간다. 부모 클래스에 money에 관련된 명령은 super.car( )에서 즉 돈 100원을 받았을때까지이고 자식 클래스에서 money에 관련된 명령은 ship( )에서 즉 돈 300원을 받았을때까지가 돈의 흐름(?)이다. 헤깔리면 다시 차근차근 보면 이해가 될 것이다. 필자가 예제를 풀이하면서 설명하니 별 어렵지 않다고 느낄지 모르지만 상속 관계에서 super로 인한 혼선이 이런 식으로 가중되고 증폭되는 것이다. 간단한 예지만 느끼는 바가 크길 바란다.^^

이제 "===나는 Child 기본 생성자==="라인을 출력하고 다 했으니 메인메소드로 돌아온다. 보면 알겠지만 이제까지 고작 Child t=new Child( ); 이거 한줄 실행한 것이다.--; 그렇다고 슬퍼말자. 이거 한줄 이해하면 그 다음부터는 일사천리(?)일 것이다. 쩡말? ㅎㅎ 다음줄은 자식 돈이니까 똑같이 300원을 출력한다.

t.car( ); 자식이 우선이니 자식의 car( ) 메소드 실행해서 출력하고 t.train( ); 메소드는 자식 클래스에는 없지만 부모한테서 물려받았으니 기차 메소드 또한 출력할수 있다. 상속받은게 뭔지(?) 모두 알고 있기를 바란다. 그리고 t.money 즉 자식이 돈 지금 얼마 가지고 있냐를 물었더니 200원이란다. 이해되는가? 기차에서 돈 111원을 받았는데 이게 아니고 돈이 더 많아진 200원이란다. 살펴보니 차에서 받은 200원이 출력되었다. 상속받은 기차보다 확장시킨 차가 우선한다는 쇼킹한 부분이다. money 변수가 부모 자식 양쪽다 있어서 이런 일이 벌어지고 있는 것이다.ㅎㅎ 다음줄에 있는 t.ship( )을 실행해서 돈 300원을 받고 "자식만 배를 소유"라인을 찍는다. 다시 t.money(티머니?)를 확인한다. 가차없이 300원이다.^^

Child t2=new Child(m);
System.out.println(t2.money);//결과값 555

다음의 이 두라인은 같은 자식의 객체 생성이지만 기본 생성자가 아닌 m이라는 인자를 가진 생성자를 호출하는 예제이다. 뭐가 다른지 잘 살펴보기 바란다. 그럼 첫줄의 생성자가 있는 곳으로 가보겠다. m이 555니까 이 값을 받아줄 자식 생성자를 찾아보면 Child(int b)를 볼수 있을 것이다. 들어가서 돈 555원을 받고 "===인자 b가 있는 Child 생성자==="을 시키는데로 찍었다. 결과를 맞춰보니 이런? 앞에 한줄이 더 있다..- 부모 기본 생성자를 실행했다고 하는 출력결과가 있다. 어떻게 된일일까? ㅎㅎ

벌써 까먹었남? 필자가 얘기하지 않았던가 상속 관계에서 자식 생성자 안에 부모 생성자를 호출하는게 아무것도 없을때에는 자동적으로 super( ); 라는 부모 기본 생성자를 호출하라는 명령이 숨어있다고 말이다. 따라서 있는걸로 간주하고 부모 생성자를 호출한 것이다. 그래서 돈 2222를 받고 "===나는 Parent 기본 생성자==="를 찍고 나서 돈 555를 이때 받고 "===인자 b가 있는 Child 생성자==="를 출력한다. 부모 생성자 호출은 언제나 가장 처음에 이루어기 때문이다. 참 알아야 할것도 많다. 그치? ㅎㅎ

메인메소드로 돌아와서 티머니 아니 이제 티2머니(t2.money)를 찍으면 555가 찍힌다. 맨 마지막에 자식 money 변수의 돈 흐름은 거기에서 끝났으니까 당연하다. 여기서 분리선이 있는걸 보니 이제서야 첫번째 파트가 끝났다. ㅎㅎ 잠깐 쉬자~ 물도 먹고 스트레칭 하고 조금 후에 다시 만나자~ ^^

다 쉬었음 한번더 해보자. 파트 투우우~2 ^^
Parent t3=new Child( ); 여기서부터 할 차례인데 뭔가 심상치 않다. 객체 생성을 하는데 클래스 이름이 다르다(?) 이게 가능한가? 가능하다. 이래저래 상속은 어렵구나라고 실망하지 말자. 형태는 부모 클래스인 자식 클래스에 대한 레퍼런스 t3를 만들었는데 별 다른건 없다. 다만 t나 앞으로 만날 t5랑은 다른 레퍼런스 형태라는 것만 알아두기 바란다. 그걸로 족하니라.^^ 별 다를건 없고 new 키워드가 만들고 있는 자식 클래스 객체 생성자로 간다. 그럼 아까처럼 똑같이 실행한다.

1000
===인자 a가 있는 Parent 생성자===
부모는 자동차를 소유
이제는 자식도 자동차를 소유
자식만 배를 소유
100이 super.money값이다.
300이 this.money값이다.
===나는 Child 기본 생성자===

여기까지 똑같으니 이건 생략하고 다음줄부터 보겠다. t3.car( )랑 상속받았으니 t3.train( ) 메소드 다 실행가능하다. 따라서 "이제는 자식도 자동차를 소유"와 "부모는 기차를 소유"를 연달아서 처리한다. 그 다음 t3.ship( ); 실행하지 말라고 주석처리 해놓았는데 이거 보려고 이런 부모 자식 클래스의 다른 객체(?) 생성까지 한거다. 자식 메소드에 배(ship)가 있으니 당연히 될줄 알았겠지만 그렇지 않다. 에러다. 객체 생성시 형태는 부모 클래스로 만들고 인스턴스는 자식 클래스를 참조하게 만들면(Parent t3=new Child( );) 자식 클래스에게만 있는 이런 멤버는 접근할수가 없다. 멋들어지게 표현하자면 "부모의 자식 사랑은 끝이 없다고나 할까?" 오로지 주기만 할뿐 자식이 아니 자식 클래스가 가지고 있는 일반변수는 건드리지를 않는다. 이것은 자바의 다형성(Polymorphism)에 관련된 사항이다. 핵심만 설명하자면 부모클래스가 가지고 있는 일반변수만 사용가능하고 똑같은 메소드(오버라이드된 메소드)가 자식클래스에도 있을경우 자식클래스의 메소드를 호출하며 자식클래스에는 없는 메소드가 부모클래스에만 있을경우 부모클래스의 메소드를 호출한다. 둘다없는 경우는 당근 에러쥥.^^ 이런 형식은 객체생성을 할수 없는 추상클래스나 인터페이스의 경우에 많이 쓰인다.ㅎㅎ

다음에 Child t4=new Parent(); 라인이 나오는데 주석처리한 것처럼 실행불가능이다. 부모에서 자식으로는 객체 생성을 할수 있지만 반대로 자식에서 부모로는 객체 생성을 할수가 없다. 자식 클래스가 부모 클래스의 확장 발전된 형태이므로 이럴 경우 관계가 난감(?)해지기 때문에 안된다고 이해하면 될것이다. 또 한마디 해보자면, "자식은 부모에게 평생 제대로 효도 한번 못한다." 이런뜻과 일맥상통(?)이려나...^^ 이로써 파트2도 무사히 끝났다.ㅎㅎ 하나만 더 하자.

마지막 파트3에는 Parent t5=new Parent(); 부모 객체 생성을 통해 얻은 t5에 대한 설명이다. 당연히 부모 기본 생성자로 간다. 돈 2222로 받고 "===나는 Parent 기본 생성자==="를 찍는다. 부모 생성자안이니 여기서 자동으로 무슨 자식 생성자를 부른다거나 이런거 없다. "자식은 항상 부모의 보살핌을 필요로 하지만 부모는 그렇지 않다." 자식이 죄인이다.ㅎㅎ 그래서 생성자 호출끝내고 다시 메인메소드로 돌아와서 다음줄 t5.car( )와 t5.train( ) 메소드를 돈 100이랑 111받고 "부모는 자동차를 소유"랑 "부모는 기차를 소유"를 차례대로 출력한다. 부모 객체 생성했으니 당연히 부모만 보면 된다. 마지막에 t5.ship( )도 부모한테 이런거 없으니 당근 에러다. 당연히 안되는건데도 불구하고 이건지 아닌지 조심스러울거다. 이제껏 하도 당해놔서 말이다.ㅎㅎ

이제 예제풀이가 끝났다. 여러분의 소감은 어떤가? 상속이 그리 만만한 개념이 아니라고 느꼈을거라 본다. 거기다가 super라는 키워드가 끼어듦으로 인해서 말그대로 수퍼맨처럼 이리저리 다녀야하니 그대가 수퍼맨(?)이 되지 않고서는 자바의 상속이라는 개념을 제대로 이해하기 힘든 실정이다. 이번 강좌를 보면서 공부한 이들은 기존에 자바의 상속에 대해서 알고 있었을지라도 상당한 벽에 부딪치는 경험을 했을 것으로 생각한다. 프로그램이 방대해지면 방대해질수록 이런 미묘한 차이를 확실히 알지 못하면 디버깅 하는것조차 힘에겨워 할 것이다.

오늘은 super뿐만 아니라 그로 인해 생기는 자바 상속의 원리를 한 예제를 통해 집중조명하였다. 자바를 배우는 사람들의 절반 이상이 자바의 상속을 배우기 시작하면서부터 떨어져 나간다. 허나 강이와 함께 하는 여러분들은 승자까지는 아니더라도 살아남기만 하면 된다. 오늘 배운것처럼 부모의 원대하고 끝없는 사랑과 같이 여러분에 대한 강이의 사랑도 부모처럼 원없이 내리퍼주는 사랑(?)이기 때문이다.^^ 포기하지 말자. 계속 정진하면 언젠가 그 꿈은 반드시 이루어질 것이다. "Dream High!" ^^