연산자(operator)
연산자는 '연산을 수행하는 기호'를 말합니다. 연산자가 연산을 수행하려면 반드시 연산의 대상이 있어야하는데, 이것을 '피연산자(operand)'라고 합니다. 피연산자로 상수, 변수 또는 식 등을 사용할 수 있습니다. 대부분의 연산자는 두 개의 피연산자를 필요로 하며, 하나 또는 세 개의 피연산자를 필요로하는 연산자도 있습니다.
대입 연산자
- =
연산자와 피연산자를 조합하여 계산하고자하는 바를 표현한 것을 '식(expression)'이라고 하고, '식'을 계산하여 결과를 얻는 것을 식을 평가(evaluate)한다고 합니다. 그리고 식이 평가되어 나온 결과를 대입 연산자(=)를 사용하여 변수와 같이 값을 저장할 수 있는 공간에 저장합니다.
연산자의 종류

연산자는 위 그림과 같이 크게 산술, 비교, 논리, 대입 4가지로 나눌 수 있습니다. 기타 연산자에는 형변환 연산자, 삼항 연산자, instanceof 연산자가 있습니다.
피연산자의 개수에 의한 분류
피연산자의 개수가 하나면 '단항 연산자', 두 개면 '이항 연산자', 세 개면 '삼항 연산자'라고 부릅니다. 대부분의 연산자는 이항 연산자이고, 삼항 연산자는 오직 '? :' 뿐입니다. 증감 연산자와 부호 연산자의 경우 단항 연산자에 해당합니다.
연산자의 우선순위
식에 사용된 연산자가 둘 이상인 경우, 연산자의 우선순위에 의해서 연산순서가 결정됩니다.
- 산술 > 비교 > 논리 > 대입
- 단항 > 이항 > 삼항
- 단항 연산자와 대입 연산자를 제외한 모든 연산의 진행방향은 왼쪽 -> 오른쪽

