
연산자는 앞장에서 배운 기본 자료형을 더하거나 빼는 등의 계산을 하기 위해서 사용합니다. 자바 언어에서는 다양한 연산자를 제공해주는데 타입별 사용 가능한 연산자가 있습니다.
| 구분 | 연산자 | 비고 | |
|---|---|---|---|
| 결과가 boolean | 숫자 비교 연산자 | <, <=, >, >= | |
| 숫자 동등 연산자 | ==, != | ||
| 결과가 int 혹은 long | 기본 사칙 연산자 | +, -, *, /, % | |
| 증감 연산자 | ++, -- | ||
| 비트 연산자 | &, | , ^, ~, <<, >>, >>> | |
| 기타 연산자 | 삼항 연산자 | ? : | |
| 형 변환 연산자 | (타입) | ||
| 문자열 더하기 연산자 | + |
수많은 연산자 분류는 피연산자 수에 따라 단항, 이항, 삼항 연산자로 구분합니다.
단항 연산자 : ++x;
이항 연산자 : x + y;
삼항 연산자 : (sum > 90) ? "A" : "B"
연산식은 하나의 값을 산출합니다. 연산자 수가 아무리 많아도 두 개 이상의 값을 산출하는 연산식은 없습니다.
int result = x + y;
그리고 연산식은 다른 피연산자 위치에도 올 수 있습니다. 아래 코드를 볼까요?
boolean result = (x + y) < 5;
비교 연산자인 < 의 좌측 피연산자로 (x + y)라는 연산식이 사용되었고, x와 y 변수의 값을 더하고나서 5보다 작은지 검사 후 결과값을 result 변수에 true 나 false (boolean 타입) 형태로 저장합니다.'
하나의 연산식에는 다양한 연산자가 복합적으로 구성된 경우가 많습니다. 이 경우에는 우선순위를 알아야 연산을 할 수 있는데요.
보통 산술 연산식에서 덧셈, 뺄셈 연산자 보다는 곱셈, 나눗셈 연산자가 우선 처리된다는 것을 알고있죠. 자바의 연산식에서도 마찬가지입니다.
아래의 연산식에서는 어떤 연산이 먼저 처리될까요? && 일까요, < 일까요, > 일까요?
x > 0 && y < 0
&& 보다는 <, >가 우선순위가 높기 때문에 x > 0, y < 0이 먼저 처리되고 각각에 산출된 값으로 && 연산을 합니다.
우선순위가 같은 연산자는 어떻게 될까요? 수학과 비슷합니다. 왼쪽에서부터 오른쪽 방향으로 연산을 합니다. 아래 연산식의 경우죠.
100 * 2 / 3 % 5
하지만 단항 연산자(++, - -, ~, !), 부호 연산자(+, -), 대입 연산자(=, +=, -=, …)는 오른쪽에서 왼쪽으로 연산됩니다. 예를 들어 다음과 같은 연산식을 보시죠.
a = b = c = 5;
순서가 어떻게 될까요?
c = 5, b = c, a = b 순서로 실행이 됩니다. (←) 실행되고 난 후에는 a, b, c값이 모두 5가 되지요.
이와 같이 어떤 연산자를 사용하느냐에 따라 연산의 방향과 우선순위가 정해져있기 때문에 복잡한 연산식에서는 주의가 필요합니다.
1. 단항, 이항, 삼항 연산자 순으로 우선순위를 가진다.
2. 산술, 비교, 논리, 대입 연산자 순으로 우선순위를 가진다.
3. 단항과 대입 연산자를 제외한 모든 연산의 방향은 왼쪽에서 오른쪽(->)이다.
4. 복잡한 연산식에는 괄호()를 사용해서 우선순위를 정해준다.
다음은 연산자의 연산 방향과 우선순위를 정리한 표 입니다.
| 연산자 | 연산 방향 | 우선 순위 |
|---|---|---|
| 증감(++, --), 부호(+, -), 비트(~), 논리(!) | ← | 높음 |
| 산술(*, /, %) | → | |
| 산술(+, -) | → | |
| 쉬프트(<<, >>, >>>) | → | |
| 비교(<, >, <=, >=) | → | |
| 비교(==, !=) | → | |
| 논리(&) | → | |
| 논리(^) | → | |
| 논리( | ) | → |
| 논리(&&) | → | |
| 논리( | ) | |
| 조건(?:) | → | |
| 대입(=, +=, -=, *=, /=, %=, &=, ^=, …) | ← | 낮음 |
연산자에 너무 많은 종류가 있고 우선순위도 다 외우기 힘들기 때문에, 되도록이면 본인이 원하는 연산 순서대로 소괄호로 묶어서 명시적으로 개발하는 버릇을 들이는 것을 추천합니다. 그리고 한 라인에 너무 많은 계산을 해서 소괄호가 제대로 닫혔는지 안닫혔는지 알 수 없을 정도라면, 분리하는 것도 추천드립니다. 프로그래밍 에서의 가독성은 협업할 때 굉장히 중요하니까요.
다음 코드를 실행 했을 때 처리되는 result 연산식의 순서는 어떻게 될까요?
int var1 = 1;
int var2 = 3;
int var3 = 2;
int result = var1 + var2 * var3;
단항 연산자는 피연산자가 하나뿐인 연산자를 말합니다. 여기에는 부호 연산자(+, -), 증감 연산자(++, --), 논리 부정 연산자(!), 비트 반전 연산자(~)가 있습니다.
부호 연산자에는 양수나 음수를 표시하는 +, -가 있습니다.
int i1 = +100;
int i2 = -100;
double d1 = +3.14;
double d2 = -10.5;
부호 연산자는 변수값의 부호를 유지하거나 바꾸기 위해 사용하기도 합니다.
다음의 예시를 보고 풀어보겠습니다.
public static void main(String[] args) {
int x = -100;
int result1 = +x;
int result2 = -x;
System.out.println(result1);
System.out.println(result2);
}
증감 연산자는 변수값을 1 증가(++) 시키거나 감소(--) 시키는 연산자를 말합니다. boolean 타입을 제외한 모든 기본 타입의 피연산자에 사용할 수 있습니다.
| 연산식 | 설명 |
|---|---|
| ++피연산자 | 다른 연산을 수행하기 전에 피연산자의 값을 1 증가시킴 |
| --피연산자 | 다른 연산을 수행하기 전에 피연산자의 값을 1 감소시킴 |
| 피연산자++ | 값을 먼저 읽어온 후에 피연산자의 값을 1 증가시킴 |
| 피연산자-- | 값을 먼저 읽어온 후에 피연산자의 값을 1 감소시킴 |
++ 연산자는 피연산자의 기존 값에 1을 더해서 그 결과를 다시 피연산자에 저장합니다.
예를들어 num 변수의 기존 값이 5라면, ++num 연산 후 num변수의 값은 6이 됩니다. 그래서 ++연산자를 증가 연산자라고 부릅니다.
package chapter03;
public class IncreaseDecreaseOperatorExample {
public static void main(String[] args) {
int x = 10;
int y = 10;
int z;
System.out.println("----------------------");
x++;
++x;
System.out.println("x=" + x);
System.out.println("----------------------");
y--;
--y;
System.out.println("y=" + y);
System.out.println("----------------------");
z = x++;
System.out.println("z=" + z);
System.out.println("x=" + x);
System.out.println("----------------------");
z = ++x;
System.out.println("z=" + z);
System.out.println("x=" + x);
System.out.println("----------------------");
z = ++x + y++;
System.out.println("z=" + z);
System.out.println("x=" + x);
System.out.println("y=" + y);
}
}
위 코드는 어떻게 출력될까요?
생각해봅시다.
출력 결과는?

