Ch 3.2 단항 연산자 (Unary Operator)
단항 연산자(Unary Operator) 는 피연산자가 단 하나인 연산자입니다. 부호 연산자(+, -), 증감 연산자(++, --), 논리 부정 연산자(!), 비트 반전 연산자(~) 가 여기에 속합니다.
1. 단항 연산자 종류 요약
| 연산자 | 사용 형태 | 설명 |
|---|---|---|
+ | +a | 피연산자의 양의 부호 (거의 사용 안 함) |
- | -a | 피연산자의 부호를 반전 |
++ | ++a / a++ | 피연산자를 1 증가 (전위 / 후위) |
-- | --a / a-- | 피연산자를 1 감소 (전위 / 후위) |
! | !a | boolean 값을 반전 (true ↔ false) |
~ | ~a | 정수의 모든 비트를 반전 |
2. 부호 연산자 (+, -)
수학에서의 양수/음수 부호와 동일한 개념입니다.
+a: 피연산자의 값을 그대로 반환합니다 (실질적으로 아무 일도 하지 않음).-a: 피연산자의 부호를 반전한 값을 반환합니다.
public class SignOperator {
public static void main(String[] args) {
int a = 10;
int b = -a; // b = -10, a는 변하지 않음
int c = -b; // c = 10
System.out.println("a = " + a); // 10
System.out.println("b = " + b); // -10
System.out.println("c = " + c); // 10
// 음수를 다시 음수로 → 양수
int temperature = -15;
System.out.println("온도: " + temperature); // -15
System.out.println("절댓값처럼: " + (-temperature)); // 15
}
}
부호 연산자를 byte, short, char 타입에 적용하면 결과는 int 타입 으로 자동 변환됩니다.
byte b = 10;
// byte result = -b; // 컴파일 에러! -b의 결과는 int 타입
int result = -b; // 정상: int에 저장
System.out.println(result); // -10
// 명시적 캐스팅으로 byte에 저장하려면:
byte b2 = (byte)(-b); // 명시적 형변환 필요
boolean과 char(+ 부호 연산자의 경우)를 제외한 기본형에만 사용할 수 있습니다.
3. 증감 연산자 (++, --)
증감 연산자는 피연산자의 값을 1 증가시키거나 1 감소시킵니다. 반복문 에서 가장 빈번하게 사용됩니다.
- 증가 연산자(
++): 피연산자의 값을 1 증가 - 감소 연산자(
--): 피연산자의 값을 1 감소
전위형(Prefix)과 후위형(Postfix) 비교
연산자의 위치에 따라 식이 계산되는 시점 이 달라집니다.
| 형태 | 형식 | 동작 순서 |
|---|---|---|
| 전위(Prefix) | ++i, --i | 먼저 증감→ 그 값을 식에서 사용 |
| 후위(Postfix) | i++, i-- | 먼저 현재 값을 사용→ 그 후 증감 |
public class IncrementDecrement {
public static void main(String[] args) {
// 후위형(Postfix): 현재 값을 먼저 사용, 그 후 증가
int i = 5;
int j = i++; // j에 i의 현재 값(5)을 대입한 뒤, i를 1 증가
System.out.println("후위형 - i: " + i + ", j: " + j); // i=6, j=5
// 전위형(Prefix): 먼저 증가, 증가된 값을 사용
int x = 5;
int y = ++x; // x를 먼저 1 증가(6)시킨 뒤, 그 값(6)을 y에 대입
System.out.println("전위형 - x: " + x + ", y: " + y); // x=6, y=6
// 감소 연산자
int a = 10;
int b = a--; // b=10, a=9
int c = --a; // a=8, c=8
System.out.println("감소 - a: " + a + ", b: " + b + ", c: " + c); // a=8, b=10, c=8
}
}
단독 사용 시 전위·후위 차이 없음
증감 연산자를 단독으로 사용할 때(다른 연산이나 대입 없이)는 전위·후위의 차이가 없습니다.
int n = 0;
n++; // n = 1 (후위)
++n; // n = 2 (전위) → 단독 사용 시 동일
n--; // n = 1 (후위)
--n; // n = 0 (전위) → 단독 사용 시 동일
식 내에서의 전위·후위 예제 (심화)
public class PrefixPostfixInExpr {
public static void main(String[] args) {
int a = 3;
// 식 안에서 혼합 사용 → 복잡해서 실무에서는 지양
int result = a++ + ++a; // 3 + 5 = 8 (a는 5가 됨)
// 설명: a++(현재 값 3 사용 후 a=4), ++a(a=5로 먼저 증가 후 5 사용)
System.out.println("result = " + result); // 8
System.out.println("a = " + a); // 5
}
}
a++ + ++a 처럼 하나의 식에 증감 연산자를 여러 번 섞어 쓰면 가독성이 극도로 낮아지고 실수가 발생하기 쉽습니다. 반복문 카운터처럼 단독으로 사용하는 것이 권장됩니다.
반복문에서의 증감 연산자
public class LoopIncrement {
public static void main(String[] args) {
// for 루프에서 i++ (후위형) - 가장 일반적인 패턴
for (int i = 0; i < 5; i++) {
System.out.print(i + " "); // 0 1 2 3 4
}
System.out.println();
// for 루프에서 ++i (전위형) - 단독 사용이므로 결과 동일
for (int i = 0; i < 5; ++i) {
System.out.print(i + " "); // 0 1 2 3 4
}
System.out.println();
// while 루프에서 감소 연산자
int count = 5;
while (count > 0) {
System.out.print(count + " "); // 5 4 3 2 1
count--;
}
System.out.println();
}
}
++i vs i++ 성능 차이 (이론)Java의 기본형(int 등)에서는 JIT 컴파일러가 최적화하므로 실질적 성능 차이는 없습니다. 그러나 C++에서 반복자(Iterator) 같은 객체에는 전위형이 더 효율적입니다. Java에서는 스타일 차이로 봐도 무방하며, 관례적으로 i++ 을 더 많이 사용합니다.
4. 논리 부정 연산자 (!)
boolean 값을 반전 시킵니다. true는 false로, false는 true로 변환됩니다.
public class LogicalNot {
public static void main(String[] args) {
boolean isLoggedIn = false;
boolean isGuest = !isLoggedIn; // true
System.out.println("로그인 상태: " + isLoggedIn); // false
System.out.println("게스트 여부: " + isGuest); // true
// 조건문에서 활용
int age = 17;
boolean isAdult = (age >= 18);
if (!isAdult) {
System.out.println("미성년자입니다.");
}
// 이중 부정: !!x 는 x와 동일 (의미 없음)
boolean flag = true;
System.out.println(!flag); // false
System.out.println(!!flag); // true (권장하지 않는 표현)
}
}
! 연산자는 boolean 타입에만 사용 가능합니다. 다른 타입에 사용하면 컴파일 에러가 발생합니다.
int n = 5;
// boolean b = !n; // 컴파일 에러! int에는 ! 사용 불가
5. 비트 반전 연산자 (~)
정수형 피연산자의 모든 비트를 반전 시킵니다 (0은 1로, 1은 0으로). 결과는 -(n + 1) 과 동일합니다. 이는 2의 보수 표현 방식 때문입니다.
public class BitwiseNot {
public static void main(String[] args) {
int a = 5; // 2진수: 00000000 00000000 00000000 00000101
int b = ~a; // 2진수: 11111111 11111111 11111111 11111010 = -6
System.out.println("~5 = " + b); // -6
int c = 0;
System.out.println("~0 = " + (~c)); // -1
int d = -1; // 모든 비트가 1
System.out.println("~(-1) = " + (~d)); // 0
// 공식: ~n == -(n + 1)
for (int n = -3; n <= 3; n++) {
System.out.println("~" + n + " = " + (~n) + " [검증: -("+n+"+1) = " + (-(n+1)) + "]");
}
}
}
실행 결과:
~5 = -6
~0 = -1
~(-1) = 0
~-3 = 2 [검증: -(-3+1) = 2]
~-2 = 1 [검증: -(-2+1) = 1]
~-1 = 0 [검증: -(-1+1) = 0]
~0 = -1 [검증: -(0+1) = -1]
~1 = -2 [검증: -(1+1) = -2]
~2 = -3 [검증: -(2+1) = -3]
~3 = -4 [검증: -(3+1) = -4]
~ 연산자는 비트 마스킹, 특정 비트 끄기 등에서 사용됩니다.
int flags = 0b1111; // 모든 비트 ON
int mask = 0b0010; // 2번째 비트만 끄고 싶음
flags = flags & (~mask); // flags & 0b1101 = 0b1101
System.out.println(Integer.toBinaryString(flags)); // 1101
6. 단항 연산자 사용 시 주의사항
연산 후 타입 변환
byte, short 타입에 단항 산술 연산자를 적용하면 결과가 int로 승격됩니다.
byte b1 = 100;
byte b2 = 20;
// byte result = b1 + b2; // 컴파일 에러: 결과가 int
int result = b1 + b2; // 정상: 120
// 복합 대입 연산자는 내부적으로 캐스팅 처리
b1 += b2; // b1 = (byte)(b1 + b2) = 120
System.out.println(b1); // 120
오버플로우 주의
증감 연산자로 인해 자료형의 최댓값을 초과하면 오버플로우 가 발생합니다.
int max = Integer.MAX_VALUE; // 2147483647
max++;
System.out.println(max); // -2147483648 (오버플로우!)
int min = Integer.MIN_VALUE; // -2147483648
min--;
System.out.println(min); // 2147483647 (언더플로우!)
7. 실전 예제: 토글(Toggle) 기능 구현 패턴
토글은 어떤 상태를 켜고 끄는 스위치 같은 기능입니다. ! 연산자를 이용하면 간단하게 구현할 수 있습니다.
public class ToggleExample {
public static void main(String[] args) {
// 패턴 1: 불리언 토글
boolean isPlaying = false;
System.out.println("초기 상태: " + (isPlaying ? "재생 중" : "정지"));
// 버튼을 누를 때마다 상태 반전
isPlaying = !isPlaying;
System.out.println("1번 클릭: " + (isPlaying ? "재생 중" : "정지")); // 재생 중
isPlaying = !isPlaying;
System.out.println("2번 클릭: " + (isPlaying ? "재생 중" : "정지")); // 정지
isPlaying = !isPlaying;
System.out.println("3번 클릭: " + (isPlaying ? "재생 중" : "정지")); // 재생 중
System.out.println();
// 패턴 2: 증감 연산자를 이용한 카운터
int clickCount = 0;
System.out.println("클릭 횟수: " + clickCount); // 0
clickCount++;
clickCount++;
clickCount++;
System.out.println("3번 클릭 후: " + clickCount); // 3
clickCount--;
System.out.println("실행 취소 후: " + clickCount); // 2
System.out.println();
// 패턴 3: 방향 전환 (부호 연산자 토글)
int direction = 1; // 1: 오른쪽, -1: 왼쪽
System.out.println("초기 방향: " + (direction > 0 ? "오른쪽" : "왼쪽"));
direction = -direction; // 방향 반전
System.out.println("반전 후: " + (direction > 0 ? "오른쪽" : "왼쪽")); // 왼쪽
direction = -direction;
System.out.println("재반전: " + (direction > 0 ? "오른쪽" : "왼쪽")); // 오른쪽
System.out.println();
// 패턴 4: 비트 XOR를 이용한 토글 (비트 연산자 활용)
int lamp = 0; // 0: OFF, 1: ON
System.out.println("램프: " + (lamp == 1 ? "ON" : "OFF")); // OFF
lamp ^= 1;
System.out.println("스위치 후: " + (lamp == 1 ? "ON" : "OFF")); // ON
lamp ^= 1;
System.out.println("스위치 후: " + (lamp == 1 ? "ON" : "OFF")); // OFF
}
}
실행 결과:
초기 상태: 정지
1번 클릭: 재생 중
2번 클릭: 정지
3번 클릭: 재생 중
클릭 횟수: 0
3번 클릭 후: 3
실행 취소 후: 2
초기 방향: 오른쪽
반전 후: 왼쪽
재반전: 오른쪽
램프: OFF
스위치 후: ON
스위치 후: OFF
8. 핵심 정리
| 연산자 | 피연산자 타입 | 주요 특징 |
|---|---|---|
+, - | 숫자형 (byte/short → int로 승격) | 부호 표시, -는 값 반전 |
++, -- | 숫자형 | 전위: 먼저 증감 후 사용, 후위: 먼저 사용 후 증감 |
! | boolean만 | true ↔ false 반전 |
~ | 정수형 | 모든 비트 반전, 결과 = -(n+1) |
- 단순 카운터에는
i++또는++i중 일관성 있게 하나를 선택하세요 (Java에서는 성능 차이 없음). - 식 내에서 증감 연산자와 다른 연산자를 함께 사용하는 것은 피하세요.
- 토글 로직에는
!flag패턴을 사용하면 코드가 명확해집니다.