x << 2 + 1 // x << (2 + 1)
data & 0xFF == 0 // data & (0xFF == 0)
x < -1 || x > 3 && x < 5 // x < -1 || (x > 3 && x < 5)
산술 변환
이항 연산자는 두 피연산자의 티입이 일치해야 연산이 가능하므로, 피연산자의 타입이 서로 다르다면 연산 전에 형변환 연산자로 타입을 일치시켜야합니다. 대부분의 경우, 두 피연산자의 타입 중에서 더 큰 타입으로 일치시키는데, 그 이유는 작은 타입으로 형변환하면 원래의 값이 손실될 가능성이 있기 때문입니다. 물론 작은 타입에서 큰 타입으로 형변환하는 경우, 자동으로 형변환되므로 형변환 연산자를 생략할 수 있습니다.
이처럼 연산 전에 피연산자 타입의 일치를 위해 자동 형변환되는 것을 '산술 변환' 또는 '일반 산술 변환'이라 하며, 이 변환은 이항 연산에서만 아니라 단항 연산에서도 일어납니다. '산술 변환'의 규칙은 다음과 같습니다.
- 두 피연산자의 타입을 큰 타입으로 일치시킵니다.
- 피연산자의 타입이 int보다 작은 타입이면 int로 변환됩니다.
모든 연산에서 '산술 변환'이 일어나지만, 쉬프트 연산자와 증감 연산자는 예외입니다.
첫 번째 규칙은 앞서 자동 형변환에서 배운 것처럼 피연산자의 값손실을 최소화하기 위한 것이고, 두 번째 규칙은 정수형의 기본 타입인 int가 가장 효율적으로 처리할 수 있는 타입임과 동시에 int보다 작은 타입들은 연산중에 오버플로우가 발생할 가능성이 높기 때문입니다.
단항 연산자
증감 연산자
- ++ --
증감 연산자는 피연산자에 저장된 값을 1 증가 또는 감소시킵니다. 증감 연산자의 피연산자로 정수와 실수가 모두 가능하지만, 상수는 값을 변경할 수 없으므로 가능하지 않습니다.
대부분의 연산자는 피연산자의 값을 읽어서 연산에 사용할 뿐, 피연산자의 타입이나 값을 변경시키지 않습니다. 오직 대입 연산자와 증감 연산자만 피연산자의 값을 변경합니다.
증감 연산자가 피연산자의 왼쪽에 위치하면 '전위형(prefix)', 오른쪽에 위치하면 '후위형(postfix)'이라고 합니다. 전위형은 값이 참조되기 전에 증감시키고, 후위형은 값이 참조된 후에 증감시킵니다.
public static void main(String[] args) {
int i = 5, j = 0;
j = i++;
System.out.printf("j=i++;, i = %d, j = %d\n", i, j); // i=6, j=5
i = 5;
j = 0;
j = ++i;
System.out.printf("j=++i;, i = %d, j = %d\n", i, j); // i=6, j=6
}
실행결과를 보면 i의 값은 두 경우 모두 1이 증가되어 6이 되지만, j의 값은 다른 것을 확인할 수 있습니다. 전위형은 변수(피연산자)의 값을 먼저 증가시킨 후에 변수의 값을 읽어오는 반면, 후위형은 변수의 값을 먼저 읽어온 후에 값을 증가시킵니다.
부호 연산자
- - +
부호 연산자 '-'는 피연산자의 부호를 반대로 변경한 결과를 반환합니다. 피연산자가 음수면 양수, 양수면 음수가 연산의 결과가 됩니다. 부호 연산자 '+'는 하는 일이 없으며, 쓰이는 경우도 거의 없습니다. 부호 연산자는 boolean형과 char형을 제외한 기본형에만 사용할 수 있습니다.
산술 연산자
사칙 연산자
- + - * / %
사칙 연산자 덧셈(+), 뺄셈(-), 곱셈(*), 나눗셈(/)은 아마도 프로그래밍에 가장 많이 사용되는 연산자들 일 것입니다. 기본적으로 곱셈, 나눗셈, 나머지(%) 연산자가 덧셈, 뺄셈 연산자보다 우선순위가 높아 먼저 처리됩니다. 그리고 피연산자가 정수형인 경우, 나누는 수로 0을 사용할 수 없습니다.
public static void main(String[] args) {
int a = 10;
int b = 4;
System.out.printf("a + b = %d\n", a + b); // 14
System.out.printf("a - b = %d\n", a - b); // 6
System.out.printf("a * b = %d\n", a * b); // 40
System.out.printf("a / b = %d\n", a / b); // 2
System.out.printf("a / (float)b = %f\n", a / (float)b); // 2.500000
}
위 코드의 결과에서 살펴봐야할 것은 a / b 의 결과가 2가 나왔다는 것입니다.
나누기 연산자의 두 피연산자가 모두 int 타입인 경우, 연산결과 역시 int타입이기 때문에 실제 연산결과는 2.5일지라도 소수점 이하는 버려지고 int타입의 값인 2를 결과로 얻습니다. 이때 반올림은 발생하지 않습니다.
그래서 올바른 연산결과를 얻기 위해서는 두 피연산자 중 어느 한 쪽을 실수형으로 형변환해야 합니다. 그래야만 다른 한 쪽도 같이 실수형으로 자동 형변환되어 결국 실수형의 값을 결과로 얻습니다.
이때 피연산자가 정수형인 경우 나누는 수로 0을 사용하면, 오류(ArithmeticException)가 발생하게 되지만, 부동 소수점값인 0.0f, 0.0d로 나누면 오류가 아닌 무한대(Infinity)가 나오게 됩니다.
또 다른 예시를 살펴보겠습니다.
public static void main(Stirng[] args) {
char a = 'a';
byte b = 30;
char c = a + b; // error: incompatible types
}
위와 같이 코드를 작성해보면 컴파일 에러가 발생합니다. a와 b는 모두 int형보다 작은 자료형이기 때문에 연산자 '+'는 이 두 개의 피연산자들의 자료형을 int형으로 변환한 다음에 연산을 수행합니다. 그래서 'a + b'의 연산결과는 char형이 아닌 int형인 것입니다. 만약 연산결과를 char형으로 받고 싶다면 명시적인 형변환을 해줘야 합니다.
비교 연산자
비교 연산자는 두 피연산자를 비교하는데 사용되는 연산자로, 주로 조건문과 반복문의 조건식에 사용되고, 연산결과는 오직 true와 false 둘 중의 하나입니다.
대소비교 연산자
- > < <= >=
두 피연산자의 값의 크기를 비교하는 연산자로 기본형 중에서는 boolean형을 제외한 나머지 자료형에 다 사용할 수 있지만, 참조형에는 사용할 수 없습니다.
등가비교 연산자
- == !=
두 피연산자의 값이 같은지 또는 다른지를 비교하는 연산자로 대소비교 연산자와 달리 기본형은 물론 참조형에도 사용할 수 있습니다. 기본형의 경우 변수에 저장되어 있는 값이 같은지를 알 수 있고, 참조형의 경우 객체의 주소값을 저장하기 때문에 두 개의 피연산자(참조변수)가 같은 객체를 가리키고 있는지를 알 수 있습니다.
public static void main(String[] args) {
System.out.printf("10 == 10.0f : %b%n", 10.0 == 10.0f); // true
System.out.printf("0.1 == 0.1f : %b%n", 0.1 == 0.1f); // false
}
위 예제의 결과를 보면 다소 혼란스러울 수 있습니다.
이러한 결과가 나오는 이유는 정수형과 달리 실수형은 근삿값으로 저장되므로 오차가 발생할 수 있기 때문입니다. 10.0f는 오차없이 저장할 수 있는 값이라서 float과 double의 값이 같지만, 0.1f는 저장할 때 2진수로 변환하는 과정에서 오차가 발생합니다. double타입의 상수인 0.1도 저장되는 과정에서 오차가 발생하지만, 0.1f보다 적은 오차로 저장됩니다.
float타입의 값과 double타입의 값을 비교하기 위해서는 double타입의 값을 float타입으로 명시적 형변환을 하고 비교해야 합니다. 반대로 float타입의 값을 double타입으로 형변환하고 비교하면 float타입일 때와 값이 동일하게 나와 올바른 비교가 되지 않습니다.
문자열 비교
두 문자열을 비교할 때는, 비교 연산자 '=='대신 equals()라는 메서드를 사용해야 합니다. 비교 연산자는 문자열 그 자체가 완전히 같은 것인지 비교할 뿐으로, 문자열의 내용이 같은지를 비교하기 위해서는 equals()를 사용해야 합니다.
public static void main(String[] args) {
String str = new String("abc");
System.out.printf("== : %b%n", str == "abc"); // false
System.out.printf("equals() : %b%n", str.equals("abc")); // true
}
논리 연산자
논리 연산자는 둘 이상의 조건을 AND, OR로 연결하여 하나의 식으로 표현할 수 있게 해줍니다.
논리 연산자
- && ||
논리 연산자 '&&'는 그리고(AND)에 해당하며, 두 피연산자가 모두 true일 때만 true를 결과로 얻습니다. '||'는 또는(OR)에 해당하며, 두 피연산자 중 어느 한 쪽만 true여도 true를 결과로 얻습니다. 그리고 논리 연산자는 피연산자로 boolean형 또는 boolean형 값을 결과로 하는 조건식만을 허용합니다.
short circuit evaluation
논리 연산자의 또 다른 특징은 효율적인 연산을 한다는 것입니다.
OR연산 '||'의 경우, 두 피연산자 중 어느 한 쪽만 '참'이어도 전체 연산결과가 '참'이어도 전체 연산결과가 '참'이므로 좌측 피연산자가 true면, 우측 피연산자의 값은 평가하지 않습니다.
AND연산 '&&'의 경우도 마친가지로 어느 한쪽만 '거짓'이어도 전체 연산결과가 '거짓'이므로 좌측 피연산자가 false면, 우측 피연산자의 값은 평가하지 않습니다.
그래서 같은 조건식이라도 피연산자의 위치에 따라서 연산속도가 달라질 수 있는 것입니다.
논리 부정 연산자
- !
논리 부정 연산자(!)는 피연산자가 true면 false를, false면 true를 결과로 반환합니다. 이 연산자의 이러한 성질을 이용하면 '토글 버튼(toggle button)'을 논리적으로 구현할 수 있습니다.
비트 연산자
- & | ^ ~ << >>
비트 연산자는 피연산자를 비트단위로 논리 연산합니다. 피연산자를 이진수로 표현했을 때의 각 자리를 아래의 규칙에 따라 연산을 수행하며, 피연산자로 정수만 허용하며, 실수는 허용하지 않습니다.

