연산식에는 다양한 연산자가 복합적으로 구성된 경우가 많다. 산술 연산식에서 덧셈(+), 뺄셈(-) 연산자보다는 곱셈(*), 나눗셈(/) 연산자가 우선 처리된다는 것을 우리는 이미 알고 있다. 그러면 다른 연산자들의 경우는 어떨까? 예를 들어 다음과 같은 연산식에서 && 연산자가 먼저 처리될까 아니면 >, < 연산자가 먼저 처리될까?


x > 0 && y < 0


프로그램에서는 연산자의 연산 방향과 연산자 간의 우선순위가 정해져 있다. &&보다는 >, < 가 우선순위가 높기 때문에 x > 0과 y < 0이 먼저 처리되고, &&는 x > 0 과 y < 0의 산출값을 가지고 연산하게 된다. 그러면 우선순위가 같은 연산자들끼리는 어떤 순서로 처리가 될까? 이 경우에는 연산의 방향에 따라 달라진다. 대부분의 연산자는 왼쪽에서부터 오른쪽으로(→) 연산을 시작한다. 예를 들어 다음 연산식을 보자.


100 * 2 / 3 % 5


*, /, %는 같은 우선순위를 갖고 있다. 이 연산자들은 연산 방향이 왼쪽에서 오른쪽으로 수행된다. 100 * 2가 제일 먼저 연산되어 200이 산출되고, 그 다음 200 / 3이 연산되어 66이 산출된다. 그 다음으로 66 % 5가 연산되어 1이 나온다.

하지만 단항 연산자(++, --, ~, !), 부호 연산자(+, -), 대입 연산자(=, +=, -=, ...)는 오른쪽에서 왼쪽(←)으로 연산된다. 예를 들어 다음 연산식을 보자.


a =b = c = 5;


위 연산식은 c = 5, b= c, a = b 순서로 실행된다. 실행되고 난 후에는 a, b, c의 값이 모두 5가 된다. 이와 같이 어떤 연산자를 사용하느냐에 따라 연산의 방향과 우선순위가 정해져 있기 때문에 복잡한 연산식에서는 주의가 필요하다. 다음은 연산자의 연산 방향과 우선순위를 정리한 표이다.


연산자

연산 방향

우선순

증감(++, --), 부호(+, -), 비트(~), 논리(!)

높음

산술(*, /, %)


산술(+, -)

쉬프트(<<, >>, >>>)

비교(<, >, <=, >=, instanceof)

비교(==, !=) 

논리(&) 

논리(^) 

논리(|)

논리(&&)

논리(||)

조건(?:) 

대입(=, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>=, >>>=)

낮음


이렇게 우선순위와 연산 방향이 정해져 있다 하더라도 여러 가지 연산자들이 섞여 있다면 어느 연산자가 먼저 처리될지 매우 혼란스러울 것이다. 그래서 괄호()를 사용해서 먼저 처리해야 할 연산식을 묶는 것이 좋다. 예를 들어 다음 산술 연산식을 ①이 먼저 연산되고 ②가 나중에 연산된다.


int var1 = 1;

int var2 = 3;

int var3 = 2;

int result = var1 + var2 * var3;

                               ①

                     ②


만약 var1 + var2를 먼저 연산하고 싶다면 괄호()를 사용하면 된다. 괄호 부분의 연산은 최우선순위를 갖기 때문에 다른 연산자보다 우선 연산된다.


int result = (var1 + var2) * var3;

                       ①

                                ②


지금까지 연산의 방향과 우선순위를 정리하면 다음과 같다.


1. 단항, 이항, 삼항 연산자 순으로 우선순위를 가진다.

2. 산술, 비교, 논리, 대입 연산자 순으로 우선순위를 가진다.

3. 단항과 대입 연산자를 제외한 모든 연산의 방향은 왼쪽에서 오른쪽이다(→).

4. 복잡한 연산식에는 괄호()를 사용해서 우선순위를 정해준다.

프로그램에서 데이터를 처리하여 결과를 산출하는 것을 연산(operations)이라고 한다. 연산에 사용되는 표시나 기호를 연산자(operator)라고 하고, 연산되는 데이터는 피연산자(operand)라고 한다. 연산자와 피연산자를 이용하여 연산의 과정을 기술한 것을 연산식(expressions)이라고 부른다. 예를 들어 다음 연산식에서 +, -, *, ==은 연산자이고 x, y, z 변수는 피연산자이다.


x + y

x - y

x * y + z

x == y


자바 언어에서는 다양한 연산자를 제공하고 있다. 이 연산자들은 피연산자를 연산해서 값을 산출하는데, 산출되는 값의 타입은 연산자별로 다르다. 예를 들어 산술 연산자일 경우는 숫자 타입(byte, short, int, long, float, double)으로 결과값이 나오고, 비교 연산자와 논리 연산자는 논리(boolean) 타입으로 나온다. 다음과 자바에서 제공하는 다양한 연사자를 보여준다.


연산자 종류

연산자

피연산자 수

산출값

기능 설명

산술

+, -, *, /, %

이항

숫자

사칙연산 및 나머지 계산

부호

+, -

단항

숫자

음수와 양수의 부호

문자열

+

이항

문자열

두 문자열을 연결

대입

=, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>=, >>>=

이항

다양

우변의 값을 좌변의 변수에 대입

증감

++, --

단항

숫자

1만큼 증가/감소

비교

==, !=, >, <, >=, <=, instanceof

이항

boolean

값의 비교

논리

!, &, |, &&, ||

단항

이항

boolean

논리적 NOT, AND, OR 연산

조건

(조건식) ? A : B

삼항

다양

조건식에 따라 A 또는 B 중 하나를 선택

비트

~, &, |, ^

단항

이항

숫자

boolean

비트 NOT, AND, OR, XOR 연산

쉬프트

>>, <<, >>>

이항

숫자

비트를 좌측/우측으로 밀어서 이동


연산자는 필요로 하는 피연산자의 수에 따라 단항, 이항, 삼항 연산자로 구분된다. 부호 연산자와 증가/감소 연산자는 피연산자 하나만을 요구하므로 단항 연산자이고, 조건 연산자는 조건식, A, B와 같이 세 개의 피연산자가 필요하므로 삼항 연산자라고 한다. 그 이외의 연산자는 두 개의 피연산자를 요구하므로 모두 이항 연산자이다.


단항 연산자 : ++x;

이항 연산자 : x + y;

삼항 연산자 : (sum > 90) ? "A" : "B";


연산식은 반드시 하나의 값을 산출한다. 연산자 수가 아무리 많아도 두 개 이상의 값을 산출하는 연산식은 없다. 그렇기 때문에 하나의 값이 올 수 있는 곳이면 어디든지 값 대신에 연산식을 사용할 수 있다.보통 연산식의 값은 변수에 저장하는데, 다음과 같이 x와 y 변수의 값을 더하고 나서 result 변수에 저장한다.


int result = x + y;


연산식은 다른 연산식의 피연산자 위치에도 올 수 있다. 다음과 같이 비교 연산자인 < 의 좌측 피연산자로 (x+y)라는 연산식이 사용되어, x와 y 변수의 값을 더하고 나서 5보다 작은지 검사한 후 결과값(true 또는 false)을 result 변수에 저장한다.


boolean result = (x+y) < 5;

타입 변환이란 데이터 타입을 다른 데이터 타입으로 변환하는 것을 말한다. 예를 들어 byte 타입을 int 타입으로 변환하거나 반대로 int 타입을 byte 타입으로 변환하는 행위를 말한다. 타입 변환에는 두 가지 종류가 있다. 하나는 자동(묵시적) 타입 변환이고 다른 하나는 강제(명시적) 타입 변환이다.


2.3.1 자동 타입 변환

자동 타입 변환(Promotion)은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다. 자동 타입 변환은 작은 크기를 가지는 타입이 큰 크기를 가지는 타입에 저장될 때 발생한다.


큰 크기 타입과 작은 크기 타입의 구분은 사용하는 메모리 크기이다. 예를 들어 byte 타입은 1byte 크기를 가지고, int 타입은 4byte 크기를 가지므로 int 타입이 큰 크기 타입이고, byte 타입이 작은 크기 타입이다. 크기별로 타입을 정리하면 다음과 같다.


byte(1) < short(2) < int(4) < long(8) < float(4) < double(8)


float은 4byte 크기인데 int(4byte)와 long(8byte)보다 더 큰 타입으로 표시했다. 그 이유는 표현할 수 있는 값의 범위가 float이 더 크기 때문이다. 이것만 주의하면 데이터 타입의 크기를 비교하는 것은 그리 어렵지 않을 것이다. 다음 코드를 보자.

byte byteValue = 10;

int intValue = byteValue;    //자동 타입 변환이 일어난다.


byteValue는 byte 타입 변수이므로 1byte 크기를 가진다. 그리고 intValue는 int 타입 변수이므로 4byte 크기를 가진다. 따라서 byte타입 byteValue는 int타입 intValue로 자동 타입 변환된다. 메모리에서 값이 복사되는 모양을 그림으로 표현하면 다음과 같다.



int타입(4byte)




자동 타입 변환

byte타입(1byte)

00000000

00000000

00000000

00000000


00001010


자동 타입 변환이 발생되면 변환 이전의 값과 변환 이후의 값은 동일하다. 즉, 변환 이전의 값은 변환 이후에도 손실 없이 그대로 보존된다. 이것은 작은 그릇의 물을 큰 그릇으로 옮기더라도 물의 양은 변하지 않는다는 것과 유사하다. 정수 타입이 실수 타입으로 변환하는 것은 무조건 자동 타입 변환이 된다. 실수 타입으로 변환된 이후의 값은 정수값이 아닌 .0이 붙은 실수값이 된다. 다음 코드에서 intValue가 doubleValue에 저장되면 200은 200.0으로 저장된다.


int intValue = 200;

double doubleValue = intValue;    //200.0


char 타입의 경우 int 타입으로 자동 변환되면 유니코드 값이 int 타입에 저장된다.


char charValue = 'A';

int intValue = charValue;    //65가 저장


자동 타입 변환에서 단 하나의 예외가 있는데, char가 2byte의 크기를 가지지만, char의 범위는 0~65535이므로 음수가 저장될 수 없다. 따라서 음수가 저장될 수 있는 byte타입을 char타입으로 자동 변환시킬 수 없다.


byte byteValue = 65;

char charValue = byteValue; (x)        //컴파일 에러

char charData = (char)byteData; (o)    //강제 타입 변환

다음은 자동 타입 변환이 생기는 다양한 코드들이다.




2.3.2 강제 타입 변환

