들어가기 앞서

연산자(operator)는 +, -, /, * 등과 같이 연산에 사용되는 기호를 말합니다. 자바에는 아래의 표와 같이 단항, 산술, 시프트, 관계 등 매우 다양한 연산자가 있습니다. 아래의 연산자 표를 외울 필요 없이 자주 쓰다보면 손에 익습니다. 그냥 간단하게 보고 넘어가주세요.

연산자의 우선순위

여기서 우선순위가 눈에 띄는데, 우선순위는 연산식 내에 여러 개의 연산자가 사용됐을 경우에 무엇을 먼저 처리할 것인지, 무엇을 나중에 처리할 것인지를 결정합니다. 예를 들어서, '124 + 62 * 43 - 22'는 연산자 우선순위에 따라 아래와 같이 처리됩니다.

이번에는 연산식이 'score >= 80 && score < 90'일 때는 무엇이 먼저 처리될까요? 살펴보면 연산자 <, >=은 관계 연산자고 &&은 논리 연산자입니다. 여기서 관계 연산자가 논리 연산자보다 우선순위가 높으므로 아래와 같이 처리됩니다.

연산방향

그리고 연산방향은 우선순위가 같은 연산자가 있을 때 연산을 수행하는 방향을 말하는 것입니다. 연산식 '45 * 21 / 7'에 쓰인 연산자가 모두 산술 연산자로 우선순위가 같습니다. 여기서 연산방향은 왼쪽에서 오른쪽으로 이동하므로 왼쪽에 있는 연산자부터 계산하게 됩니다.

연산자와 피연산자

앞으로 종종 등장할 단어들입니다. 연산자는 위에서 설명했듯이 연산에 사용되는 기호를 말하고, 피연산자는 연산의 대상을 말합니다. 예를 들어서 연산식 'a + b'가 있을 때 a, b는 피연산자이고 +는 연산자라고 할 수 있습니다.

이 문서에서는 대입, 산술, 관계, 논리 연산자를 먼저 살펴보도록 하겠습니다. 다음 문서에서는 단항, 삼항, 비트, 시프트 연산자에 대해 설명하고 있습니다.

대입 연산자와 산술 연산자

다음의 표는 대입 연산자(assignment operators)산술 연산자(arithmetic operators)를 정리한 것입니다.

아래는 대입 연산자와 산술 연산자를 활용한 예제입니다.

public class OperatorExamples {
	public static void main(String[] args) {
		int num1 = 714;
		int num2 = 500;

		// 714 + 500 = 1214
		System.out.println(num1 + " + " + num2 + " = " + (num1 + num2));
		// 714 - 500 = 214
		System.out.println(num1 + " - " + num2 + " = " + (num1 - num2));
		// 714 * 500 = 357000
		System.out.println(num1 + " * " + num2 + " = " + (num1 * num2)); 
		// 714 / 500 = 1
		System.out.println(num1 + " / " + num2 + " = " + (num1 / num2)); 
		// 714 % 500 = 214
		System.out.println(num1 + " % " + num2 + " = " + (num1 % num2)); 
	}
}

다시 코드로 돌아가서, 3~4행에서 10진수 정수형 변수 num1, num2를 컴파일러에게 선언함과 동시에 714와 500이란 값을 각각의 변수에 대입 연산자를 통해 대입했습니다. 그리고 7~15행에서 차례대로 덧셈, 뺄셈, 곱셈, 나눗셈, 나머지를 구하는 연산자를 사용해 그 결과를 출력하도록 했습니다. 간단하죠?

문자열 연결 연산자

여태까지 너무 자연스럽게 사용해왔지만 +은 부호 연산자이자 산술 연산자이기도 하고 문자열 연결 연산자이기도 합니다. 문자열 연결 연산자는 피연산자 중 한쪽이 문자열이면 문자열이 아닌 피연산자를 문자열로 변환하고 서로 연결하는 역할을 합니다. 예를 들어서 아래와 같은 경우를 생각해 보겠습니다.

System.out.println(num1 + " + " + num2 + " = " + (num1 + num2));

연산방향은 왼쪽에서 오른쪽이니 아래와 같이 연산이 진행됩니다. 위에서 확인해봤듯이, 최종적으로 "714 + 500 = 1214"가 출력될 것입니다. 여기서 소괄호를 제거하면 무슨 일이 일어날까요?

