본문으로 건너뛰기

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 (&&)

ABA && B
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse

OR (||)

ABA || B
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse

NOT (!)

A!A
truefalse
falsetrue

XOR (^)

ABA ^ B
truetruefalse
truefalsetrue
falsetruetrue
falsefalsefalse
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 호출됨
}
}
단락 평가 활용 시 순서 원칙
  1. null 체크 → 실제 사용: obj != null && obj.method()
  2. 비용 낮은 조건 먼저: 빠른 조건을 앞에 배치
  3. &&: 거짓이 될 가능성 높은 조건을 왼쪽에
  4. ||: 참이 될 가능성 높은 조건을 왼쪽에

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두 조건이 다를 때: 토글, 암호화
&, |단락 평가 없음두 조건 모두 평가가 필요한 특수 경우