논리 부정 연산자는 true를 false로, false를 true로 변경합니다. 그렇기 때문에 boolean 타입에만 사용할 수 있습니다.
논리 부정 연산자는 조건문, 제어문에서 사용되어 조건식의 값을 부정할 때 사용합니다. 그렇게 실행 흐름을 제어하죠.
그리고 두 가지 상태(true / false)를 번갈아가며 변경하는 토글(Toggle) 기능 구현시에도 주로 사용합니다.
public class DenyLogicOperatorExample {
public static void main(String[] args) {
boolean play = true;
System.out.println(play);
play = !play;
System.out.println(play);
play = !play;
System.out.println(play);
}
}
위 코드는 어떻게 출력될까요?

이항 연산자는 피연산자가 두 개인 연산자를 의미합니다.
산술 연산자는 사칙연산과 유사합니다.
| 산술 연산자 | 설명 |
|---|---|
| + | 덧셈 |
| - | 뺄셈 |
| * | 곱셈 |
| / | 나눗셈 |
| % | 왼쪽의 피연산자를 오른쪽의 피연산자로 나눈 나머지 |

% 연산자가 생소하실텐데 나눗셈을 수행하고 몫이 아닌 나머지를 돌려주는 연산자입니다. 10 나누기 3은 몫이 3이고 나머지는 1이므로, 10 % 3 == 1 입니다.
만약 피연산자들의 타입이(byte, int, long, double, …) 동일하지 않다면 아래와 같은 규칙으로 연산이 됩니다.
위의 규칙에 따라 다음 코드는 에러가 발생합니다.
byte a = 1;
byte b = 1;
byte c = a + b; // 에러. byte는 int로 변환 후 연산되기 때문
예제를 좀 더 살펴봅시다.
int a = 10;
int b = 4;
int c = a / b; // 2
double d = a / b; // 2.0
10 나누기 4는 2.5 이지만 c 변수에는 소수점 이하 부분을 버리고 2라는 결과가 저장됩니다. 그럼 d처럼 double 타입을 사용하면 2.5가 저장될까요? 아닙니다. int 타입의 연산이기 때문에 결과는 2가 되고 2를 실수화하여 2.0이 저장됩니다.
만약 2.5를 결과로 얻고 싶다면 어떻게 해야할까요? 피연산자 중 하나가 실수 타입이면 됩니다. 규칙에 따르면 피연산자 중 실수 타입이 있으면 모두 실수 타입으로 변환 후 연산합니다.
int a = 10;
int b = 4;
double c = (double)a / b; // 2.5
이러한 결과값도 있겠다!
char 타입도 산술 연산이 가능합니다. 예를 들어 ‘A’ 는 65라는 유니코드를 가지기 때문에 ‘A’ + 1 은 66이 됩니다. 자바에서는 리터럴 간의 연산은 타입 변환 없이 계산하기 때문에 ‘A’ + 1 은 유니코드 66에 해당하는 ‘B’ 가 됩니다.
char c1 = 'A' + 1; // 'B' (유니코드 66은 문자 B)
char c2 = 'A';
char c3 = c2 + 1; // 에러
c3 변수의 경우 에러가 발생하는데 이유는 천천히 생각해보셨으면 합니다. 위의 규칙을 참고해주시면 됩니다.
int a = 1000000;
int b = 1000000;
int c = a * b;
System.out.println(c); // -727379968
위의 코드에서 곱셈의 결과가 엉뚱한 값임을 볼 수 있습니다. 이는 곱셈의 결과가 int 타입에 저장할 수 있는 범위를 초과하였기 때문입니다. 이와 같은 현상을 오버플로우라고 하며 int 타입을 long으로 수정하면(크기가 큰 타입으로 바꾸면) 해결할 수 있습니다.
NaN, Infinity는 실수 타입 연산 중에 발생할 수 있습니다.
double a = 10;
double b = 0;
System.out.println(a / b); // Infinity
System.out.println(a % b); // NaN
NaN은 Not a Number이고, Infinity는 무한대라는 의미입니다. NaN 또는 Infinity가 연산의 결과로 나오면 다음 연산을 수행해서는 안됩니다. 이 값에 어떤 연산을 해도 NaN 또는 Infinity가 되기 때문입니다.
double a = 10;
double b = 0;
System.out.println(a / b + 1); // Infinity
System.out.println(a % b + 1); // NaN
NaN, Infinity를 체크할 수 있는 방법은 아래와 같습니다.
double a = 10;
double b = 0;
System.out.println(Double.isInfinite(a / b)); // true
System.out.println(Double.isNaN(a % b)); // true
오 ㅋㅋ
+ 연산자는 산술 연산자, 부호 연산자인 동시에 문자열 연결 연산자이기도 합니다. 피연산자 중 한쪽이 문자열이면 + 연산자는 문자열 연결 연산자로 사용되어 문자열을 결합합니다.
String str1 = "Hello!";
String str2 = str1 + "Nice to meet you";
System.out.println(str2); // Hello!Nice to meet you
만약 + 연산자를 이용하여 문자열과 숫자를 연결하면 어떻게 될까요? 문자열과 숫자가 혼합된 경우 왼쪽에서부터 오른쪽으로 연산이 진행됩니다.
// 문자열 "Hello"와 123이 먼저 연산되어 "Hello123"이 되고,
// 이것을 다시 456과 연산하여 "Hello123456"이 됩니다.
System.out.println("Hello" + 123 + 456); // Hello123456
// 숫자 123과 456이 먼저 연산되어 579가 되고,
// 이것을 문자열 "Hello"와 연산하여 "579Hello"가 됩니다.
System.out.println(123 + 456 + "Hello"); // 579Hello
비교 연산자는 피연산자들을 비교하여 boolean 타입인 true, false 를 산출합니다.
크기 비교
| 연산식 | 설명 |
|---|---|
| A > B | A가 B보다 큰 지 검사 |
| A >= B | A가 B보다 크거나 같은지 검사 |
| A < B | A가 B보다 작은 지 검사 |
| A <= B | A가 B보다 작거나 같은지 검사 |
동등 비교
| 연산식 | 설명 |
|---|---|
| A == B | A와 B가 같은지 검사 |
| A != B | A와 B가 다른지 검사 |
만약 피연산자가 char 타입이면 유니코드 값으로 비교 연산을 수행합니다.
('A' < 'B') -> (65 < 66)
비교 연산 시 연산을 수행하기 전에 둘 중에 크기가 더 큰 타입으로 일치시킵니다. 예를 들어 아래와 같이 char 타입과 int 타입을 비교할 때는 int로 타입을 일치시킨 후 비교합니다.
'A' == 65 // true
10 == 10.0 // true (int타입인 10을 double 타입인 10.0으로 변환 후 비교)
아래의 예제는 double 타입과 float 타입을 비교합니다.
0.1 == 0.1f // false
오른쪽의 0.1f가 double로 변환되어 0.1 == 0.1 의 결과로 true가 되어야할 것 같지만 그렇지 않습니다. 그 이유는 모든 부동소수점 타입은 0.1을 정확히 표기할 수 없어서 0.1f는 0.1의 근사 값이(예를 들어 0.100000000149와 같은 값) 되기 때문입니다.
피연산자를 모두 float 타입으로 변환하거나 int로 변환하면 이런 문제를 해결할 수 있습니다.
(float)0.1 == 0.1f // true
(int)(0.1 * 10) == (int)(0.1f * 10) // true
String 타입의 문자열은 대소 비교를 할 수 없고, 동등 비교는 할 수 있습니다. 다만 문자열이 같은지 다른지를 비교하는 용도로 사용할 때는 주의해야합니다. 아래의 예제로 확인해보겠습니다.
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
str1, str2, str3가 모두 동일한 문자열이지만 str1과 str2는 같고, str1과 str3는 다르다는 결과를 볼 수 있습니다. 자바는 문자열이 동일하다면 동일한 String 객체를 참조합니다. 그래서 str1과 str2는 동일한 String 객체의 주소 값을 가지고 있습니다. 반면 str3는 new 로 생성한 새로운 String 객체이므로 새로운 주소를 가지고 있습니다. 아래에 그림으로 표현하였습니다.