코드를 아래와 같이 수정하고 결과를 확인해봅시다. 그러면 우리가 기대한 값과는 전혀 다른 이상한 값이 출력될 것입니다.

System.out.println(num1 + " + " + num2 + " = " + num1 + num2); // 714 + 500 = 714500

연산은 아래와 같이 진행됩니다. 따라서, 정상적인 결과값을 출력하려면 위와 같이 소괄호로 num1 + num2를 둘러싸야 합니다. 그러면 num1 + num2를 우선적으로 계산하게 됩니다.

이처럼 연산자 우선순위에 따라 연산식의 결과가 달라지므로 유의하시기 바랍니다.

복합 대입 연산자

복합 대입 연산자란 대입 연산자와 다른 연산자들과 묶여서 쓰이는 형태의 연산자를 말하는 것입니다. 다음의 표를 보시면 쉽게 이해하실 수 있습니다. 아래에는 대입 연산자와 산술 연산자를 묶은 복합 대입 연산자를 소개했지만, &=, ^=, |=와 같이 비트 연산자와 묶어서 사용할 수 있으며, <<=, >>=, >>>=와 같이 시프트 연산자와 묶어서 사용할 수 있습니다. 비트 연산자와 시프트 연산자에 대해선 다음 문서에서 다룹니다.

아래는 복합 대입 연산자를 활용한 예제입니다.

public class OperatorExamples {
	public static void main(String[] args) {
		 int num1 = 5;
		 int num2 = 4;

		 num1 += num2; // num1 = num1 + num2
		 System.out.println("num1의 값: " + num1); // num1의 값: 9

		 num2 *= num1; // num2 = num2 * num1
		 System.out.println("num2의 값: " + num2); // num2의 값: 36
	}
}

코드로 돌아가서, 3~4행에서 10진수 정수형 변수 num1, num2를 선언하여 5와 4라는 값을 각각 대입했습니다. 그런 뒤에 6행과 9행을 보시면 복합 대입 연산자가 쓰였는데, 이것은 num1 = num1 + num2, num2 = num2 * num1으로 해석이 됩니다. 정리하면 num1 = 5 + 4가 되어 9가 num1에 대입되었고, num2 = 4 * 9가 되어 36이란 값이 num2에 대입된 것입니다.

관계 연산자

관계 연산자란 연산식의 결과가 참이면 true를, 거짓이면 false를 반환하는 연산자입니다. 이 관계 연산자는 나중에 배울 조건문(if~else)이나 반복문(for, while)을 사용할 때 많이 사용됩니다. 

다음 예제를 통해 관계 연산자를 쉽게 이해하실 수 있습니다.

public class OperatorExamples {
	public static void main(String[] args) {
		int num1 = 10;
		int num2 = 20;

		// 10 > 20 = false
		System.out.println(num1 + " > " + num2 + " = " + (num1 > num2));
		// 10 < 20 = true
		System.out.println(num1 + " < " + num2 + " = " + (num1 < num2));
		// 10 >= 20 = false
		System.out.println(num1 + " >= " + num2 + " = " + (num1 >= num2));
		// 10 <= 20 = true
		System.out.println(num1 + " <= " + num2 + " = " + (num1 <= num2));
		// 10 == 20 = false
		System.out.println(num1 + " == " + num2 + " = " + (num1 == num2));
		// 10 != 20 = true
		System.out.println(num1 + " != " + num2 + " = " + (num1 != num2));
	}
}

결과를 살펴보면, 10 > 20에서 당연히 10은 20보다 작으므로 거짓(false)를 반환합니다. 10 < 20은 맞는 수식이므로 참(true)을 반환합니다. 10 >= 20은 거짓(false), 10 <= 20은 참(true), 그리고 10과 20은 같지 않으므로 거짓(false), 10과 20은 서로 다르므로 참(true)을 반환합니다.

논리 연산자

논리 연산자는 관계 연산자와 같이 true, false를 반환하는 연산자입니다. 논리 연산자의 종류엔 논리곱(AND, &&), 논리합(OR, ||), 논리부정(NOT, !)이 있습니다. 논리 연산자의 피연산자는 true 또는 false여야 합니다.

