연산자
1. 식과 연산자
주어진 식(expression)을 계산하여 결과를 얻어내는 과정을 연산이라고 한다.
연산에는 연산자와 피연산자가 사용이 된다.
1) 식 : a+5; n > 23; a == n;
2) 연산자 : ==, +, -, %, *, !=, >> 등...
자바에는 산술 연산자, 논리 연산자, 비교 연산자, 시프트 연산자, 비트 연산자, 대입 연산자, 증감 연산자, 그리고 조건 연산자가 있다.
3) 피연산자 : 연산이 이루어지는 데이터를 피연산자라고 한다.
예) a, 5, n, 23 등
2. 연산자 우선순위
식에 여러 개의 연산자가 있는 경우, 우선순위가 높은 연산자를 먼저 처리한다.
(상위에 있을수록 우선순위가 높은 연산자이다!)
동일한 우선순위를 가진 연산자는 왼쪽에서부터 오른쪽으로 처리하나, 대입 연산자, --, ++, +(양의 부호), -(음의 부호), !, 타입 변환 연산자 등은 오른쪽에서 왼쪽으로 처리한다.
2. 산술 연산자 (Arithmetic Operator)
가장 많이 사용하는 연산자는 수식 계산에 사용되는 산술 연산자이다. 산술 연산자에는 + (더하기), 빼기 (-), 곱하기 (*), 나누기 (/), 그리고 나머지 (%)가 있다. 정수 연산에서 '/' 연산자는 정수 몫을 구하며, '%' 연산자는 나머지를 정수 값으로 구한다.
i.e. 5/2 = 2 <- 몫 5%2 = 1 <- 나머지
나머지 연산자는 주로 어떤 값이 짝수인지 홀수인지 판별할 때에 쓰인다.
3. 비트 연산자 (Bitwise Operator)
비트 연산자는 피연산자의 각 비트들을 대상으로 연산이 이루어진다. 비트 연산자의 종류로는 총 4개의 연산자가 있다. '~' 연산자를 제외한 나머지 3개의 비트 연산자는 모두 두 개의 피연산자를 가진다.
1) & (AND) 연산자 :
두 피연산자들의 각 자리의 비트를 비교해서 둘 다 1일 경우에만 결과를 1이 되게 하고, 그 의외의 경우에는 결과값을 0으로 만든다.
i.e. (01101010) & (11001101) = (01001000)
2) | (OR) 연산자 :
두 피연산자들의 각 비트를 OR 연산하는 연산자. 두 비트 모두 0일 때만 0이 되며, 나머지 경우에는 1이 된다.
i.e. (01101010) | (11001101) = (11101111)
3) ^ (XOR) 연산자 :
두 피연산자들의 각 비트들을 가지고 XOR 연산을 하는 연산자이다. 두 비트의 값이 다를 경우 1이 되고, 같을 경우에는 0이 결과 값이 된다.
i.e. (01101010) ^ (11001101) = (10100111)
4) ~ (NOT) 연산자 :
단항 연산자로서 피연산자의 각 비트들에 NOT 연산을 한다. 비트의 값이 1이면 0으로, 0이면 결과 값을 1로 만든다.
i.e. ~ (10010101) = (01101010)
4. 시프트 연산자 (shift operator)
시프트 연산자(shift operator) 역시 피연산자의 각 비트들을 대상으로 연산이 이루어진다. 자바에는 총 3개의 시프트 연산자가 있다.
시프트 연산자는 모두 이항 연산자이다. 시프트 연산자에서 피연산자는 byte, short, int, long, char 타입만 가능하면 float, double, boolean 등은 사용할 수 없다.
1) << 연산자
<< 연산자는 왼쪽 시프트 연산자로서 각 비트를 왼쪽으로 이동시킨다. 빈 공간이 되는 가장 오른쪽 비트는 0으로 채워지게 된다.
i.e. byte a = 5; byte b = (byte) (a <<2); // a 값을 왼쪽으로 2 비트 이동. b의 값은 20이 된다.
<< 연산자는 2의 n승을 곱해주는 가장 효율적인 방법이라고 할 수 있다.
그 이유는, 실제로 * 연산자를 사용하게 되면 여러번의 더하기를 수행하게 되어 단순히 비트를 옮기는 시프트 연산자 보다 시간이 더 오래 걸릴 확률이 크기 때문이다. (5 * 3 을 실행시키면 5+5+5 를 하는 것과 동일하다고 보면 된다.)
2) >>> 연산자
>>> 연산자는 오른쪽 시프트 연산자로서 비트를 오른쪽으로 이동시킨다. 다음 코드를 보자. 시프트 시 맨 왼쪽에 삽입되는 비트는 0이다.
i.e. byte a = 20; byte b = (byte) (a >>> 2); // a 값을 오른쪽으로 2 비트 이동시킨다. b에 저장되는 값은 5이다.
>>> 연산자는 모든 비트를 오른쪽으로 옮기고, 가장 왼쪽의 빈 비트의 값으로 0을 채워 넣는다. 그렇기 때문에 만약 원래 가장 왼쪽에 1이 있었고, 그 1이 부호를 나타내는 비트였다면, 부호가 달라지게 되는 문제가 생기게 된다.
3) >> 연산자
>> 연산자 역시 오른쪽 시프트 연산자이지만 >>> 와는 달리 시프트 시 맨 왼쪽의 최상위 비트에는 시프트 이전의 최상위 비트가 그대로 삽입된다.
i.e. byte a = 0xf8; /* -8을 나타내는 값 */ byte b = (byte) (a >> 2); // b에 저장되는 값은 -2 이다.
>> 는 왼쪽 최상위 부호를 그대로 두기 때문에 산술 연산으로서 손색이 없다고 할 수 있다. 따라서 2의 n승으로 나누는 경우, >> 연산자가 가장 효율적이라고 할 수 있다. (/ 연산자를 저수준 언어로 바꾸게 되면 더하기, 빼기, 비교 연산 등을 여러번 사용하는 매우 복잡한 연산이 되어서 + 나 - 심지어는 * 연산자 보다도 복잡한 연산을 수행하게 된다.)
+a) <<< 연산자가 없는 이유?
>>> 연산자와 >> 연산자의 차이는 간단하다. >> 연산자는 최상위 비트의 값을 신경 쓰는 산술 시프트 연산자고, >>> 연산자는 그냥 오른쪽으로 시프트 시키고 빈자리에 0을 채우는 논리 시프트 연산자이다. 일반적으로, 컴퓨터에서는 숫자와 같이 부호가 있는 값을 표시할 때에는 왼쪽 최상위 비트를 사용한다. 최상위 비트 값이 0이면 양수고, 1이면 음수가 된다. 참고로, 이러한 부호를 나타내는 방식에는 one's complement라는 방식과 two's complement라는 방식이 있다. 대부분의 현대 컴퓨터 언어들에서는 two's complement 방식을 사용하는데, 이 이유는 one's complement 방식을 사용하면 +0의 표시법과 -0의 표시법이 달라서 0이라는 값을 서로 다른 방식으로 표현하게 되어서 비효율적이 되기 때문이다. one's complement 방식과 two's complement 방식에 대해서 더 자세한 것은 나중에 기회가 되면 설명하도록 하겠다.
5. 비교 연산자와 논리 연산자
비교연산자는 두 개의 피연산자를 비교하여 결과 값이 참 또는 거짓 중 하나인 불린(boolean)이 되는 연산자이다. 논리 연산자는 불린 값을 대상으로만 연산을 하며 결과도 불린이다.
1) 비교 연산자
( 출처: <https://www.pinterest.co.kr/pin/335307134748914353/> )
위의 이미지를 통해서 알 수 있듯이, 비교 연산자는 모두 6개이며, 두 값을 비교하여 불린 값을 반환한다.
2) 논리 연산자
( 출처: <https://www.slideshare.net/munsifullah56/java-chapter-3-47355298> )
위의 이미지를 통해서 알 수 있듯이, 논리 연산자는 모두 4개이다.
^ 연산자는 비트 연산자 ^와 같은 역할이다. 다만, 이 논리 연산자로서의 XOR 연산자는 두 피연산자가 모두 boolean 타입이라는 점이 중요하다.
두 피연산자가 하나는 true 하나는 false이면 결과가 true가 되고, 두 피연산자의 논리값이 같게 되면 false를 반환한다.
앞서 말했듯이, 자바에서는 타입이 매우 중요하다. 단순 비교를 할 때에도 타입이 다르면 컴파일 오류가 생기게 되기 마련이다. 따라서, 피연산자의 타입이 다른 비트 연산자 ^와 논리 연산자 ^는 다르다고 할 수 있는 것이다.
|| 연산자와 && 연산자는 각각 | 연산자와 & 연산자와 비슷한 역할을 한다. || 연산자는 두 피연산자 중 하나만 참이여도 true를 반환하고, && 연산자는 두 피연산자 중 하나만 false여도 false를 반환한다. ||, && 연산자와 |, & 연산자의 가장 큰 차이점은 조기 종료 여부이다. 만약 a && b 라는 비교를 할 때에, a의 값이 false라면 && 연산자는 b의 값을 확인하지 않고 바로 false를 반환한다. 무조건 두 연산자를 모두 체크하는 & 연산자와는 차이가 확실하다. 이와 비슷하게, || 연산자는 첫번째 피연산자의 값이 true일 경우, 두번째 연산자의 값을 확인하지 않고 바로 true를 반환한다.
! 연산자는 NOT 연산자이다. 이 연산자는 단항 연산자로, 오직 하나의 피연산자만 받는다. 피연산자의 값이 true이면 false를 반환하고, false이면 true를 반환한다.
6. 대입 연산자, 증감 연산자
대입연산자는 연산자 오른쪽에 있는 식의 결과 값을 연산자 왼쪽에 있는 변수에 대입시킨다. 증감 연산자는 변수 자신의 값을 증가시키거나 감소시킨다. 특히, 증감 연산자의 사용에 유의하여야 한다. 증감 연산자는 연산자가 피연산자 앞에 붙는지 뒤에 붙는지가 큰 차이를 만들기 때문이다.
1) 대입 연산자
a = b : b의 값을 a에 대입시킨다.
a += b : a = a + b 와 동일
a -= b : a = a - b 와 동일
a *= b : a = a * b 와 동일
a /= b : a = a / b 와 동일
a %= b : a = a % b 와 동일
a &= b : a = a & b 와 동일
a ^= b : a = a ^ b 와 동일
a |= b : a = a | b 와 동일
a <<= b : a = a << b 와 동일
a >>= b : a = a >> b 와 동일
a >>>= b : a = a >>> b 와 동일
2) 증감 연산자
a++ : a의 값을 먼저 반환한 후에 값을 1 증가 시킨다.
++a : a의 값을 1 증가시킨 후에 a의 값을 반환한다.
a-- : a의 값을 먼저 반환한 후에 a의 값을 1 감소 시킨다.
--a : a의 값을 1 감소시킨 후에 a의 값을 반환한다.
7. 조건 연산자
조건 연산자는 세개의 피연산자로 구성되어 있어서 삼항 연산자(ternary operator)라고도 한다.
이 삼항 연산은 "condition ? : opr2 : opr3;" 의 형태로 구성되게 된다. 이 식에서 조건문이 condition이 true이면 전체 식의 값은 opr2의 값이 되고, false이면 opr3의 값이 된다. 이 조건 연산자를 이용하면 조건문(if-else문)을 대체할 수도 있고, 반대로 조건문으로 이 삼한 연산을 대체할 수 있다. 따라서, 본인이 익숙한 방식을 이용하는 것이 좋다.