이와 같이 String 타입에 동등 연산을 할 경우 객체의 주소 값이 같은지 다른지를 확인합니다.
그렇다면 문자열 자체가 같은지 다른지 비교하려면 어떻게 해야할까요? equals() 메소드를 이용하면 됩니다.
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
System.out.println(str1.equals(str2)); // true
System.out.println(str1.equals(str3)); // true
논리 연산자는 조건식을 연결할 때 사용하는 연산자입니다.
논리 연산자의 피연산자는 boolean 타입만 사용할 수 있으며 종류와 기능은 아래와 같습니다.
피연산자가 모두 true일 경우에만 결과가 true 입니다.
| x | y | x && y |
|---|---|---|
| true | true | true |
| true | false | false |
| false | true | false |
| false | false | false |
피연산자 중 하나만 true이면 결과는 true 입니다.
| x | y | x |
|---|---|---|
| true | true | true |
| true | false | true |
| false | true | true |
| false | false | false |
피연산자가 하나는 true이고 다른 하나는 false일 경우에만 결과가 true 입니다.
| 연산식 | 결과 |
|---|---|
| true ^ true | false |
| true ^ false | true |
| false ^ false | false |
피연산자의 논리 값을 바꿉니다.
| 연산식 | 결과 |
|---|---|
| !true | false |
| !false | true |
다음의 문제를 보면서 논리연산자를 익혀봅시다.
**x > 5 && x < 13** 으로 쓸 수 있습니다.데이터를 비트 단위로 연산할 때 사용합니다. 이진수로 표현이 가능한 int 타입만 비트 연산을 할 수 있습니다. (비트는 0과 1로 이루어져있습니다.)
비트 논리 연산자에는 &, |, ^, ~ 가 있습니다. &, |, ^ 연산자는 피연산자가 boolean이면 일반 논리 연산자이고, 피연산자가 정수이면 비트 논리 연산자로 동작합니다.
| x | y | x & y | x | y |
|---|---|---|---|---|
| 1 | 1 | 1 | 1 | 0 |
| 1 | 0 | 0 | 1 | 1 |
| 0 | 1 | 0 | 1 | 1 |
| 0 | 0 | 0 | 0 | 0 |
AND (논리곱)
두 비트가 모두 1일 경우에만 결과가 1입니다.
| 연산식 | 결과 |
|---|---|
| 1 & 1 | 1 |
| 1 & 0 | 0 |
| 0 & 0 | 0 |
OR (논리합)
두 비트 중 하나만 1이면 결과가 1입니다.
| 연산식 | 결과 |
|---|---|
| 1 | 1 |
| 1 | 0 |
| 0 | 0 |
XOR (배타적 논리합)
두 비트 중 하나는 1이고 다른 하나는 0일 경우 결과가 1입니다.
| 연산식 | 결과 |
|---|---|
| 1 ^ 1 | 0 |
| 1 ^ 0 | 1 |
| 0 ^ 0 | 0 |
NOT (논리 부정)
0은 1로, 1은 0으로 바꿉니다.
| 연산식 | 결과 |
|---|---|
| ~1 | 0 |
| ~0 | 1 |
예를 들어 15와 30을 비트 논리 연산해봅시다. 15를 이진수로 표현하면 01111 이고, 30을 이진수로 표현하면 11110 입니다.
01111 과 11110 의 비트 논리 연산 결과는 아래와 같습니다.
논리곱 (&)
01111 & 11110 == 01110논리합 (|)
01111 & 11110 == 11111배타적 논리합 (^)
01111 & 11110 == 10001논리 부정 (~)
01111 == 10000비트 이동 연산자는 정수 데이터의 비트를 좌측 또는 우측으로 밀어서 이동시키는 연산을 수행합니다. 비트를 좌측으로 이동하면 자릿수가 늘어나고, 우측으로 이동하면 자릿수가 줄어드는 효과겠죠.
| 연산식 | 결과 |
|---|---|
| a << b | 정수 a의 각 비트를 b만큼 왼쪽으로 이동 (빈자리는 0으로 채워짐) |
| a >> b | 정수 a의 각 비트를 b만큼 오른쪽으로 이동 (빈자리는 최상위 부호 비트(MSB)와 같은 값으로 채워짐) |
| a >>> b | 정수 a의 각 비트를 b만큼 오른쪽으로 이동시키며, 새로운 비트는 전부 0으로 채워짐 |
정수 8을 왼쪽으로 2비트 이동시켜봅시다. 정수 8을 2진수로 표현하면 00001000 입니다.