XOR은 eXclusive OR로 피연산자의 값이 서로 배타적인 경우에만 참(1)을 결과로 얻습니다.
비트 OR연산자 '|' 는 주로 특정 비트의 값을 변경할 때 사용합니다.

비트 AND연산자 '&'는 주로 특정 비트의 값을 뽑아낼 때 사용합니다.

비트 XOR연산자 '^'는 두 피연산자의 비트가 다를 때만 1이 되는데, 이는 XOR연산을 같은 값으로 2번 수행하면 원래의 값으로 돌아오는 특징이 있어서 간단한 암호화에 사용됩니다.

비트 전환 연산자
- ~
비트 전환 연산자(~)는 피연산자를 2진수로 표현했을 때, 0은 1로, 1은 0으로 바꿉니다. 논리 부정 연산자(!)와 유사합니다. 비트 전환 연산자에 의해 비트 전환이 되고 나면, 부호있는 타입의 피연산자는 부호가 반대로 변경됩니다. 그래서 비트 전환 연산자를 '1의 보수'연산자라고도 합니다.
public static void main(Strings[] args) {
byte p = 10;
byte n = -10;
System.out.printf("p = %d \t%s%n", p, toBinaryString(p));
System.out.printf("~p = %d \t%s%n", ~p, toBinaryString(~p));
System.out.printf("~p+1 = %d \t%s%n", ~p+1, toBinaryString(~p+1));
System.out.printf("~~p = %d \t%s%n", ~~p, toBinaryString(~~p));
System.out.printf("n = %d%n", n);
System.out.printf("~(n-1) = %d%n", ~(n-1));
}
static String toBinaryString(int x) {
String zero = "00000000000000000000000000000000";
String tmp = zero + Integer.toBinaryString(x);
return tmp.substring(tmp.length() - 32);
}
실행 결과를 보면, 어떤 양의 정수 p에 대한 음의 정수를 얻으려면 '~p + 1'계산하면 됩니다. 반대로 어떤 음의 정수 n에 대한 양의 정수를 얻으려면 '~(n - 1)'을 계산하면 됩니다. 물론 실제로는 이렇게 복잡하게 변환하지 않고 부호 연산자를 사용하면 됩니다.
'~~p'는 변수 p에 비트 전환 연산을 두 번 적용한 것인데, 1을 0으로 바꿨다가 다시 0을 1로 바꾸므로 원래의 값이 됩니다. 그러나 연산 결과의 타입이 byte가 아니라 int라는 것을 주의해야 합니다.
쉬프트 연산자
- << >>
이 연산자는 피연산자의 각 자리(2진수)를 오른쪽(>>) 또는 왼쪽(<<)으로 이동(shift)한다고 해서 쉬프트 연산자라고 이름이 붙여졌습니다.