이 논리 연산자의 연산결과를 모아둔 표를 진리표(truth table)라고 합니다. 여기서 T란 참(True)을, F란 거짓(False)을 나타내며 A, B는 들어가는 값(Input)을 의미하고 Y는 나가는 값(Output)을 의미합니다.

이해를 돕기 위해서 관계 연산자와 논리 연산자가 같이 쓰인 아래의 예제를 살펴보도록 합시다.

public class OperatorExamples {
	public static void main(String[] args) {
		int num1 = 10;
		int num2 = 20;

		// AND
		System.out.println(num1 < num2 && num1 != num2); // true
		System.out.println(num1 > num2 && num1 != num2); // false

		// OR
		System.out.println(num1 < num2 || num1 != num2); // true
		System.out.println(num1 > num2 || num1 != num2); // true
		System.out.println(num1 > num2 || num1 == num2); // false

		// NOT
		System.out.println(!(num1 < num2)); // false
		System.out.println(!(num1 > num2)); // true
	}
}

위에서 몇 개만 살펴보자면 아래와 같습니다.

한 걸음 더 나아가기

단락 평가(Short-Circuit Evaluation)

단락 평가는 앞에 있는 연산식의 결과에 따라서 뒤에 있는 연산식의 실행 여부가 결정되는 것을 말합니다. 예를 들어서, AND 연산을 할 때 첫 번째 피연산자가 false이면 두 번째 피연산자를 볼 필요도 없이 결과는 false가 됩니다. OR 연산도 이와 비슷하게 첫 번째 피연산자가 true이면 두 번째 피연산자를 볼 필요도 없이 결과는 true가 됩니다.

단락 평가를 어떻게 이용할 수 있을까요? 우선 조건문은 아직 배우지 않았지만 if문의 소괄호 안에 있는 연산식이 참일 때 그 아래에 있는 문장이 실행된다고 이해합시다. 아래의 코드를 한 번 실행해보세요.

public class ShortCircuitingExamples {
	public static void main(String[] args) {
		int n, d;
		
		n = 14;
		d = 7;
		if (n % d == 0)
			System.out.println(n + "는 " + d + "의 배수입니다.");
		
		d = 0;
		if (n % d == 0)
			System.out.println(n + "는 " + d + "의 배수입니다.");
	}
}

그럼 아래의 문장에서 예외(exception)가 발생하는 것을 볼 수 있습니다. 이처럼 프로그램 실행 도중에 예기치 못한 상황에 부딪혔을 때 에러 메시지를 출력하고 프로그램이 비정상적으로 종료되게 됩니다. 예외의 원인은 'n % d == 0'에서 제수 d가 0이라서 0으로 n을 나눌 수 없기 때문입니다.

여기서 어떻게 해야 올바른 코드로 고칠 수 있을까요? 여기서 단락 평가를 이용할 수 있습니다. 아래와 같이 'd != 0 && n % d == 0'으로 고치면 더이상 에러가 발생하지 않습니다.

public class ShortCircuitingExamples {
	public static void main(String[] args) {
		int n, d;
		
		n = 14;
		d = 7;
		if (d != 0 && n % d == 0)
			System.out.println(n + "는 " + d + "의 배수입니다.");
		
		d = 0;
		if (d != 0 && n % d == 0)
			System.out.println(n + "는 " + d + "의 배수입니다.");
	}
}

왜 그럴까요? d가 0일 때 'd != 0'이 false이므로 뒤에 있는 연산식은 계산하지 않아도 논리 AND 연산의 결과가 false라는 것은 쉽게 알 수 있기 때문입니다. 따라서 뒤에 있는 식은 실행되지 않으므로 에러가 발생하지 않습니다.

그 외에도 이를 이용해서 배열의 범위를 벗어나거나 객체가 null인 경우 뒤에 있는 연산식은 실행하지 않도록 만들 수 있습니다. 배열과 객체는 아직 배우지 않은 내용이므로 가볍게 넘어가셔도 괜찮습니다.

'프로그래밍 관련 > 자바' 카테고리의 다른 글

8편. 제어문 (1)  (16) 2012.07.30
7편. 연산자 (2)  (27) 2012.07.25
5편. 주석  (14) 2012.07.22
4편. 변수와 타입  (24) 2012.07.22
2편. 개발 환경 구축하기  (19) 2012.07.20