비트 전체를 왼쪽으로 이동할 때 맨 왼쪽 2비트는 버려지고, 맨 오른쪽에 새로 생긴 2비트는 0으로 채워집니다. 연산 결과를 10진수로 변환해보면 32가 됨을 알 수 있습니다.
이번에는 정수 -8을 오른쪽으로 2비트 이동시켜봅시다.

비트 전체를 오른쪽으로 이동할 때 맨 오른쪽 2비트는 밀려서 버려지고, 맨 왼쪽에 새로 생긴 2비트는 최상위 부호비트(MSB)와 동일한 값으로 채워집니다. (최상위 부호비트가 0이면 양수, 1이면 음수입니다.) 연산 결과의 최상위 부호비트가 1이므로 2의 보수를 적용해보면 -2라는 값이 됨을 알 수 있습니다. (컴퓨터가 음수를 저장하기위해 2의 보수라는 방법을 사용합니다.)
이번에는 정수 -8을 >>> 연산자로 2비트 오른쪽으로 이동시켜봅시다.

비트 전체를 오른쪽으로 이동할 때 맨 오른쪽 2비트는 밀려서 버려지고, 맨 왼쪽에 새로 생긴 2비트는 무조건 0으로 채워집니다. 맨 왼쪽 최상위 부호비트가 0이 되었기 때문에 음수였던 값이 양수로 바뀌었음을 알 수 있습니다.
삼항 연산자는 세 개의 피연산자가 있는 연산자를 말합니다.
아래 연산식에서 ? 앞의 조건식에 따라 콜론( : ) 앞 뒤의 연산자가 선택되는 조건 연산식 이라고 할 수 있습니다.