예를 들어 '8 << 2'는 왼쪽 피연산자인 10진수 8의 2진수를 왼쪽으로 2자리 이동합니다. 이때, 자리이동으로 저장범위를 벗어난 값들은 버려지고 빈자리는 0으로 채워집니다. '<<' 연산자의 경우, 피연산자의 부호에 상관없이 각 자리를 왼쪽으로 이동시키며 빈 칸을 0으로만 채우면 되지만, '>>'연산자는 오른쪽으로 이동시키기 때문에 부호 있는 정수는 부호를 유지하기 위해 왼쪽 피연산자가 음수인 경우 빈자리를 1로 채웁니다.
- x << n은 x * 2^n의 결과와 같습니다.
- x >> n은 x / 2^n의 결과와 같습니다.
곱셈이나 나눗셈 연산자를 사용하면 같은 결과를 얻을 수 있는데, 굳이 쉬프트 연산자를 제공하는 이유가 무엇일까요?
그 이유는 속도 때문입니다.
예를 들어 '8 >> 2'의 결과는 '8 / 4'의 결과와 같습니다. 하지만, '8 / 4'를 연산하는데 걸리는 시간보다 '8 >> 2'를 연산하는데 걸리는 시간이 더 적게 걸립니다. 그러나 프로그램의 실행속도도 중요하지만 프로그램을 개발할 때 코드의 가독성도 중요합니다. 쉬프트 연산자가 빠르긴 해도 곱셈이나 나눗셈 연산자보다 가독성이 떨어지기 때문에, 일반적으로는 곱셈과 나눗셈 연잔자를 사용하는 것이 좋습니다.
그 외의 연산자
조건 연산자
- ? :
조건 연산자는 조건식, 식1, 식2 모두 세 개의 피연산자를 필요로 하는 삼항 연산자이며, 삼항 연산자는 조건 연산자 하나뿐입니다.

