Ch 3.5 논리 연산자 (Logical Operator)
논리 연산자는 boolean 값들을 결합하여 더 복잡한 조건을 표현할 때 사용합니다. &&(AND), ||(OR), !(NOT), ^(XOR) 4가지가 있으며, 조건문과 반복문에서 핵심적인 역할을 합니다.
1. 논리 연산자 4가지 요약
| 연산자 | 이름 | 설명 |
|---|---|---|
&& | AND (그리고) | 두 조건이 모두 true일 때만 true |
|| | OR (또는) | 두 조건 중 하나라도 true면 true |
! | NOT (부정) | true ↔ false 반전 |
^ | XOR (배타적 OR) | 두 조건이 서로 다를 때 true |
2. 진리표(Truth Table) 완전 정리
AND (&&)
| A | B | A && B |
|---|---|---|
| true | true | true |
| true | false | false |
| false | true | false |
| false | false | false |
OR (||)
| A | B | A || B |
|---|---|---|
| true | true | true |
| true | false | true |
| false | true | true |
| false | false | false |
NOT (!)
| A | !A |
|---|---|
| true | false |
| false | true |
XOR (^)
| A | B | A ^ B |
|---|---|---|
| true | true | false |
| true | false | true |
| false | true | true |
| false | false | false |
public class LogicalTruthTable {
public static void main(String[] args) {
boolean[] values = {true, false};
System.out.println("=== AND (&&) ===");
for (boolean a : values) {
for (boolean b : values) {
System.out.printf("%5b && %5b = %5b%n", a, b, a && b);
}
}
System.out.println("\n=== OR (||) ===");
for (boolean a : values) {
for (boolean b : values) {
System.out.printf("%5b || %5b = %5b%n", a, b, a || b);
}
}
System.out.println("\n=== XOR (^) ===");
for (boolean a : values) {
for (boolean b : values) {
System.out.printf("%5b ^ %5b = %5b%n", a, b, a ^ b);
}
}
System.out.println("\n=== NOT (!) ===");
for (boolean a : values) {
System.out.printf("!%5b = %5b%n", a, !a);
}
}
}
3. 기본 사용 예제
public class LogicalBasic {
public static void main(String[] args) {
int score = 75;
int attendance = 85;
// AND: 점수도 70 이상이고 출석도 80 이상이면 합격
boolean pass = (score >= 70) && (attendance >= 80);
System.out.println("합격: " + pass); // true
// OR: 점수가 90 이상이거나 출석이 95 이상이면 우수상
boolean honor = (score >= 90) || (attendance >= 95);
System.out.println("우수상: " + honor); // false
// NOT: 불합격 여부
System.out.println("불합격: " + !pass); // false
// XOR: 둘 중 정확히 하나만 만족할 때
boolean highScore = score >= 90;
boolean highAttend = attendance >= 95;
System.out.println("한 가지만 우수: " + (highScore ^ highAttend)); // false (둘 다 false)
int x = 5;
// 범위 체크: 0 < x < 10
boolean inRange = (x > 0) && (x < 10);
System.out.println(x + "은 0~10 사이: " + inRange); // true
}
}
4. 단락 평가(Short-circuit Evaluation)
논리 연산자 &&와 ||는 왼쪽 피연산자만으로 결과가 확정되면 오른쪽 피연산자를 아예 평가하지 않습니다. 이를 단락 평가(Short-circuit Evaluation) 또는 단락 회로 평가 라고 합니다.
&&의 단락 평가
왼쪽이 false이면 전체 결과는 무조건 false → 오른쪽 실행 안 함.
public class ShortCircuitAnd {
public static void main(String[] args) {
int[] numbers = null;
// 위험한 코드: numbers가 null이면 numbers.length에서 NullPointerException
// if (numbers.length > 0 && numbers[0] == 1) { ... }
// 안전한 코드: null 체크를 먼저 → null이면 오른쪽은 실행되지 않음
if (numbers != null && numbers.length > 0) {
System.out.println("첫 번째 원소: " + numbers[0]);
} else {
System.out.println("배열이 null이거나 비어있습니다."); // 이 줄 실행
}
}
}
||의 단락 평가
왼쪽이 true이면 전체 결과는 무조건 true → 오른쪽 실행 안 함.
public class ShortCircuitOr {
public static void main(String[] args) {
String name = null;
// null이면 "게스트" 반환, null이 아니면 실제 이름 확인
// name이 null이면 오른쪽 name.equals() 는 실행되지 않음
boolean isAdmin = (name == null) || name.equals("admin");
// name == null → true → 단락 평가로 오른쪽 건너뜀
System.out.println("관리자 접근 허용: " + isAdmin); // true (null이므로 허용)
}
}
단락 평가의 성능 최적화
빠르게 결과가 판정되는 조건을 앞에 배치하면 성능이 개선됩니다.
public class ShortCircuitPerformance {
static int callCount = 0;
static boolean expensiveCheck() {
callCount++;
// 가정: 매우 복잡한 계산
System.out.println(" → expensiveCheck() 호출됨 (call #" + callCount + ")");
return true;
}
static boolean quickCheck(boolean val) {
System.out.println(" → quickCheck(" + val + ") 호출됨");
return val;
}
public static void main(String[] args) {
callCount = 0;
System.out.println("--- 빠른 false 먼저 ---");
// quickCheck가 false → expensiveCheck 호출 안 됨
boolean r1 = quickCheck(false) && expensiveCheck();
System.out.println("결과: " + r1); // false, expensiveCheck 미호출
callCount = 0;
System.out.println("\n--- 느린 check 먼저 ---");
// expensiveCheck 먼저 호출된 후 quickCheck 호출
boolean r2 = expensiveCheck() && quickCheck(false);
System.out.println("결과: " + r2); // false, expensiveCheck 호출됨
}
}
- null 체크 → 실제 사용:
obj != null && obj.method() - 비용 낮은 조건 먼저: 빠른 조건을 앞에 배치
&&: 거짓이 될 가능성 높은 조건을 왼쪽에||: 참이 될 가능성 높은 조건을 왼쪽에
5. 사이드 이펙트와 단락 평가 주의
단락 평가로 인해 오른쪽 표현식이 실행되지 않으면, 그 표현식의 부수 효과(side effect)도 발생하지 않습니다.
public class SideEffectWarning {
public static void main(String[] args) {
int i = 0;
// i++ 이 실행될 수도, 안 될 수도 있음
boolean result = (1 > 2) && (++i > 0); // 1>2 가 false → ++i 실행 안 됨
System.out.println("i = " + i); // 0 (증가 안 됨!)
System.out.println("result = " + result); // false
// OR의 경우
int j = 0;
boolean result2 = (1 < 2) || (++j > 0); // 1<2 가 true → ++j 실행 안 됨
System.out.println("j = " + j); // 0 (증가 안 됨!)
}
}
조건식 안에서 변수를 증감하거나 상태를 변경하는 코드는 단락 평가로 인해 의도한 대로 실행되지 않을 수 있습니다. 부수 효과가 있는 연산은 조건식 밖에서 별도로 처리하세요.
6. &와 &&, |와 ||의 차이
| 연산자 | 종류 | 단락 평가 | 피연산자 타입 |
|---|---|---|---|
&& | 논리 AND | 있음 | boolean |
& | 비트 AND / 논리 AND | 없음 | 정수형 또는 boolean |
|| | 논리 OR | 있음 | boolean |
| | 비트 OR / 논리 OR | 없음 | 정수형 또는 boolean |
&와 |를 boolean에 사용하면 논리 연산을 하지만, 단락 평가가 없어 항상 양쪽 모두 평가 합니다.
public class SingleVsDouble {
static boolean checkA() {
System.out.println("A 평가됨");
return false;
}
static boolean checkB() {
System.out.println("B 평가됨");
return true;
}
public static void main(String[] args) {
System.out.println("--- && (단락 평가 있음) ---");
boolean r1 = checkA() && checkB(); // A만 평가 (A=false → B 건너뜀)
System.out.println("결과: " + r1);
System.out.println("\n--- & (단락 평가 없음) ---");
boolean r2 = checkA() & checkB(); // A, B 모두 평가
System.out.println("결과: " + r2);
}
}
출력:
--- && (단락 평가 있음) ---
A 평가됨
결과: false
--- & (단락 평가 없음) ---
A 평가됨
B 평가됨
결과: false
&, |를 boolean에 사용하는 경우실무에서는 거의 사용하지 않습니다. 두 조건 모두 반드시 평가되어야 하는 특수한 경우(예: 두 메서드 모두 로그를 남겨야 함)에만 사용하세요. 일반적으로는 항상 &&, ||를 사용하는 것이 맞습니다.
7. 드모르간 법칙 (De Morgan's Law)
복잡한 조건식을 단순화하거나 반대 조건을 만들 때 드모르간 법칙을 활용합니다.
!(A && B) == (!A) || (!B)
!(A || B) == (!A) && (!B)
public class DeMorgan {
public static void main(String[] args) {
int age = 25;
boolean hasTicket = false;
// 원래 조건: 나이가 18 미만이거나 티켓이 없으면 입장 거부
boolean deny = (age < 18) || !hasTicket;
System.out.println("입장 거부: " + deny); // true
// 드모르간 법칙 적용: 입장 허용 = !(입장 거부) = !(age<18 || !hasTicket)
// = (age >= 18) && hasTicket
boolean allow = (age >= 18) && hasTicket;
System.out.println("입장 허용: " + allow); // false
// 검증: deny == !allow
System.out.println("검증: " + (deny == !allow)); // true
// 실용 예시: 조건 반전
boolean isWeekend = true;
boolean isHoliday = false;
// 평일이고 공휴일이 아닌 경우 (근무일)
boolean isWorkday = !isWeekend && !isHoliday;
// 드모르간: !(isWeekend || isHoliday)
boolean isWorkday2 = !(isWeekend || isHoliday);
System.out.println("근무일: " + isWorkday); // false
System.out.println("근무일2: " + isWorkday2); // false (동일 결과)
}
}
8. XOR(^) 활용
XOR는 두 값이 서로 다를 때 true를 반환합니다. 토글, 암호화 등에 활용됩니다.
public class XorUsage {
public static void main(String[] args) {
// 둘 중 정확히 하나만 참인지 확인
boolean condA = true;
boolean condB = false;
System.out.println("정확히 하나만 true: " + (condA ^ condB)); // true
condA = true;
condB = true;
System.out.println("정확히 하나만 true: " + (condA ^ condB)); // false (둘 다 true)
// 상태 토글 (비트 XOR)
int state = 0b1010;
int mask = 0b0110;
System.out.println("원래: " + Integer.toBinaryString(state)); // 1010
System.out.println("토글 후: " + Integer.toBinaryString(state ^ mask)); // 1100
// 두 변수 스왑 (임시 변수 없이)
int a = 5, b = 10;
a ^= b; // a = a XOR b = 5 XOR 10 = 15 (0101 ^ 1010 = 1111)
b ^= a; // b = b XOR a = 10 XOR 15 = 5 (1010 ^ 1111 = 0101)
a ^= b; // a = a XOR b = 15 XOR 5 = 10 (1111 ^ 0101 = 1010)
System.out.println("스왑 후 a=" + a + ", b=" + b); // a=10, b=5
}
}
9. 복잡한 조건식 작성 가이드
public class ComplexCondition {
public static void main(String[] args) {
int age = 25;
String role = "user";
boolean isPremium = true;
boolean isActive = true;
// 나쁜 예: 괄호 없이 && 와 || 혼용 → 우선순위 실수 가능
// boolean ok = age >= 18 && role.equals("admin") || isPremium && isActive;
// 위는 (age>=18 && role.equals("admin")) || (isPremium && isActive) 로 해석됨
// 좋은 예: 명시적 괄호로 의도를 드러냄
boolean isAdminAccess = (age >= 18) && role.equals("admin");
boolean isPremiumAccess = isPremium && isActive;
boolean hasAccess = isAdminAccess || isPremiumAccess;
System.out.println("접근 허용: " + hasAccess); // true
// 가독성을 위해 복잡한 조건은 변수로 분리
int score = 88;
int attendance = 90;
boolean examPassed = score >= 60;
boolean attendanceMet = attendance >= 80;
boolean extraCredit = score >= 85;
boolean finalPass = examPassed && attendanceMet;
boolean withBonus = finalPass || extraCredit;
System.out.println("최종 합격: " + withBonus); // true
}
}
10. 실전 예제: 로그인 유효성 검사
import java.util.Scanner;
public class LoginValidator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("아이디를 입력하세요: ");
String id = scanner.nextLine();
System.out.print("비밀번호를 입력하세요: ");
String password = scanner.nextLine();
// ===== 아이디 유효성 검사 =====
// 조건: 4~20자, 영문자와 숫자만 허용
boolean idLengthOk = (id.length() >= 4) && (id.length() <= 20);
boolean idPatternOk = id.matches("[a-zA-Z0-9]+"); // 영문자+숫자만
boolean idValid = idLengthOk && idPatternOk;
// ===== 비밀번호 유효성 검사 =====
// 조건: 8자 이상, 대문자 포함, 숫자 포함, 특수문자 포함
boolean pwLengthOk = password.length() >= 8;
boolean pwHasUpper = !password.equals(password.toLowerCase()); // 대문자 포함
boolean pwHasDigit = password.matches(".*[0-9].*"); // 숫자 포함
boolean pwHasSpecial = password.matches(".*[!@#$%^&*].*"); // 특수문자 포함
boolean pwValid = pwLengthOk && pwHasUpper && pwHasDigit && pwHasSpecial;
// ===== 결과 출력 =====
System.out.println("\n===== 유효성 검사 결과 =====");
System.out.println("[아이디]");
System.out.println(" 길이(4~20자): " + (idLengthOk ? "통과" : "실패 (" + id.length() + "자)"));
System.out.println(" 영문/숫자만: " + (idPatternOk ? "통과" : "실패 (특수문자 포함됨)"));
System.out.println(" 최종: " + (idValid ? "유효함" : "유효하지 않음"));
System.out.println("\n[비밀번호]");
System.out.println(" 길이(8자+): " + (pwLengthOk ? "통과" : "실패 (" + password.length() + "자)"));
System.out.println(" 대문자 포함: " + (pwHasUpper ? "통과" : "실패"));
System.out.println(" 숫자 포함: " + (pwHasDigit ? "통과" : "실패"));
System.out.println(" 특수문자 포함: " + (pwHasSpecial ? "통과" : "실패"));
System.out.println(" 최종: " + (pwValid ? "유효함" : "유효하지 않음"));
System.out.println("\n[로그인 시도]");
if (idValid && pwValid) {
System.out.println("입력 형식이 올바릅니다. 서버에 인증 요청을 보냅니다...");
} else {
System.out.println("입력 형식이 올바르지 않습니다. 다시 확인해 주세요.");
}
scanner.close();
}
}
예시 실행 결과 (id: user1, pw: Pass1!23 입력 시):
===== 유효성 검사 결과 =====
[아이디]
길이(4~20자): 통과
영문/숫자만: 통과
최종: 유효함
[비밀번호]
길이(8자+): 통과
대문자 포함: 통과
숫자 포함: 통과
특수문자 포함: 통과
최종: 유효함
[로그인 시도]
입력 형식이 올바릅니다. 서버에 인증 요청을 보냅니다...
11. 핵심 정리
| 연산자 | 특징 | 주요 사용 예 |
|---|---|---|
&& | AND, 단락 평가 있음 | null 체크 후 접근: obj != null && obj.doSomething() |
|| | OR, 단락 평가 있음 | 기본값 제공: value != null || useDefault() |
! | NOT | 조건 반전: !isEmpty(), !isError() |
^ | XOR | 두 조건이 다를 때: 토글, 암호화 |
&, | | 단락 평가 없음 | 두 조건 모두 평가가 필요한 특수 경우 |