논리 연산자는 비트 연산자와 매우 비슷하지만, 피연산자로 불리언값(true 또는 false)만 올 수 있고, 연산 결과 또한 불리언 타입만을 가진다. 논리 AND(&&)는 두 값이 모두 true일 때만 true, 나머지는 모두 false 값을 가진다. 반면 논리 OR(||)은 두 값이 모두 false일 때만 false이며, 나머지는 모두 true이다. 논리 XOR(^)은 두 값이 다를 때는 true, 같을 때는 false의 연산 결과가 나타나며, 논리 NOT(!)은 true와 false를 반전시키는 연산자다.
비트 연산자의 0과 1 값의 연산 과정을 논리 연산자의 false와 true에 대응해 생각하면 비슷하다는 것을 알 수 있다.
쇼트 서킷
이러한 논리 연산은 비트 연산자로도 수행할 수 있다. 일반적으로 비트 연산자의 양쪽에 위치하고 있는 피연산자는 정숫값이지만, 양쪽 피연산자의 자리에 불리언값이 위치하면 비트 연산자는 비트 연산이 아닌 논리 연산을 수행한다.
그렇다면 논리 연산자로 논리 연산을 수행하는 것과 비트 연산자로 논리 연산을 수행하는 것의 차이점은 무엇일까? 그것은 바로 쇼트 서킷short circuit의 적용 여부다. 쇼트 서킷은 연산을 수행하는 과정에서 결과가 이미 확정됐을 때 나머지 연산 과정을 생략하는 것을 말한다. 예를 들어 (5 > 3) || (3 < 2)를 수행하고자 할 때 왼쪽의 (5 > 3)이 true이므로 오른쪽 항의 결과와 상관없이 결과는 항상 true일 것이다. 따라서 이때는 오른쪽 항인 (3 < 2)를 아예 읽지 않는 것이 바로 쇼트 서킷이다.
논리 연산자로 논리 연산을 수행할 때는 쇼트 서킷이 적용되지만 비트 연산자로 논리 연산을 수행하면 쇼트 서킷이 적용되지 않는다. 즉 비트 연산자일 때 계산과정에서 결과가 이미 확정돼도 나머지 연산을 모두 수행하는 것이다. 쇼트 서킷은 단순히 불필요한 계산 과정을 생략하는 것이므로 결과에는 아무런 영향을 미치지 않는다. 따라서 다음과 같이 논리 연산자를 사용하든 비트 연산자를 사용하든 논리 연산자의 결과는 같다.
논리 연산자와 비트 연산자의 결괏값 비교
//논리 연산자
System.out.println(true && false); // false 출력
System.out.println(true || (5 < 3)); // true 출력
System.out.println((5 >= 5) ^ (7 > 2)); // false 출력
//비트 연산자
System.out.println(true & false); // false 출력
System.out.println(true | (5 < 3)); // true 출력
System.out.println((5 >= 5) ^ (7 > 2)); // false 출력
두 연산자를 이용한 논리 연산의 결과가 항상 동일하면서 논리 연산은 불필요한 계산을 생략했으므로 논리 연산자가 좋아 보일 수도 있다. 하지만 쇼트 서킷을 의도적으로 적용하지 않아야 하는 때도 있다. ↓
논리, 비트 연산자를 이용한 논리 연산의 차이점
int a, b, c;
// 논리 연산자
a = 3; b = 3; c = 3;
System.out.println(false && a++ > 6); // false
System.out.println(true || b++ > 6); // true
System.out.println(true ^ c++ > 6); // true
System.out.println(a); // 3
System.out.println(b); // 3
System.out.println(c); // 4
//비트 연산자
a = 3; b = 3; c = 3;
System.out.println(false && a++ > 6); // false
System.out.println(true || b++ > 6); // true
System.out.println(true ^ c++ > 6); // true
System.out.println(a); // 4
System.out.println(b); // 4
System.out.println(c); // 4
논리 연산과 비트 연산의 각 오른쪽 피연산자에 증감 연산자가 포함돼 있다. 논리 연산자일 때 왼쪽 항의 결과로 이미 결과가 결정됐을 때는 오른쪽 항을 실행시키지 않으므로 증감 연산은 이뤄지지 않는다. 반면 비트 연산자를 이용해 동일한 논리 연산을 수행하면 연산 결과의 결정 시기와 관계 없이 항상 각 변수의 증감 연산이 수행된다.
논리 XOR으로 연산 결과를 계산하기 위해서는 항상 양쪽 값을 모두 확인해야 하므로 쇼트 서킷을 구조적으로 적용할 수 없다. 이것이 바로 논리 XOR연산과 비트 XOR 연산의 연산 기호(^)가 동일한 이유다.
// 논리 연산자
// @AND(&&)
System.out.println(true && true);
System.out.println(true && false);
System.out.println(true && (5 < 3));
System.out.println((5 <= 5) && (7 > 2));
System.out.println();
// @OR(||)
System.out.println(true || true);
System.out.println(true || false);
System.out.println(false || (5 < 3));
System.out.println((5 <= 5) || (7 > 2));
System.out.println();
// @XOR(^)
System.out.println(true ^ true);
System.out.println(true ^ false);
System.out.println(false ^ (5 < 3));
System.out.println((5 <= 5) ^ (7 > 2));
System.out.println();
// @NOT(!)
System.out.println(!true);
System.out.println(!false);
System.out.println(false || !(5 < 3));
System.out.println((5 <= 5) || !(7 > 2));
System.out.println();
//비트 연산자로 논리 연산 수행
System.out.println(true & true);
System.out.println(true & false);
System.out.println(false | (5 < 3));
System.out.println((5 <= 5) | (7 > 2));
System.out.println();
// @쇼트 서킷 사용 여부 (논리 연산자는 0, 비트 연산자 X)
int value1 = 3;
System.out.println(false && ++value1 > 6);
System.out.println(value1);
int value2 = 3;
System.out.println(false & ++value2 > 6);
System.out.println(value2);
int value3 = 3;
System.out.println(true || ++value3 > 6);
System.out.println(value3);
int value4 = 3;
System.out.println(true | ++value4 > 6);
System.out.println(value4);
결과
true
false
false
true
true
true
false
true
false
true
false
false
false
true
true
true
true
false
false
true
false
3
false
4
true
3
true
4