조건 연산자는 첫 번째 피연산자인 조건식의 평가결과에 따라 다른 결과를 반환합니다. 조건식의 결과가 true면 식1이, false면 식2가 연산 결과가 됩니다. 조건 연산자는 조건문인 if문으로 바꿔 쓸 수 있으며, if문 대신 조건 연산자를 사용하면 코드를 보다 간단히 할 수 있습니다.
대입 연산자
- = += -= *= /= %=
- <<= >>= &= ^= |=
대입 연산자는 변수와 같은 저장공간에 값 또는 수식의 연산결과를 저장하는데 사용됩니다. 이 연산자는 오른쪽 피연산자의 값을 왼쪽 피연산자에 저장합니다. 그리고 저장된 값을 연산 결과로 반환합니다.

대입 연산자는 연산자들 중에서 가장 낮은 우선순위를 가지고 있기 때문에 식에서 제일 나중에 수행됩니다. 그리고 연산 진행 방향이 오른쪽에서 왼쪽이기 때문에 'x=y=3'에서 'y=3'이 먼저 수행되고, 그 다음에 'x=y'가 수행됩니다.
대입 연산자의 왼쪽 피연산자를 'lvalue(left value)'이라 하고, 오른쪽 피연산자를 'rvalue(right value)'라고 합니다. 대입 연산자의 rvalue는 변수뿐 아니라 식이나 상수 등이 모두 가능한 반면, lvalue는 반드시 변수처럼 값을 변경할 수 있는 것이어야 합니다. 그래서 리터럴이나 상수같은 값을 저장할 수 없는 것들은 lvalue가 될 수 없습니다.
복합 대입 연산자
대입 연산자는 다른 연산자(op)와 결합하여 'op='와 같은 방식으로 사용될 수 있습니다. 예를 들어 'i = i + 3'은 'i += 3'과 같이 표현될 수 있습니다. 그리고 결합된 두 연산자는 반드시 공백없이 붙여 써야 합니다.
'Lang > Java' 카테고리의 다른 글
| [Java 21] (6) - Object-oriented Programming 1 (0) | 2025.09.29 |
|---|---|
| [Java 21] (5) - array (1) | 2025.09.25 |
| [Java 21] (4) - statement (0) | 2025.09.22 |
| [Java 21] (2) - variable (0) | 2025.09.15 |
| [Java 21] (1) - getting started with Java (0) | 2025.09.11 |