큰 크기의 타입은 작은 크기의 타입으로 자동 타입 변환을 할 수 없다. 예를 들어, 4byte인 int타입을 1byte인 byte 타입에 담을 수 없다. 마치 큰 그릇의 물을 작은 그릇 안에 모두 넣을 수 없는 것과 동일한 이치이다. 하지만 큰 그릇을 작은 그릇 사이즈로 쪼개어서 한 조각만 작은 그릇에 넣는다면 가능하다. 즉 int 타입을 4개의 byte로 쪼갠 다음, 끝에 있는 1byte만 byte 타입 변수에 저장하는 것은 가능하다. 이와 같이 강제적으로 큰 데이터 타입을 작은 데이터 타입으로 쪼개어서 저장하는 것을 강제 타입 변환(캐스팅: Casting)이라고 한다. 강제 타입 변환은 캐스팅 연산자 ()를 사용하는데, 괄호 안에 들어가는 타입은 쪼개는 단위이다.


강제 타입 변환

  ←

작은 크기 타입 = (작은 크기 타입) 큰 크기 타입


다음 코드를 보자. int 타입 intValue 변수는 4byte이므로 1byte 크기를 가지는 byte 타입 byteValue 변수에 저장할 수 없다. 그래서 강제적으로 (byte) 캐스팅 연산자를 사용해서 int 타입 intValue를 1byte씩 쪼개고, 끝에 있는 1byte만 byteValue 변수에 저장한다.


int intValue = 103029770;

byte byteValue = (byte) intValue;        //강제 타입 변환(캐스팅)


끝 1byte만 byte 타입 변수에 담게 되므로 원래 int 값은 보존되지 않는다. 하지만 int 값이 끝 1byte로만 표현이 가능하다면 byte 타입으로 변환해도 같은 값이 유지될 수 있다. 이럴 경우 강제 타입 변환이 의미 있게 된다. 예를 들어 int 타입 변수에 10을 저장할 경우, 4byte 중 끝 1byte로 10을 충분히 표현할 수 있으므로 앞 3byte는 모두 0으로 채워진다. 이것을 byte 타입으로 강제 타입 변환할 경우 앞의 3byte는 버려지고 끝 1byte만 byte 타입 변수에 저장되기 때문에 10이 그대로 유지된다.


다른 예로 long 타입 변수에 300이 저장되어 있을 경우, 8byte 중 끝의 4byte로 300을 충분히 표현 할 수 있으므로 이것을 int 타입으로 강제 타입 변환하면 앞의 4byte는 버려지고 끝의 4byte만 int 타입 변수에 저장되어 300이 그대로 유지된다.


long longValue = 300;

int intValue = (int) longValue;        //intValue는 300이 그대로 저장된다.


int 타입은 char 타입으로 자동 변환되지 않기 때문에 강제 타입 변환을 사용해야 한다. int 타입에 저장된 값이 유니코드 범위(0~65535)라면 다음과 같이 (char) 캐스팅 연사자를 사용해서 char 타입으로 변환할 수 있다. char 타입으로 변환된 값을 출력하면 유니코드에 해당하는 문자가 출력된다.


int intValue = 'A';

char charValue = (char) intValue;

System.out.println(charValue);


실수 타입(float, double)은 정수 타입(byte, short, int, long)으로 자동 변환되지 않기 때문에 강제 타입 변환을 사용해야 한다. 이 경우 소수점 이하부분은 버려지고, 정수 부분만 저장된다.


double doubleValue = 3.14;

int intValue = (int) doubleValue;        //intValue는 정수 부분인 3만 저장된다.



강제 타입 변환에서 주의할 점은 사용자로부터 입력받은 값을 변환할 때 값의 손실이 발생하면 안된다는 것이다. 강제 타입 변환을 하기 전에 우선 안전하게 값이 보존될 수 있는지 검사하는 것이 좋다. 아래 예제는 byte 타입으로 변환하기 전에 변환될 값이 byte 타입으로 변환된 후에도 값의 손실이 발생하지 않는지 검사해서 올바른 타입 변환이 되도록 한다.



8라인에서 사용된 연산식과 if문은 3장과 4장을 학습하면 자연스럽게 알게 되므로 여기서는 변수 i의 값이 byte 타입의 최소값보다 작은지, 최대값보다 큰지를 조사해서 하나라도 해당이 된다면 9~10라인을 실행시키고, 그렇지 않을 경우 12~13라인을 실행시킨다는 것만 알아두자. i는 128이므로 Byte.MAX_VALUE인 127보다 크기 때문에 if문의 조건식이 true가 되어 6~7라인만 실행된다. 자바는 코드에서 데이터 값을 검사하기 위해 boolean과 char 타입을 제외하고 모든 기본 타엡 대해 최대값(max)과 최소값(min)을 다음과 같이 상수로 제공하고 있다. 어떤 정수값과 실수값을 다른 타입으로 변환하고자 할 때는 변환될 타입의 최소값과 최대값을 벗어나는지 반드시 검사하고, 만약 벗어난다면 타입 변환을 하지 말아야 한다.


기본 타입

최대값 상수

최소값 상수

byte

Byte.MAX_VALUE

Byte.MIN_VALUE

short

Short.MAX_VALUE

Short.MIN_VALUE

int

Integer.MAX_VALUE

Integer.MIN_VALUE

long

Long.MAX_VALUE

Long.MIN_VALUE

float

Float.MAX_VALUE

Float.MIN_VALUE

double

Double.MAX_VALUE

Double.MIN_VALUE


강제 타입 변환에서 또 다른 주의점이 있다. 정수 타입을 실수 타입으로 변환할 때 정밀도 손실을 피해야 한다. 다음 예제를 보자. int 타입 변수 num1과 num2에 동일한 123456780 값을 저장시키고, num2를 float 타입으로 변환시킨 후, 다시 int 타입으로 변환해서 num2에 저장한다. 그리고 num1에서 num2를 뺀 결과를 변수 result에 저장하고 콘솔에 출력한다. 동일한 값을 뺐기 때문에 당연히 0이 출력되는 것이 맞다.



그러나 실행 결과를 보면 엉뚱하게도 0이 나오질 않는다. 이러한 결과가 나온 이유는 int 값을 floatt 타입으로 자동 변환하면서 문제가 발생했기 때문이다. float 타입은 다음과 같이 비트 수가 할당되어 있다.


float: 부호(1비트) + 지수(8비트) + 가수(23비트)


int 값을 손실 없이 float 타입의 값으로 변환할 수 있으려면 가수 23비트로 표현 가능한 값이어야 한다. 123456780은 23비트로 표현할 수 없기 때문에 근사치로 변환된다. 즉 정밀도 손실이 발생한다. 그렇기 때문에 float 값을 다시 int 타입으로 변환하면, 원래의 int 값을 얻지 못한다. 따라서 12라인에서 num1과 num2는 동일한 값이 아니다. 해결책은 모든 int 값을 실수 타입으로 안전하게 변환시키는 double 타입을 사용하는 것이다. double 타입은 다음과 같이 비트 수가 할당되어 있다.


double: 부호(1비트) + 지수(11비트) + 가수(52비트)


int의 크기는 32비트이므로 double의 가수 52비트보다는 작기 때문에 어떠한 int 값이라도 안전하게 정밀도 손실 없이 double 타입으로 변환될 수 있다. 그래서 double 값을 원래 int 타입으로 변환해도 손실 없이 복원된다. 이전 예제의 9라인을 수정하고 다시 실행하면 다음과 같다.




2.3.3 연산식에서의 자동 타입 변환

연산은 기본적으로 같은 타입의 피연산자(operand) 간에만 수행되기 때문에 서로 다른 타입의 피연산자가 있을 경우 두 피연산자 중 크기가 큰 타입으로 자동 변환된 후 연산을 수행한다. 예를 들어 int 타입 피연산자와 double 타입 피연산자를 덧셈 연산하면 먼저 int 타입 피연산자가 double 타입으로 자동 변환되고 연산을 수행한다. 당연히 연산의 결과는 double이 된다.


int intValue = 10;

double doubleValue = 5.5;

double result = intValue + doubleValue;        //result에 15.5가 저장

→ double 값으로 변환


만약 int 타입으로 꼭 연산을 해야 한다면 double 타입을 int 타입으로 강제 변환하고 덧셈 연산을 수행하면 된다.


int intValue = 10;

double doubleValue = 5.5;

int result = intValue +(int)doubleValue;        //result에 15가 저장


자바는 정수 연산일 경우 int 타입을 기본으로 한다. 그 이유는 피연산자를 4byte 단위로 저장하기 때문에 크기가 4byte보다 작은 타입(byte, char, short)은 4byte인 int 타입으로 변환된 후 연산이 수행된다. 따라서 연산의 결과도 int 타입이 된다.


int result =

byte 타입

char 타입

short 타입

int 타입

연산자(+, -, *, /, %)

byte 타입

char 타입

short 타입

int 타입


예를 들어 char 타입의 연산 결과는 int 타입으로 산출되므로 int 타입 변수에 결과를 저장해야 한다. 연산의 결과를 다시 문자로 출력하거나 저장하기 위해서는 int 결과값을 char 타입으로 강제 변환(casting)해야 한다.


char ai = 'A';

int result = ai + 1;        //'A'의 유니코드보다 1이 큰 유니코드가 저장

char na = (char)result;   //'B'가 저장됨


만약 피연산자 중 하나가 long 타입이라면 다른 피연산자도 long 타입으로 자동 타입 변환되고 연산의 결과는 long 타입이 된다.


long result

long 변수

연산자(+, -, *, /, %) 

byte 타입

char 타입

short 타입

int 타입


float 타입과 float 타입을 연산하면 연산의 결과는 float 타입으로 나오지만, 피연산자 중에 실수 리터럴이나 double 타입이 있다면 다른 피연산자도 double 타입으로 자동 타입 변환되어 연산되므로 결과는 double 타입으로 산출된다.


double result

실수 리터럴

double 변수

연산자(+, -, *, /, %)

byte 타입

char 타입

short 타입

int 타입

float 타입

double 타입



모든 변수에는 타입(type: 형(形))이 있으며, 타입에 따라 저장할 수 있는 값의 종류와 범위가 달라진다. 변수를 선언할 때 주어진 타입은 변수를 사용하는 도중에 변경할 수 없다. 따라서 변수를 선언할 때 어떤 타입을 사용할지 충분히 고려해야 한다.



2.2.1 기본(원시: primitive)타입