예시를 하나 볼게요.
public class Example {
public static void main(String[] args) {
int score = 95;
char grade = (score > 90) ? 'A' : 'B';
System.out.println("당신의 학점은 : " + grade);
}
}
(score > 90) 을 연산하면 true 이고, true일 경우에는 첫번째 값 또는 연산식이 결과가 됩니다.
삼항연산자는 if조건절과 비슷하게 조건에 따른 값 선택을 하는 연산이기 때문에 if문으로 변경해서 작성할 수도 있겠죠. 위의 코드를 if문으로 변경해보겠습니다.
public class Example {
public static void main(String[] args) {
int score = 95;
char grade;
if (score > 90) {
grade = 'A';
} else {
grade = 'B';
}
System.out.println("당신의 학점은 : " + grade);
}
}
이처럼 삼항연산자는 if문으로 변경해서 작성할 수도 있지만, 간단한 연산식이라면 삼항연산자 한 줄로 간단하게 사용하는것이 더욱 효율적입니다. 가독성을 해치지 않는 선에서 실무에서도 굉장히 많이 사용하는 연산자입니다.
그럼 아래 코드에 대한 출력을 맞춰보세요. 여러분들이 문제를 맞췄다면 삼항연산자의 개념은 이해를 한겁니다.
public class Example {
public static void main(String[] args) {
int score = 95;
char grade = (score > 90) ? 'A' : ((score > 85) ? 'B' : 'C');
System.out.println("당신의 학점은 : " + grade);
}
}