기본(원시) 타입이란 정수, 실수, 문자, 논리 리터럴을 직접 저장하는 타입을 말한다. 정수 타입에는 byte, char, short, int, long이 있고, 실수 타입에는 float, double이 있다. 그리고 논리 타입에는 boolean이 있다. 다음은 각 기본 타입의 메모리 크기와 저장되는 값의 범위를 보여준다.


 갑의 종류

 기본 타입 

 메모리 사용 크기 

 저장되는 값의 범위 

 정수

 byte

 1 byte

 8 bit

 -2^7 ~ (2^7-1)(-128 ~ 127)

 char

 2 byte

 16 bit

 0 ~ 2^16-1(유니코드: \u0000 ~ \uFFFF, 0 ~ 65535)

 short

 2 byte

 16 bit

 -2^15 ~ (2^15-1)(-32,768 ~ 32,767)

 int

 4 byte

 32 bit

 -2^31 ~ (2^31-1)(-2,147,483,648 ~ 2,147,483,647)

 long

 8 byte

 64 bit

 -^63 ~ (2^63-1)

 실수

 float

 4 byte

 32 bit

 (+/-)1.4E-45 ~ (+/-)3.4028235E38

 double

 8 byte

 64 bit

 (+/-)4.9E-324 ~ (+/-)1.7976931348623157E308

 논리 boolean 1 byte 8 bit

 true, false


메모리에는 0과 1을 저장하는 최소 기억 단위인 비트(bit)가 있다. 그리고 8개의 비트를 묶어서 바이트(byte)라고 한다. 기본 타입은 정해진 메모리 사용 크기(바이트 크기)로 값을 저장하는데 바이트 크기가 클수록 표현하는 값의 범위가 크다. 각 타입에 저장되는 값의 범위를 정확히 외울 필요는 없지만, 메모리 사용 크기 정도는 알고 있는 것이 좋다. 정수 타입일 경우 -2^(n-1) ~ 2^(n-1)-1의 값을 저장할 수 있는데, 여기서 n이 메모리 사용 크기(bit 수)이다. 예를 들어 int 타입의 경우 4byte(32bit)이므로 -2^31 ~ 2^31-1의 값의 범위를 갖는다. 실수 타입일 경우 가수가 지수 부분에 사용되는 bit크기에 따라서 값의 범위가 결정된다.



2.2.2 정수 타입(byte, char, short, int, long)

정수 타입에는 모두 다섯 개의 타입이 있으며 저장할 수 있는 값의 범위가 서로 다르다. 메모리 크기순으로 나열하면 다음과 같다.


정수 타입 

byte 

char 

short 

int 

long 

바이트 수 



자바는 기본적으로 정수 연산을 int타입으로 수행한다. 그렇기 때문에 저장하려는 값이 정수 리터럴이라면 특별한 이유가 없는 한 intt 타입 변수에 저장하는 것이 좋다. byte와 short이 int보다는 메모리 사용 크기가 작아서 메모리를 절약할 수는 있지만, 값의 범위가 작은 편이라서 연산 시에 범위를 초과하면 잘못된 결과를 얻기 쉽다.


byte 타입

byte 타입은 색상 정보 및 파일 또는 이미지 등의 이진(바이너리) 데이터를 처리할 때 주로 사용된다. byte 타입은 정수 타입 중에서 가장 작은 범위의 수를 저장하는데, 표현할 수 있는 값의 범위는 -128 ~ 127(-2^7 ~ 2^7-1)이다. 양수가 2^7-1인 이유는 0이 포함되기 때문이다. 만약 -128 ~ 127을 초과하는 값이 byte 타입 변수에 저장될 경우 컴파일 에러("Type mismatch: cannot convert from int to byte)가 발생한다. byte 타입이 왜 -128 ~ 127까지 정수값을 저장하는지 알아보자. byte 타입은 1byte, 즉 8bit 크기를 가지므로 다음과 같이 0과 1이 8개로 구성된 이진수로 표현이 가능한다.


 이진수

 

 십진수

 최상위 비트

  

0

1

127

0

0

126


0

1

0

 0

-1 

-2 

 

1

-127 

-128 


최상위 비트(MSB: Most Significat Bit)는 정수값의 부호를 결정한다. 최상위 비트가 0이면 양의 정수, 1이면 음의 정수를 뜻한다. 실제 정수값은 나머지 7개의 bitt로 결정된다. 최상위 비트가 1인 음수의 경우에는 나머지 7개의 bit를 모두 1의 보수(1은 0으로, 0은 1로)로 바꾸고 1을 더한 값에 -를 붙여주면 십진수가 된다. 예를 들어 -2는 다음과 같이 계산된다.


-2의 이진수 

→ 

1

1

1

1

1

1

1

0

1의 보수

십진수 계산 방법

 

0

0

0

0

0

0

1

 

 

 

 

 

 

+

1

 

 

 

0

0

0

0

0

1

0

→ 

-2

 

2^6

2^5

2^4

2^3

2^2

2^1

2^0

 (1×2^1)


byte 타입보다 크기가 큰 short, int, long 타입도 전체 바이트 수만 다를 뿐 동일한 원리로 정수값을 표현한다.




코드에서 정상적으로 변수에 올바른 값을 저장하더라도 프로그램이 실행하는 도중에 변수의 값은 변경된다. 만약 실행 중에 저장할 수 있는 값의 범위를 초과하면 최소값부터 다시 반복 저장되는데, byte일 경우 -128(최소값)부터 시작해서 127(최대값)을 넘으면 다시 -128부터 시작하게 된다. 또 다른 정수 타입인 short, int, long역시 저정할 수 있는 값의 범위를 넘어서면 이와 같은 방식으로 처리된다. 이와 같이 저장할 수 있는 값의 범위를 초과햇서 값이 저장될 경우 엉터리 값ㅅ이 변수에 저장되는데, 이러한 값을 쓰레기값이라고 한다. 개발자는 쓰레기값이 생기지 않도록 주의해야 한다. 다음 예제는 byte 변수와 int 변수를 각각 125로 초기화하고 5회에 걸쳐 1씩 더하기한 다음 출력한 결과를 보여준다.



byte 변수는 127을 넘어서는 순간 최소값인 -128부터 다시 저장되는 것을 볼 수 있고, int 타입의 변수는 정상적으로 1 증가된 값을 계속 저장하는 것을 볼 수 있다.


char 타입

자바는 모든 문자를 유니코드(Unicode)로 처리한다. 유니코드는 세계 각국의 문자들을 코드값으로 매핑한 국제 표준 규약이다. 유니코드는 하나의 문자에 대해 하나의 코드값을 부여하기 때문에 영문 'A' 및 한글 '가'도 하나의 코드값을 갖는다. 유니코드는 0 ~ 65535 범위의 2byte 크기를 가진 정수값이다. 0 ~ 127까지는 아스키(ASCII) 문자(특수기호 및 영어 알파벳)가 할당되어 있고, 44032 ~ 55203까지는 한글 11172자가 할당되어 있다. 유니코드에 대한 자세한 정보는 유니코드 홈페이지(http://www.unicode.org)에서 찾을 수 있다. 자바는 하나의 유니코드를 저장하기 위해 2byte 크기인 char 타입을 제공한다. 유니코드는 음수가 없기 때문에 char 타입의 변수에는 음수 값을 저장할 수 없다. char 타입에 저장할 수 있는 값은 0 ~ 65535까지 2^16개이다. char 타입 변수에 작은 따옴포(')로 감싼 문자를 대입하면 해당 문자의 유니코드가 저장된다. 예를 들어 'A', 'B', '가', '나' 문자를 char 변수에 저장할 경우 변수에 저장되는 유니코드 값은 다음과 같다.


char var1 = 'A'; //유니코드: 0x0041 → 2진수 : 00000000 01000001

char var2 = 'B'; //유니코드: 0x0042 → 2진수 : 00000000 01000010

char var3 = '가'; //유니코드: 0xAC00 → 2진수 : 10101100 00000000

char var4 = '나'; //유니코드: 0xAC01 → 2진수 : 10101100 00000001


char 변수에 작은 따옴표(')로 감싼 문자가 아니라 직접 유니코드 정수값을 저장할 수도 있다. 특정 문자의 유니코드를 안다면 10진수 또는 16진수로 저장하면 되는데, 예를 들어 문자 A는 10진수로 65이고, 16진수로 0x41이므로 다음과 같이 char 변수에 저장할 수 있다. 16진수로 저장할 경우에는 유니코드라는 의미에서 '\u + 16진수값' 형태로 값을 저장하면 된다.


char c = 65;

char c = '\u0041';


프로그램 코드엣서 char 변수에 저장된 유니코드를 알고 싶다면 char 타입 변수를 int 타입 변수에 저장하면 된다.


char c = 'A';

int uniCode = c;



위 예제의 실행 결과를 보면 System.out.println()은 변수의 타입이 char이면 유니코드에 해당하는 문자를 출력하는 것을 볼 수 있다. char 타입 변수는 단 하나의 문자만 저장한다. 만약 문자열을 저장하고 싶다면 String 타입을 사용해야 하는데, 다음과 같이 String 변수를 선언하고, 큰 따옴표(")로 감싼 문자열 리터럴을 대입하면 된다.


String name = "홍길동";


String은 기본 타입이 아니다. String은 클래스 타입이고 String 변수는 참조 변수이다. 문자열을 String 변수에 대입하면 문자열이 변수에 직접 저장되는 것이 아니라, String 객체가 생성되고, String 변수는 String 객체의 번지를 참조하게 된다. char 타입의 변수에 어떤 문자를 대입하지 않고 단순히 초기화를 할 목적으로 다음과 같이 작은 따옴표(') 두 개를 연달아 붙인 빈(empty) 문자를 대입하면 컴파일 에러가 발생한다. 그렇기 때문에 공백(유니코드:32) 하나를 포함해서 초기화해야 한다.


char c = ''; //컴파일 에러 → char c = ' ';


하지만 String 변수는 큰 따옴표(") 두 개를 연달아 붙인 빈 문자를 대입해도 괜찮다.


String str = "";


short 타입

short 타입은 2byte(16bit)로 표현되는 정수값을 저장할 수 있는 데이터 타입이다. 저장할 수 있는 값의 범위는 -32,768 ~ 32,767(-2^15 ~ 2^15-1)이다. C언어와의 호환을 위해 사용되며 비교적 자바에서는 잘 사용되지 않는 타입이다.


int 타입

int 타입은 4byte(32bit)로 표현되는 정수값을 저장할 수 있는 데이터 타입이다. 저장할 수 있는 값의 범위는 -2,147,483,648 ~ 2,147,483,647(-2^31 ~ 2^31-1)이다. int 타입은 자바에서 정수 연산을 하기 위한 기본 타입이다. 쉽게 설명하면 byte 타입 또는 short 타입의 변수를 + 연산하면 int 타입으로 변환된 후 연산되고 연산의 결과 역시 int 타입이 된다. 이것은 자바에서 정수 연산을 4byte로 처리하기 때문이다. 따라서 byte 타입이나 short 타입으로 변수를 선언한 것과 int 타입으로 변수를 선언한 것의 성능 차이는 거의 없다. 정수값을 직접 코드에서 입력할 경우 8진수, 10진수, 16진수로 표현할 수 있다. 8진수일 경우 숫자 앞에 '0'을 붙이면 되고, 16진수는 '0x'를 붙이면 된다. 다음은 10진수 10을 각각 8진수와 16진수로 표현해 변수에 저장한다.


int number = 10;

int octNumber = 012;

int hexNumber = 0xA;


변수에 어떤 진수로 입력을 하더라도 동일한 값이 2진수로 변환되어 저장된다. 10이 int 타입 변수에 저장되면 메모리에 생성되는 변수는 다음과 같다. int가 4byte의 크기를 가지기 때문에 4byte의 공간을 차지하면서 총 32bit로 10을 표현한다.


00000000 00000000 00000000 00001010

1byte      1byte       1byte      1byte


10은 1byte로 충분히 표현이 가능하기 때문에 나머지 상위 3byte의 bit 값은 모두 0이다.



long 타입

long 타입은 8byte(64bit)로 표현되는 정수값을 저장할 수 있는 데이터 타입이다. 저장할 수 있는 값의 범위는 -2^63 ~ 2^63-1이다. 수치가 큰 데이터를 다루는 프로그램에서는 long 타입이 필수적으로 사용된다. 대표적인 예가 은행 및 우주와 관련된 프로그램들이다. long 타입의 변수를 초기화할 때에는 정수값 뒤에 소문자 'l'이나 대문자 'L'을 붙일 수 있다. 이것은 4byte 정수 데이터가 아니라 8byte 정수 데이터임을 컴파일러에게 알려주기 위한 목적이다. int 타입의 저장 범위를 넘어서는 큰 정수는 반드시 소문자 'l'이나 대문자 'L'을 붙여야 한다. 그렇지 않으면 컴파일 에러가 난다. 일반적으로 'l'은 숫자 '1'과 비슷해 혼돈하기 쉬우므로 대문자 'L'을 사용한다.



7라인에서 에러(The literal 1000000000000 of type int is out of range)가 나는 이유는 int 타입의 저장 범위를 넘어서는 정수 리터럴에 'L'을 붙이지 않았기 때문이다.



2.2.3 실수 타입(float, double)

실수 타입은 소수점이 있는 실수 데이터를 저장할 수 있는 타입으로, 메모리 사용 크기에 따라 float과 double이 있다.


실수 타입

float

double

바이트 수

4

8


float과 double의 메모리 사용 크기는 각각 int와 long의 크기와 같지만, 정수 타입과는 다른 저장 방식 때문에 정수 타입보다 훨씬 더 큰 범위의 값을 저장할 수 있다. 실수는 정수와 달리 부동 소수점(floating-point) 방식으로 저장된다. 부동 소수점 방식은 실수를 다음과 같은 형태로 표현한 것을 말한다.


+      m    ×   10^n

    부호      가수(mantissa)     지수(exponent)


가수 m은 0 ≤ m < 1 범위의 실수이어야 한다. 예를 들어 실수 1.2345는 부동 소수점 방식으로 표현하면 0.12345 × 10^1이며, 가수는 0.12345이고 지수는 1이 된다. float 타입과 double 타입은 가수와 지수를 저장하기 위해 전체 bit를 나누어 사용한다. 다음은 float과 double 타입이 전체 bit를 어떻게 사용하는지 그림으로 보여준다.


float : 부호(1bit) + 지수(8bit) + 가수(23bit) = 32bit = 4byte

1

지수 (8bit)

가수 (23bit)


double : 부호(1bit) + 지수(11bit) + 가수(52bit) = 64bit = 8byte

1

지수 (11bit)

가수 (52bit)


위 그림에서 가수를 표현하는데 있어서 float에 비해 double이 약 두 배의 자릿수가 배정되어 있다. 따라서 float보다 double이 더 정밀한 값을 저장할 수 있기 때문에 높은 정밀도를 요구하는 계산에서는 double을 사용해야 한다. 자바는 실수 리터럴의 기본 타입을 double로 간주한다. 이 말은 실수 리터럴을 float 타입 변수에 그냥 저장할 수 없다는 뜻이다. 실수 리터럴을 float 타입 변수에 저장하려면 리터럴 뒤에 소문자 'f'나 대문자 'F'를 붙여야 한다.


double var1 = 3.14;

float var2 = 3.14;        //컴파일 에러(Type mismatch: cannot convert from double to float)

float var3 = 3.14F;


만약 정수 리터럴에 10의 지수를 나타내는 E또는 e를 포함하고 있으면 정수 타입 변수에 저장할 수 없고 실수 타입 변수에 저장해야 한다. 다음은 정수값 3000000을 저장하는 방법을 보여준다.


int var6 = 3000000;        //3000000

double var7 = 3e6;        //3000000

float var8 = 3e6f;          //3000000

double var9 = 2e-3;      //0.002



float과 double의 정밀도를 테스트하기 위해 var4와 var5에 값을 저장하고 출력해보니, double 타입인 var4가 float 타입인 var5보다 두 배 이상 정밀하게 값이 저장되어 있는 것을 볼 수 있다. 이것은 double 탑의 가수 bit 수가 float 타입의 가수 bit 수보다 약 두 배 정도 크기 때문이다.



2.2.4 논리 타입(boolean)

boolean 타입은 1byte(8bit)로 표현되는 논리값(true/false)을 저장할 수 있는 데이터 타입이다. boolean 타입은 두 가지 상태값을 저장할 필요성이 있을 경우에 사용되며, 상태값에 따라 조건문과 제어문의 실행 흐름을 변경하는데 주로 이용된다. 예를 들어 다음 코드를 보면 stop 변수에 true가 저장되어 있기 때문에 if 블록을 실행해서 "중지합니다."가 출력된다. 만약 stop 변수에 false가 저장되어 있다면 else 블록이 실행되어 "시작합니다."를 출력한다. stop 변수에 값을 변경 해보면서 출력 내용을 확인해보길 바란다.



2.1.1 변수란?

프로그램은 작업을 처리하는 과정에서 필요에 따라 데이터를 메모리에 저장한다. 이때 변수를 사용하는데, 변수(Variable)는 값을 저장할 수 있는 메모리의 공간을 의미한다. 변수란 이름을 갖게 된 이유는 프로그램에 의해서 수시로 값이 변동될 수 있기 때문이다. 변수에는 복수 개의 값을 저장할 수 없고, 하나의 값만 저장할 수 있다.

변수란, 하나의 값을 저장할 수 있는 메모리 공간이다.

변수에는 다양한 타입의 값을 저장할 수 없고, 한 가지 타입의 값만 저장할 수 있다. 예를 들어 정수 타입 변수에는 정수값만 저장할 수 있고, 실수 타입 변수에는 실수값만 저장할 수 있다.



2.1.2 변수의 선언

변수를 사용하기 위해서는 먼저 변수를 선언해야 한다. 변수 선언은 어떤 타입의 데이터를 저장할 것인지 그리고 변수 이름이 무엇인지를 결정한다. 변수 선언은 다음과 같다.


         ↙타입         ↙변수이름;

int                age;        //정수(int)값을 저장할 수 있는 age 변수 선언

double          value;      //실수(double)값을 저장할 수 있는 value 변수 선언


타입은 변수에 저장되는 값의 종류와 범위를 결정짓는 요소이기 때문에 어떤 값을 변수에 저장할지 충분히 생각한 다음 결정해야 한다. 예를 들어 변수에 정수를 저장하고 싶다면 정수 타입(int)을 사용할 수 있고, 실수를 저장하고 싶다면 실수 타입(double)을 사용할 수 있다. 같은 타입의 변ㄴ수는 콤마(,)를 이용해서 한꺼번에 선언할 수도 있다. 다음은 정수 타입 변수 x, y, z를 선언한 예를 보여준다.


int x, y, z;


변수 이름은 메모리 주소에 붙여진 이름이다. 프로그램은 변수 이름을 통해서 메모리 주소에 접근하고, 그곳에 값을 저장하거나 그곳에 있는 값을 읽는다. 변수 이름은 자바 언어에서 정한 명명 규칙(naming convention)을 따라야 한다. 변수 명명 규칙은 다음과 같다.


작성 규칙

예 

첫 번째 글자는 문자이거나 '$', '_' 이어야 하고 숫자로 시작할 수 없다.(필수) 

가능: price, $price, _companyName

안됨: 1v, @speed, $#value 

영어 대소문자가 구분된다.(필수) 

firstname과 firstName은 다른 변수 

첫 문자는 영어 소문자로 시작되는 다른 단어가 붙을 경우 첫 문자를 대문자로 한다.(관례) 

maxSpeed, firstName, carBodyColor 

문자 수(길이)의 제한은 없다. 

 

 자바 예약어는 사용할 수 없다.(필수)

아래 표 참조 


자바는 다음 표에 언급되어 있는 예약어를 가지고 있다. 이 예약어들로 변수 이름을 지정하면 컴파일 에러가 발생하기 때문에 주의해야 한다.


분류 

예약어 

기본 데이터 타입 

boolean, byte, char, short, int, long, float, double 

접근 지정자 

private, protected, public 

클래스와 관련된 것 

class, abstract, interface, extends, implements, enum 

객체와 관련된 것 

new, instanceof, this, super, null 

메소드와 관련된 것

void, return 

제어문과 관련된 것 

if, else, switch, case, default, for, do, while, break, continue 

논리값 

true, false 

예외 처리와 관련된 것

try, catch, finally, throw, throws 

기타

transient, volatile, package, import, synchronized, native, final, static, strictfp, assert 



개발자는 변수 이름을 보고, 이 변수가 어떤 값을 저장하고 있는지 쉽게 알 수 있도록 의미 있는 변수 이름을 지어주는 것이 좋다. 변수 이름의 길이는 프로그램 실행과는 무관하기 때문에 충분히 길어도 상관없다. 필수적인 것은 아니지만 명명 규칙과 관련된 자바 개발자들 간에 지켜져오는 관례가 있는데, 관례란 필수적인 것은 아니지만 개발자 간의 코드 작성 패턴을 공유하고자 하는 약속이기때문에 가급적 지켜주는 것이 좋다. 변수 이름에 한글도 사용이 가능하지만 가급적이면 한글을 포함하지 않는 것이 좋다. 한국어를 모르는 다른 사람도 소스 코드를 볼 수 있기 때문이다.



2.1.3 변수의 사용

변수를 사용한다는 것은 변수에 값을 저장하고 읽는 행위를 말한다. 변수에 값을 저장하는 방법과 변수에 저장된 값을 읽는 방법에 대해 알아보기로 하자.


변수값 저장

변수에 값을 저장할 때에는 대입 연산자(=)를 사용한다. 일반 수학에서 =은 같다는 의미지만, 자바언어에서는 우측의 값을 좌측 변수에 저장한다는 의미를 갖는다. 변수를 선언하고 처음 값을 저장할 경우, 이러한 값을 초기값이라고 한다. 그리고 변수에 초기값을 주는 행위를 변수의 초기화라고 한다. 예를 들어, 90이라는 값을 변수 score에 저장하려면 먼저 정수 타입(int)으로 score 변수를 선언하고 초기값 90을 다음과 같이 기술하면 된다.

int score;        // 변수 선언

score = 90;     // 값 저장


초기값은 변수를 선언함과 동시에 줄 수도 있다.

int score = 90;


변수의 초기값은 코드에서 직접 입력하는 경우가 많은데, 소스 코드 내에서 직접 입력된 값을 리터럴(literal)이라고 부른다. 리터럴은 값의 종류에 따라 정수 리터럴, 실수 리터럴, 문자 리터럴, 논리 리터럴로 구분된다. 이 리터럴들은 정해진 표기법대로 작성되어야 한다. 사실 리터럴은 상수(constant)와 같은 의미지만, 프로그램에서는 상수를 "값을 한 번 저장하면 변경할 수 없는 변수"로 정의하기 때문에 이와 구분하기 위해 "리터럴"이라는 용어를 사용한다.


|정수 리터럴|

소수점이 없는 정수 리터럴은 10진수로 간주한다.

0, 75, -100


0으로 시작되는 리터럴은 8진수로 간주한다.

02, -04


0x 또는 0X로 시작하고 0~9 숫자나 A, B, C, D, E, F 또는 a, b, c, d, e, f로 구성된 리터럴은 16진수로 간주한다.

0x5, 0xA, 0xB3, 0xAC08


정수 리터럴은 저장할 수 있는 타입은 byte, char, short, int, long과 같이 5개가 있다.


|실수 리터럴|

소수점이 있는 리터럴은 10진수 실수로 간주한다.

0.25, -3.14


대문자 E 또는 소문자 e가 있는 리터럴은 10진수 지수와 가수로 간주한다.

5E7            // 5 x 10^7

0.12E-5       // 0.12 x 10^-5


실수 리터럴을 저장할 수 있는 타입은 float, double이 있다.


|문자 리터럴|

작은 따옴포(')로 묶은 텍스트는 하나의 문자 리터럴로 간주한다.

'A', '한', '\t', '\n'

역슬래쉬(\)가 붙은 문자 리터럴은 이스케이프(escape) 문자라고도 하는데, 다음과 같이 특수한 용도로 사용된다.


이스케이프 문자

용도

유니코드

'\t' 

수평 탭 

0x0009 

'\n'

줄 바꿈

0x000a

'\r'

리턴 

0x000d 

'\"'

"(큰따옴표)

0x0022 

'\'

'(작은따옴표)

0x0027 

'\\'

0x005c 

'\u16진수'

16진수에 해당하는 유니코드

0x0000 ~ 0xffff 


문자 리터럴을 저장할 수 있는 타입은 char 하나뿐이다.


|문자열 리터럴|

큰따옴표(")로 묶은 텍스트는 문자열 리터럴로 간주한다. 큰따옴표 안에는 텍스트가 없어도 문자열 리터럴로 간주된다. 문자열 리터럴 내부에서도 이스케이프 문자를 사용할 수 있다.

" 대한민국 "

" 탭 만큼 이동 \t 합니다. "

" 한줄 내려 쓰기 \n 합니다. "

문자열 리터럴을 저장할 수 있는 타입은 String 하나뿐이다.


|논리 리터럴|

true와 false는 논리 리터럴로 간주한다.

true, false

논리 리터럴을 저장할 수 있는 타입은 boolean 하나뿐이다.


리터럴들은 변수의 초값으로 소스 코드에서 많이 나타난다.


변수값 읽기

변수는 초기화가 되어야 읽을 수가 있고, 초기화되지 않은 변수는 읽을 수가 없다. 다음은 잘못된 코딩 예를 보여준다.

int value;                                // 변수 value 선언 (초기화 안 됨)

int result = value + 10;              // 변수 value 값을 읽고 10을 더한 결과값을 변수 result에 저장


변수 value가 선언되었지만, 초기화가 되지 않았기 때문에 산술 연산식 value + 10에서 사용할 수 없다. 이런 경우 컴파일 에러가 발생한다. 위 코드는 다음과 같이 변경해야 한다.

int value = 30;                          // 변수 value가 30으로 초기화됨

int result = value + 10;              // 변수 value 값을 읽고 10을 더한 결과값(40)을 변수 result에 저장


[VariableExample.java] 변수 선언과 초기화




2.1.4 변수의 사용 범위

변수는 중괄호 { } 블록 내에서 선언되고 사용된다. 중괄호 블록을 사용하는 곳은 클래스, 생성자, 메소드이다.

메소드 블록 내에서 선언된 변수를 특히 로컬 변수(local variable)라고 부른다. 로컬 변수는 메소드 실행이 끝나면 메모리에서 자동으로 없어진다. 다음은 main() 메소드 블록에서 변수가 선언된 모습을 보여준다.


public class VariableExample {                                                        // 클래스 블록

public static void main(String[] args) {                                            // 메소드 블록

int value = 10;                    // 변수 선언 및 초기값 저장

int sum =value + 20;           // 변수 선언 및 초기값 저장

System.out.println(sum);

}

}


변수는 메소드 블록 내 어디에서든 선언할 수 있지만, 변수 사용은 제한이 따른다. 기본적으로 변수는 다음과 같은 사용 규칙 있다.

변수는 선언된 블록 내에서만 사용이 가능하다.


메소드 블록 내에서도 여러 가지 중괄호 { } 블록들이 있을 수 있다. 조건문에 해당하는 if() { }, 반복문에 해당하는 for() { }, while() { } 등이 중괄호를 가질 수 있다. 이러한 if, for, while을 제어문이라고 하는데, 제어문 블록에서 선언된 변수는 해당 제어문 블록 내에서만 사용이 가능하고 블록 밖에서는 사용할 수 없다.


public static void main(String[] args) {                                            // 메소드 블록

int var1; ---------------------------------------- 메소드 블록에서 선언


if(...) {                                                                                        // if 블록

int var2; ---------------------------------------- if 블록에서 선언

// var1과  var2 사용 가능

}


for (...) {                                                                                    // for 블록

int var3; ---------------------------------------- for 블록에서 선언

}


// var1 사용 가능

// var2와 var3는 사용 못함

}


따라서 변수를 선언할 때에는 변수가 어떤 범위에서 사용될 것인지를 생각하고, 선언 위치를 결정해야 한다. 메소드 블록에서 어떤 위치에서건 사용할 수 있도록 한다면 메소드 블록 첫머리에 선언하는 것이 좋을 것이다. 만약 제어문 내에서 잠깐 사용되는 변수라면 제어문 내에서 선언하는 것이 좋다.



9라인에서 컴파일 에러가 발생했는데, v2가 선언된 곳은 if 블록 내부인 7라인이므로 if 블록 바깥쪽인 9라인에서는 사용할 수 없다. 에러 메시지는 "v2 cannot be resolved to a variable"라고 출력되는데 이것은 변수 v2를 해석할 수 없다는 뜻이다. 하지만 v1은 5라인에서 선언되었기 때문에 main() 메소드 내부 어디에서든 사용 가능하다.

1.6.1 이클립스 소개

지금까지 메모장에서 자바 소스 파일을 작성하고, 명령 프롬프트에서 컴파일과 실행을 해보았다. 단순한 프로그램일 경우에는 이런 방법도 괜찮지만 복잡한 프로그램을 개발할 경우, 개발자의 코딩 실수를 줄여주기 위한 키워드의 색깔 구분, 자동 코드 완성 기능 및 디버깅 기능을 갖춘 소스 편집 툴을 사용하는 것이 좋다. 디버깅(debugging)이란 모의 실행을 해서 코드의 오류를 찾는 것을 말한다. 2003년도 이전까지는 자바 프로그래 개발용 편집 툴로 EditPlus, UltraEdit, Jpadpro, Kawa, JCreator, JBuilder, Visual Cafe 등을 사용했는데, 종류가 너무 많아서 개발자들이 어떤 툴을 사용하면 좋을지 고민을 많이 했다. EditPlus와 UltraEdit는 자바 키워드의 색깔 구분까지는 지원하지만 단순한 문자 에디터이므로 프로그램 소스 작성 중에 개발자에게 많은 도움을 주지 못하고 컴파일하기 전까지는 무엇을 잘못 작성했는지 알 수 없다. 좋은 편집 툴의 조건은 프로그램 소스 작성 시 풍부한 기능을 제공하면서, 동시에 코드를 체크하여 개발자에게 보다 정확한 코딩을 유도하도록 도와주는 것이다. 그리고 실행하는 동안 오류를 찾아내는 디버깅 기능이 있다면 더할 나위 없이 좋다. 이런 기능이 탑재된 편집 툴인 이클립스(Eclipse)가 2003년도에 IBM에서 개발되었다.

이클립스는 자바 프로그램을 개발하기 위한 통합 개발 환경(IDE: Integrated Development Environments)을 제공하는데, 프로젝트 생성, 자동 코드 완성, 디버깅 기능을 가지고 있다. 이클립스 초기 버전이 발표된 이후로 지속적인 버전업을 위해 이클립스 연합(Eclipse Foundation)이 설립되었고, 그로부터 이클립스의 기능은 눈부시게 발전되었다. 이클립스는 오픈소스 개발 플랫폼으로 무료로 제공된다. 기본적으로 자바 프로그램을 개발할 수 있도록 구성되어 있지만, 개발자가 추가적으로 플러그인(plugin)을 설치하면 안드로이드 앱 개발, 웹 애플리케이션 개발, C, C++, C# 애플리케이션 개발 등 다양한 개발 환경을 구축할 수 있다. 현재 이클립스는 초급 개발자부터 고급 개발자에 이르기까지 광범위하게 사용되고 있고, 기업체에서 가장 선호하는 개발 전문 툴이다.



1.6.2 이클립스 다운로드

이클립스는 자바 언어로 개발된 툴이기 때문에 이클립스를 실행하려면 JVM이 필요하다. 이미 JDK를 설치했기 때문에 이클립스 압축 파일만 다운로드 받으면 된다. 이클립스 압축 파일은 "http://www.eclipse.org" 사이트에서 무료로 받을 수 있다.

순수 자바를 학습하기 위해서는 "Eclipse IDE for Java Developers"를 받으면 되지만, 웹 애플리케이션 등의 엔터프라이즈(네트워크) 환경에서 실행되는 자바 애플리케이션을 개발하기 위해서는 "Eclipse IDE for Java EE Developers"를 받아야 한다. 현업에서는 "Eclipse IDE for Java EE Developers"를 가지고 개발한다. 다운로드한 압축 파일을 해제하면 eclipse라는 디렉토리가 나오는데, 이 디렉토리 안에 있는 eclipse.exe 파일을 더블클릭하면 이클립스가 실행된다.




1.6.3 워크스페이스

이클립스를 실행하면 제일 먼저 만나는 [Workspace Launcher] 대화상자가 나타난다. [Workspace]는 이클립스에서 생성한 프로젝트가 기본적으로 저장되는 디렉토리를 말하는데, 기본적으로 로그인한 계정의 홈 디렉토리에 workspace라는 이름으로 지정된다. 다른 디렉토리를 지정하고 싶다면 [Browse...] 버튼을 클릭해서 변경하면 된다.



이클립스는 실행할 때 적용되는 메타데이터를 워크스페이스의 하위 디렉토리인 .metadata에 저장하는데, 처음 워크스페이스가 생성되면 이 디렉토리가 자동으로 생성된다. 이클립스를 사용하면서 변경되는 속성값들은 이 디렉토리에 차곡차곡 기록되는데, 동일한 워크스페이스에서 이클립스가 재시작될 경우 이전에 작업한 환경으로 복원할 목적으로 사용된다. 경우에 따라서는 이클립스를 초기 상태로 되돌리는 경우도 있는데, 이때에는 .metadata 디렉토리를 강제로 삭제하고 재시작하면 된다. [Workspace Launcher] 대화상자의 하단에 있는 "Use this as the default and do not ask again" 체크 박스를 체크하면, 재시작 시 [Workspace Launcher] 대화상자가 실행되지 않는다. 만약 워크스프에이스를 변경하고 싶다면 이클립스의 [File → Switch Workspace → Other...] 메뉴를 통해 변경하면 된다.



1.6.4 퍼스펙티브와 뷰

퍼스펙티브(Perspective)는 이클립스에서 프로젝트를 개발할 때 유용하게 사용하는 뷰(view)들을 묶어 놓은 것을 말한다. 뷰는 이클립스 내부에서 사용되는 작은 창을 말한다. 기본적으로 이클립스(Eclipse IDE for Java EE Developers)는 Java EE 퍼스펙티브를 보여준다. 자바를 처음 학습할 때는 Java EE 퍼스펙티브보다는 Java 퍼스펙티브로 변경하는 것이 좋다. Java 퍼스펙티브를 변경하려면 메뉴에서 [Window → Open Perspectives → Java]를 선택하면 된다.

Java 퍼스펙티브에서 제일 많이 사용하는 뷰는 Package Explorer 뷰이다. Package Explorer 뷰에서는 프로젝트를 관리하고, 자바 소스 파일을 생성 및 삭제하는 작업을 한다. 그리고 Console 뷰도 많이 사용되는데, Console 뷰는 프로그램에서 콘솔로 출력하는 내용을 보여준다. 만약 Java 퍼스펙티브에서 Console 뷰가 보이지 않는 다면 메뉴에서 [Window → Show View → Console]을 선택하면 된다.



1.6.5 프로젝트 생성

이클립스에서 자바 소스 파일을 작성하려면 우선 자바 프로젝트를 생성해야 한다. 메뉴에서 [File → New → Java Project]를 차례대로 클릭하면 [New Java Project] 대화상자가 나타나는데, [Project name] 입력란에 적절한 프로젝트명을 입력하면 된다. 프로젝트는 기본적으로 워크스페이스의 하위 디렉토리로 생성된다. 위치를 변경하고 싶다면 "Use default location"을 체크 해제하고 [Browse...] 버튼으로 선택하면 된다.



JRE는 프로젝트를 실행할 때 사용할 버전을 말하는데, 기본적으로 JavaSE-1.8로 되어 있다. 이것은 Path 환경 변수 값과 관련이 있는데, Path 환경 변수 값에 추가된 JDK의 버전이 디폴트로 설정된다. Project layout에는 자바 소스 파일(~.java)과 컴파일된 바이트 코드 파일(~.class)을 분리 저장하도록 설정되어 있다. 자바 소스 파일은 src 디렉토리에, 바이트 코드 파일은 bin 디렉토리에 기본적으로 저장된다. Package Explorer 뷰에서는 src 디렉토리만 보이기 때문에 윈도우 탐색기로 프로젝트 디렉토리를 살펴보면 bin 디렉토리를 볼 수 있다. [New Java Project] 대화상자에서 [Project name]으로 "chap01"을 입력하고, 나머지는 그대로 둔 채 [Finish] 버튼을 클릭하자.



1.6.6 소스 파일 생성과 컴파일

Package Explorer 뷰에서 chap01 프로젝트의 src 폴더를 선택하고 [마우스 오른쪽 버튼 → New → Class]를 선택하면 [New Java Class] 대화상자가 나온다.(추후 설명) 1.4.1 소스 작성에서부터 실행까지에서 만들어 보았던 Hello.java와 동일한 소스 파일을 만들어 보자. [Package] 입력란에 "sec01.exam01"이라고 입력한다. Package는 클래스를 포함하는 그룹명이라고 생각하자. [name] 입력란에 "Hello"를 입력한 후, [Finish] 버튼을 클릭하자. 그러면 src 폴더 밑에 Hello.java가 생성되고, Hello.java를 편집할 수 있도록 에디터가 열린다.



소스 파일을 작성하다 보면 에디터의 행 번호가 유용하게 사용될 경우도 있다. 한 예로 컴파일 오류가 발생했을 경우 행 번호를 모른다면 쉽게 코드 위치를 찾기 힘들다. 에디터의 행 번호를 보고 싶다면 메뉴에서 [Window → Preferences]를 클릭하고 트리 메뉴에서 [General → Editors → Text Editors]를 선택한 후, "Show line numbers"를 체크하면 된다.



1.4.1 소스 작성에서부터 실행까지에서 만들어 보았던 Hello.java와 동일한 코드를 에디터에서 작성해보자. 이데터는 자바 키워드를 색깔로 구분해주고, 점(.)을 찍을 때마다 코드 선택 박스도 보여줌으로써 개발자가 오타를 덜 발생시키도록 도와준다.



이클립스는 컴파일을 위한 메뉴가 따로 없다. 저장을 하면 내부적으로 javac Hello.java가 자동 실행되어 컴파일을 수행한다. 그렇기 때문에 소스 작성 및 수정 후에는 저장하는 습관을 갖는 것이 좋다. 컴파일이 성공되면 bin 디렉토리에 바이트 코드 파일이 생성된다. bin 디렉토리는 Package Explorer에서는 보이지 않는다. bin 디렉토리를 보고 싶다면 윈도우 탐색기로 프로젝트 디렉토리를 살펴보든지, 아니면 이클립스에서 Navigator 뷰를 추가하면 되는데, 이클립스 메뉴에서 [Window → Show View → Navigator]를 선택하면 된다.



1.6.7 바이트 코드 실행

이클립스에서 바이트 코드 파일을 실행하는 방법은 아주 간단핟다. Package Explorer 뷰에서 실행을 원하는 소스 파일을 선택하고, [마우스 오른쪽 버튼 → Run As → Java Application]을 클릭하거나, 툴바에서 실행 버튼을 클릭하면 된다. 그러면 내부적으로 java.exe가 실행되고, JVM은 컴파일된 바이트 코드를 실행한다. Package Explorer 뷰에서 Hello.java를 선택하고 실행 버튼을 클릭해보자. 내부적으로 java Hello가 실해외고, 출력되는 문자열은 Console 뷰에 다음과 같이 나타난다.



1.5.1 주석 사용하기

주석은 프로그램 실행과는 상관없이 코드에 설명을붙인 것을 말한다.컴파일 과정에서 주석은 무시되고 실행문만  바이트 코드로 번역된다. 따라서 주석을 많이 작성한다고 해서 전체 프로그램의 크기가 커지는 것은 아니므로 가급적이면 설명이 필요한 코드에 주석을 달아 두는 것이 좋다. 복잡한 코드일수록 주석을 달면 전체 코드를 이해하기 쉽고, 수정이 용이하다. 특히 다른 사람이 작성한 코드를 주석 없이 해석하는 작업은 쉬운 일이 아니다. 본인이 작성한 코드를 다른 사람이 볼 필요가 있다면 주석을 꼭 넣어주는 것이 좋다. 주석의 내용에는 작성자, 작성일과 시간, 프로그램의 버전과 변경 이력 등의 정보, 주요 코드에 대한 설명 등이 포함된다. 코드에서 사용하는 주석문의 종류에는 다음과 같이 두 가지가 있다.


 주석 기호

 설명 

 // 

 //부터 라인 끝까지 주석으로 처리한다. (행 주석)

 /* ~ */

 /*와 */ 사이에 있는 모든 범위를 주석으로 처리한다. (범위 주석) 


주석문은 코드 내 어디서든 작성이 가능하지만, 문자열(" ")내부에는 올 수 없다. 문자열 안에서의 주석문은 주석이 아니라 문자열 데이터로 인식된다. 따라서 다음과 같이 주석을 붙이면 안 된다.

System.out.println("Hello, /*주석이 될 수 없음*/welcome to the java world!");

행 주석(//)과 범위 주석(/* ~ */)을 사용해서 Hello.java 소스 파일을 다음과 같이 수정해보자.



행 주석(//)과 범위 주석(/* ~ */) 외에도 자바 API 도큐먼트 문서에 포함되는 도큐먼트 주석(/** ~ */)도 있다. 도큐먼트 주석은 javadoc.exe 명령어로 API 도큐먼트를 생성하는데 사용된다.



1.5.2 실행문과 세미콜론(;)

실행문은 변수 선언, 값 저장, 메소드 호출에 해당하는 코드를 말한다. 실행문을 작성할 때 주의할 점은 실행문의 마지막에 반드시 세미콜론(;)을 붙여서 실행문이 끝났음을 표시해주어야 한다. 다음은 실행문을 작성한 예를 보여준다.



컴파일러는 세미콜론(;)이 나올 때까지 실행문이 계속해서 연결된 것으로 보기 때문에 한 실행문이 여러 줄에 걸쳐 있어도 되고, 한 줄에 여러 개의 실행문이 있어도 된다. 예를 들어, 앞의 코드를 다음과 같이 바꾸어 작성해도 전혀 문제가 없다.



지금으로써는 변수 선언이나 메소드 호출과 같은 실행문을 이해하는 것은 힘들지만 앞으로 이 책으로 학습하다 보면 저절로 알게 될 테니 걱정하지 않아도 된다. 실행문에 세미콜론(;)을 붙이는 연습을 할 겸 다음 소스 파일을 C:\Temp 디렉토리에 생성해서 실행해보자.




1.4.1 소스 작성에서부터 실행까지

자바 프로그램을 개발하려면 다음과 같은 순서로 진행해야 한다.

.java 소스 파일 작성 → 컴파일러(javac.exe)로 바이트 코드 파일(.class) 생성  → JVM 구동 명령어(java.exe)로 실행

자바 프로그램을 개발하기 위해서는 우선 파일 확장명이 .java인 텍스트 파일을 생성하고 프로그램 소스를 작성한다. 이렇게 만들어진 파일을 자바 소스 파일이라고 한다. 작성 완료된 자바 소스 파일은 컴파일러(javac.exe)로 컴파일해야  한다. 컴파일이 성공되면 확장명이 .class인 바이트 코드 파일이 생성된다. 예를 들어, 명령 프롬프트에서 Hello.java 소스 파일을 다음과 같이 컴파일하면 Hello.class 파일이 생성된다.

javac Hello.java

바이트 코드 파일은 완전한 기계어가 아니므로 단독으로 실행할 수 없고 JVM이 실행되어야 한다. JVM을 구동시키는 명령어는 java.exe이다. 예를 들어 Hello.class라는 바이트 코드 파일을 java.exe로 실행하려면 명령 프롬프트에서 다음과 같이 입력하고 Enter키를 누르면 된다. 주의할 점은 java.exe로 바이트 코드 파일을 실행할 때는 .class 확장명을 제외한 이름을 입력해야 한다.

java Hello

java.exe 명령어가 실행되면 JVM은 바이트 코드 파일(Hello.class)을 메모리로 로드하고, 최적의 기계어로 번역한다. 그리고 main() 메소드를 찾아 실행시킨다.


소스 작성에서부터 실행까지의 순서를 이해했다면, 다음 순서대로 실습을 진행해보자.


step 1. 메모장을 실행하고 다음과 같이 [파일]-[다른 이름으로 저장] 메뉴를 선택한다.


step 2. 저장 위치를 C:\Temp 디렉토리로 변경한다. 만약 C:\Temp 디렉토리가 없다면 새로 생성하길 바란다. 파일 이름에 "Hello.java"를 입력하고 [저장] 버튼을 클릭한다. 이때 H를 반드시 대문자로 입력한다.



step 3. 다음과 같이 자바 소스 파일을 작성하고 저장한다.


[Hello.java] 자바 소스 파일

public class Hello {

public static void main(String[] args) {

System.out.println("Hello, welcome to the java  world!");

}

}


위와 같은 소스 파일을 작성할 때는 다음과 같은 사항들을 주의해야 한다.

  • 1라인에서 Hello의 H가 대문자로 작성되어야 한다(파일명과 대소문자가 동일해야 한다).

  • 2라인에서 String의 S가 대문자로 작성되어야 한다.

  • 3라인에서 System의 S가 대문자로 작성되어야 한다.

  • 3라인 끝에 세미콜론(;)을 붙여준다.


step 4. 명령 프롬프트를 실행시키고,C:\Temp 디렉토리로 이동하기 위해 다음과 같이 입력한 후 Enter키를 누른다.



step 5. C:\Temp 디렉토리에 Hello.java 소스 파일이 있는지 확인하기 위해 dir 명령어를 실행해본다.



step 6. 컴파일러로 Hello.java 소스 파일을 다음과 같이 컴파일한다.



step 7. 상기 명령을 실행한 후 에러 메시지가 출력되지 않았다면 컴파일이 정상적으로 완료된  것이다. dir 명령어를 다시 실행해서 Hello.class가 생성되었는지 확인한다.



step 8. Hello.class를 실행하기 위해 JVM 구동 명령어인 java.exe를  다음과 같이 입력하고 Enter키를  누른다.



주의할 점은  프로그램을 실행할 때에 java Hello.class와 같이 바이트 코드의 확정명을 포함해서는 안 된다. 또한 바이트 코드 파일명과 대소문자가 일치해야 한다. 즉  H를 소문자로 입력하면 안 된다.  처음 자바를 배우는 분들이 가장 많이 실수하는 부분이다.



1.4.2 프로그램 소스 분석

Hello 프로그램의 소스 작성에서부터 실행까지 해보았다면, 이제 이 프로그램이 어떻게 해서 "Hello, welcome to the java world!"를 출력할 수 있었는지 살펴보자. 자바 실행 프로그램은 반드시 클래스(class) 블록과 main() 메소드(method) 블록으로 구성되어야 한다.  메소드 블록은 단독으로 작성될 수 없고 항상 클래스 블록 내부에서 작성되어야 한다. 클래스와 메소드를 간단하게 설명하면 다음과 같다.

  • 클래스 : 필드 또는 메소드를 포함하는 블록
  • 메소드 : 어떤 일을 처리하는 실행문들을 모아 놓은 블록


클래스에는 이름이 있는데, Hello가 클래스 이름이고, 그 다음에 있는 중괄호( { )부터 그와 짝을 이루는 중괄호( } )까지가 클래스 블록이다.


↙ 클래스 이름

public class Hello {


}



↘ 클래스 블록


클래스의 이름은 개발자가  마음대로 정할 수 있다.주의할 점은 소스 파일명과 대소문자가  일치해야 한다. 그리고 숫자로 시작할 수 없고, 공백을 포함해서도 안 된다. 클래스 블록에는 메소드를 작성할 수 있는데, 메소드는 클래스처럼 이름과 블록을 가진다. main이 메소드 이름이고, 중괄호( } )부터 그와 짝을 이루는 중괄호( } )까지가 메소드 블록이다.


↙ 메소드 이름

public static void main(String[] args) {

    System.out.println("Hello, welcome to the java world!")

}



↘ 메소드 블록


메소드 이름도 개발자가 마음대로 정할 수 있지만 main() 메소드 만큼은 다른 이름으로 바꾸면 안된다. 왜냐하면 java.exe로 JVM을 구동시키면 제인 먼저 main() 메소드를 찾아서 실행시키기 때문이다. 그래서 main() 메소드를 프로그램 실행 진입점(entry point)이라고 한다. 만약 클래스 내부에 main() 메소드가 없거나 잘못 작성하면 클래스를 실행할 수 없다. main() 메소드가 없는 클랫스를 java.exe로 실행시키면 다음과 같은 에러 메시지가 나타난다.


1.3.1 자바 개발 도구(JDK) 설치

자바 프로그램을 개발하기 위해서는 먼저 Java SE(Standard Edition)의 구현체인 JDK를 설치해야 한다. Java SE의 구현체는 자바 개발 키트(JDK: Java Development Kit)와 자바 실행 환경(JRE: Java Runtime Environment)이라는 두 가지 버전이 있다. JDK는 프로그램 개발에 필요한 자바 가상 기계(JVM), 라이브러리 API, 컴파일러 등의 개발 도구가 포함되어 있고, JRE에는 프로그램 실행에 필요한 자바 가상 기계(JVM), 라이브러리 API만 포함되어 있다. 자바 프로그램을 개발하고자 하는 것이 아니고, 이미 개발된 프로그램만 실행한다면 JRE만 설치하면 된다.

  • JRE = JVM + 표준 클래스 라이브러리
  • JDK = JRE + 개발에 필요한 도구

JDK는 오라클(http://www.oracle.com) 사이트에서 무료로 다운로드 받을 수 있다.

JDK는 운영체제별로 설치 파일을 별도로 제공하고 있기 때문에 운영체제에 맞게 설치 파일을 다운로드하면 된다. MS 윈도우라면 Windows x86(32비트 CPU) 또는 Windows x64(64비트 CPU)의 파일을 선택해서 다운로드하면 된다.

JDK를  설치하면 기본 위치는 C:\Program Files\Java인데, 이 디렉토리를 보면 JDK와 함께 JRE도 설치되어 있는 것을 볼 수 있다. JDK 내부에도 JRE가 있기 때문에 자바 프로그램을 개발하고 실행할 수 있다. 그러나 웹 브라우저에서 실행하는 애플릿(Applet)은 JRE를 요구하기 때문에 애플릿을 개발한다면 JRE도 필요한다.

JDK 내부의 bin 디렉토리는 컴파일러인 javac.exe와 자바 가상 기계(JVM) 구동 명령어인 java.exe가 포함되어 있다. 이 명령어들은 자바 프로그램 개발 시 아주 빈번히 사용된다. 이명령어들을 다른 디렉토리에서도 쉽게 실행할 수 있도록 하기 위해 Path 환경 변수에 bin 위치를 등록할 필요가 있다.



1.3.2 API 도큐먼트

자바 프로그램을 개발하기 위해서는 JDK에서 제공하는 표준 클래스 라이브러리를 반드시 사용해야 한다. 이 클래스는 API(Application Programming Interface)라고도 하는데, JDK에 포함되어 있는 API들은 매우 방대하기 때문에 쉽게 찾을 수 있도록 API 도큐먼트를 제공한다. API 도큐먼트는 HTML 페이지로 작성되어 있어 웹 브라우저로 "http://docs.oracle.com/javase/8/docs/api/"에 접속해보면 볼 수 있다.

1.2.1 자바소개

1995년 도에처음 썬 마이크로시스템즈(Sun Microsystems)에서 자바(Java)언어를 발표한 후, 지금까지 자바는 성공한 프로그래밍 언어로서 전세계적으로 다양한 분야에서 사용되고 있다. 자바는  1991년에 썬의 엔지니어들에 의해서 고안된 오크(Oak)라는  언어에서부터 시작되었다. 오크는 처음에는 가전제품에서 사용될 목적이었다. 그 후 인터넷의  등장과  함께 인터넷에서 실행되는 프로그래밍 언어로 사용되었고, 이름도 자바로 변경되었다.


자바 언어를 발표한 1995년부터 1999년까지는 윈도우(Windows) 프로그램 개발이 주류였기 때문에 C++ 언어에 비해 자바는 아주 열세였다. 메모리 및 CPU를 지나치게 많이 사용하기 때문에 윈도우 프로그래밍 언어로는 부적합하다는 문제점이 있었다. 하지만 1999년도부터 인터넷이 활성화되면서 웹 애플리케이션 구축용 언어로 자바가 급부상했다. 그 이유는 기업체 및 공공기관의 다양한 서버 운영체제에서 단 한번의 작성으로 모든 곳에서 실행 가능한 언어는 자바뿐이었기 때문이다.


초기의 자바는 가전 제품에 탑재할 프로그래밍 언어로 개발되었지만, 지금은 스마트폰을 비롯해서 각종 장비와 데스크톱에서 실행되는 애플리케이션, 그리고 금융, 공공, 대기업 등의 엔터프라이즈 기업 환경에서 실행되는 서버 애플리케이션을 개발하는 중추적인 언어로 자리매김하고 있다.



1.2.2 자바의 특징

이식성이 높은 언어이다.

이식성이란 서로 다른 실행 환경을 가진 시스템 간에 프로그램을 옮겨 실행할 수 있는 것을 말한다. 예를 들어 MS 윈도우 (Microsoft Windows)에서 실행하는 프로그램을 리눅스 또는 유닉스에서 실행할 수 있다면 이식성이 높은 것이고, 실행할 수 없다면 이식성이 낮다고 볼 수 있다. MS 윈도우에서 실행되는 대부분의 프로그램들은 MS 윈도우 환경에 최적화되어 있기 때문에 유닉스나 리눅스에서 실행하려면 프로그램 소스 파일을 수정한 후, 재컴파일 과정을 거쳐야한다. 하지만 자바 언어로 개발된 프로그램은 소스 파일을 다시 수정하지 않아도, 자바 실행 환경(JRE: Java Runtime Environment)이 설치되어 있는 모든 운영체제에서 실행 가능하다. 따라서 자바 언어는 이식성이 높은 프로그래밍 언어라고 볼 수 있다.


객체 지향 언어이다.

프로그램을 개발하는 기법으로 부품에 해당하는 객체들을 먼저 만들고, 이것들을 하나씩 조립 및 연결해서 전체 프로그램을 완성하는 기법을 객체 지향 프로그래밍(OOP: Object Oriented Programming)이라고 한다. 이때 사용되는 언어를 객체 지향 언어라고 한다.

자바는 100% 객체 지향 언어이다. 객체를 만들기 위해  설계도인 클래스를  작성해야 하고, 객체와 객체를 연결하여 목적에 맞는 프로그램을 만들어 낸다. 자바는 아무리 작은 프로그램이라도 객체를  만들어 사용한다. 처음부터 객체를 고려하여 설계되었기 때문에 객체 지향 언어가 가져야 할 캡슐화, 상속, 다형성 기능을 완벽하게 지원하고 있다.


함수적 스타일  코딩을 지원한다.

자바는 객체 지향 프로그래밍이 소프트웨어 개발의 주요 패러다임이었던 1990년대에 디자인되었다. 객체 지향 프로그래밍이 나오기 오래전부터 Lisp 또는 Scheme와 같은 함수적 프로그래밍 언어들이 있었는데, 학계를 제외하고는 현업에서 큰 호응을 얻지 못했다. 최근 들어 함수적 프로그래밍이 다시 부각되고 있는데, 대용량 데이터의 병렬 처리 그리고 이벤트 지향 프로그래밍을 위해 적합하기 때문이다. 자바는 함수적 프로그래밍을 위해 람다식(Lambda Expressions)을 자바 8부터 지원한다. 람다식을 사용하면 컬렉션의 요소를 필터링, 매핑, 집계 처리하는데 쉬워지고, 코드가 매우 간결해진다.


메모리를 자동으로 관리한다.

C++은 메모리에 생성된 객체를 제거하기 위해 개발자가 직접 코드를 작성해야 한다. 만약 이 작업을 성실하게 해주지 않으면, 프로그램은 불완전해지고 갑자기 다운되는 현상을 겪게 된다. 자바는 개발자가 직접 메모리에 접근할 수 없도록 설계되었으며, 메모리는 자바가 직접 관리한다. 객체 생성 시 자동적으로 메모리 영역을 찾아서 할당하고, 사용이 완료되면 쓰레기 수집기(Garbage Collector)를 실행시켜 자동적으로 사용하지 않는 객체를 제거시켜준다. 따라서 개발자는 메모리 관리의 수고스러움을 덜고, 핵심 기능 코드 작성에 집중할 수 있다.


다양한 애플리케이션을 개발할 수 있다.

자바는 윈도우, 리눅스, 유닉스, 맥 등 다양한 운영체제(OS: Operating System)에서 실행되는 프로그램을 개발할 수 있다. 단순한 콘솔 프로그램에서부터 클라이언트용 윈도우 애플리케이션, 서버용 웹 애플리케이션 그리고 모바일용 안드로이드 앱에 이르기까지 거의 모든 곳에서 실행되는 프로그램을 개발할 수 있다. 자바는 다양한 운영체제에서 사용할 수 있는 개발 도구와 API를 묶어 에디션(Edition) 형태로 정의하고 있다.

 Java SE(Standard Edition) - 기본 에디션

Java SE는 자바 프로그램들이 공통적으로 사용하는 자바 가상 기계(JVM: Java Virtual Machine)를 비롯해서 자바 프로그램 개발에 필수적인 도구와 라이브러리 API를 정의한다. 클라이언트와 서버 프로그램에 상관없이 자바 프로그램을 개발하고 실행하기 위해서는 반드시 Java SE 구현체인 자바 개발 키드(JDK: Java Development Kit)를 설치해야 한다.

 Java EE(Enterprise Edition) - 서버용 애플리케이션 개발 에디션

Java EE는 분산 환경(네트워크, 인터넷) 에서 서버용 애플리케이션을 개발하기 위한 도구 및 라이브러리 API를 정의한다. 서버용 애플리케이션으로는 Servlet/JSP를 이용한 웹 애플리케이션, 분산 처리 컴포넌트인 EJB(Enterprise Java Bean) 그리고 XML 웹 서비스(Web Services) 등이 있다.


멀티 스레드(Multi-Thread)를 쉽게 구현할 수 있다.

하나의 프로그램이 동시에 여러 가지 작업을 처리해야 할 경우와 대용량 작업을 빨리 처리하기 위해 서브 작업으로 분리해서 병렬 처리하려면 멀티 스레드 프로그래밍이 필요하다. 프로그램이 실행되는 운영체제에 따라서 멀티 스레드를 구현하는 방법이 다르지만, 자바는 스레드 생성 및 제어와 관련된 라이브러리 API를 제공하고 있기 때문에 실행되는 운영체제에 상관없이 멀티 스레드를 쉽게 구현할 수 있다.


동적 로딩(Dynamic Loading)을 지원한다.

자바 애플리케이션은 여러 개의 객체가 서로 연결되어 실행되는데, 이 객체들은 클래스로부터 생성된다. 애플리케이션이 실행될 때 모든 객체가 생성되지 않고, 객체가 필요한 시점에 클래스를 동적 로딩해서 객체를 생성한다. 또한 개발 완료 후 유지보수(수정)가 발생하더라도 해당 클래스만 수정하면 되므로 전체 애플리케이션을 다시 컴파일할 필요가 없다. 따라서 유지보수를 쉽고 빠르게 진행 할 수 있다.


막강한 오픈소스 라이브러리가 풍부하다.

자바는 오픈소스(Open Source) 언어이기 때문에 자바 프로그램에서 사용하는 라이브러리 또한 오픈소스가 넘쳐난다. 고급 기능을 구현하는 코드를 직접 작성할 경우, 시간과 노력이 필요하고, 실행 안전성을 보장할 수 없지만, 검증된 오픈소스 라이브러리를 사용하면 개발 기간을 단축하면서 안전성이 높은 애플리케이션을 쉽게 개발할 수 있다. 많은 회사들이 자바를 선택하는 이유 중의 하나가 막강하고 풍부한 자바 오픈소스 라이브러리가 있기 때문이다.



1.2.3 자바 가상 기계(JVM)

운영체제는 자바 프로그램을 바로 실행할 수 없는데, 그 이유는 자바 프로그램은 완전한 기계어가 아닌, 중간 단계의 바이트 코드이기 때문에 이것을 해석하고 실행할 수 있는 가상의 운영체제가 필요하다. 이것이 자바 가상 기계(JVM: Java Virtual Machine)이다. JVM은 실 운영체제를 대신해서 자바 프로그램을 실행하는 가상의 운영체제 역할을 한다. 영어권에서는 컴퓨터를 흔히 기계(Machine)라고 부르기 때문에 '자바를 실행시키는 가상의 기계'라고 해서 'JVM'이라는 용어가 나오게 된 것이다. 운영체제별로 프로그램을 실행하고 관리하는 방법이 다르기 때문에 운영체제별로 자바 프로그램을 별도로 개발하는 것보다는 운영체제와 자바 프로그램을 중계하는 JVM을 두어 자바 프로그램이 여러 운영체제에서 동일한 실행 결과가 나오도록 설계한 것이다. 따라서 개발자는 운영체제와 상관없이 자바 프로그램을 개발할 수 있다.

바이트 코드는 모든 JVM에서 동일한 실행 결과를 보장하지만, JVM은 운영체제에 종속적이다. 자바 프로그램을 운영체제가 이해하는 기계어로 번역해서 실행해야 하므로 JVM은 운영체제에 맞게 설치되어야 한다. JVM은 JDK 또는 JRE를 설치하면 자동으로 설치되는데, JDK와 JRE가 운영체제별로 제공된다.

자바 프로그램은 확장자가 .java인 파일을 작성하는 것부터 시작된다. 이것을 소스 파일이라고 하는데, 이 소스 파일을 컴파일러로(javac.exe)로 컴파일하면 확장자가 .class인 바이트 코드 파일이 생성된다. 바이트 코드 파일은 JVM 구동 명령어(java.exe)에 의해 JVM에서 해석되고 해당 운영체제에 맞게 기계어로 번역된다. 바이트 코드는 하나지만, JVM에 의해서 번역되는 기계어는 운영체제에 따라서 달라진다.

자바의 가장 큰 장점 중의 하나인 "Write once, run anywhere. (한 번 작성하면 어디서든 실행된다.)"는 매우 매력적임에는 틀림없지만, 한 번의 컴파일링으로 실행 가능한 기계어가 만들어지지 않고 JVM에 의해 기계어로 번역되고 실행되기 때문에, C와 C++의 컴파일 단계에서 만들어지는 완전한 기계어보다는 속도가 느리다는 단점을 가지고 있다. 그러나 기계어로 빠르게 변환해주는 JVM 내부의 최적화된 JIT 컴파일러를 통해서 속도의 격차는 많이 줄어들고 있다.

+ Recent posts