본문 바로가기

2022 여름방학 자바 스터디

[이것이 자바다] Ch03. 연산자

3.1. 연산자와 연산식

- 연산자는 필요로 하는 피연산자의 수에 따라 단항, 이항, 삼항 연산자로 구분됨

다항 연산자: ++x;
이항 연산자: x + y;
삼항 연산자: (sum > 90) ? "A" : "B";

 

- 연산식은 반드시 하나의 값을 산출

- 연산식은 다른 연산식의 피연산자 위치에도 올 수 있음

boolean res = (x + y) < 5;

 

3.2. 연산의 방향과 우선순위

 

- 산술, 비교, 논리, 대입 연산자 순으로 우선순위를 가짐

- 복잡한 연산식은 괄호()를 사용하여 우선순위를 정해주는 것이 좋음

 

- 우선순위가 같은 연산자들끼리는 왼쪽에서 오른쪽으로 연산

100 * 2 / 3 % 5;

 

- 단항 연산자(++, --, ~, !), 부호 연산자(+, -), 대입 연산자(=, +=, -=, ...)는 오른쪽에서 왼쪽으로 연산

a = b = c = 5;

 

3.3. 단항 연산자

3.3.1. 부호 연산자 (+, -)

- boolean 타입과 char 타입을 제외한 나머지 기본 타입에서 사용 가능

- 산술 연산자이기도 하고, 부호 연산자이기도 함

 

- 일반적으로 부호 연산자를 정수 및 실수 리터럴 앞에 붙여 양수 및 음수를 표현함

int i1 = +100;
int i2 = -100;

double d1 = +3.14;
double d2 = -10.5;

 

- 정수 또는 실수 타입의 변수 앞에 붙일 수도 있다.

- 변수 값의 부호를 유지하거나 바꾸기 위해 사용함

int x = -100;
int res1 = +x;
int res2 = -x;

 

주의

- 부호 연산자의 산출 타입은 int 타입이므로, 다음과 같은 경우는 컴파일 에러

short s = 100;
short res = -s; // 컴파일 에러

 

- 이렇게 수정해줘야 함

short s = 100;
int res = -s;

 

3.3.2. 증감 연산자 (++, --)

- 변수의 값을 1 증가 시키거나 1 감소 시키는 연산자

- 증감 연산자가 변수 앞에 있으면 우선 1 증가 또는 감소를 시킨 후 다른 연산자와 계산

- 증감 연산자가 변수 뒤에 있으면 다른 연산자를 먼저 처리한 후 1 증가 또는 감소시킴

3.3.3. 논리 부정 연산자(!)

- true를 false로,  false를 true로 변경해줌

- 조건문과 제어문에서 사용되어 실행 흐름을 제어함

3.3.4. 비트 반전 연산자(~)

- 정수 타입(byte, short, int, long)의 피연산자에만 사용됨

- 피연산자를 2진수로 표현했을 때 비트 값인 0을 1로, 1을 0으로 반전해줌

- 부호 비트인 최상위 비트를 포함해서 모든 비트가 반전되기 때문에 부호가 반대인 새로운 값 산출됨

- 비트 반전 연산자 산출 타입은 int 타입

 

3.4. 이항 연산자

3.4.1. 산술 연산자 (+, -, *, /, %)

🔖 산술 연산자의 특징
- 피연산자들이 모두 정수 타입이고, int 타입보다 크기가 작은 타입일 경우 모두 int 타입으로 변환 후 연산을 수행
   int 타입 산출

- 피연산자들이 모두 정수 타입이고, long 타입이 있을 경우 모두 long 타입으로 변환 후 연산을 수행함
   long 타입 산출
- 피연산자 중 실수 타입이 있을 경우, 크기가 큰 실수 타입으로 변환 후 연산을 수행함
  → 실수 타입 산출

즉, long을 제외한 정수 타입 연산은 int 타입으로 산출되고,
피연산자 중 하나라도 실수 타입이면 실수 타입으로 산출.
byte b1 = 1;
byte b2 = 1;

int res = b1 + b2;

 

✔️ 산술 연산은 쉽게 할 수 있지만, 올바른 계산을 위해 값을 미리 검정해야 하고, 정확한 계산을 위해 실수 타입을 피해야 하며, 특수값 처리에 신경을 써야 한다.

 

📝 오버 플로우 탐지

? 산출 값이 산출 타입으로 충분히 표현 가능한지 살펴봐야 함

public class OverflowExample {

	public static void main(String[] args) {
		int x = 1000000;
		int y = 1000000;
		int z = x * y;
		
		System.out.println(z);

	}

}

// 실행 결과 : -727379968

→ 위 예제가 올바른 값을 얻기 위해서는 변수 x와 y 중 최소 하나라도 long 타입이 되어야 하고, 변수 z가 long 타입이어야 함

public class OverflowExample {

	public static void main(String[] args) {
		long x = 1000000;
		long y = 1000000;
		long z = x * y;
		
		System.out.println(z);

	}

}

// 실행 결과 : 1000000000000

 

📝 정확한 계산은 정수 사용

- 정확하게 계산해야 할 때는 부동 소수점(실수) 타입을 사용하지 않는 것이 좋음

public class AccuracyExample1 {

	public static void main(String[] args) {
		int apple = 1;
		double pieceUnit = 0.1;
		int number = 7;
		
		double res = apple - number * pieceUnit;
		
		System.out.println("사과 한 개에서 ");
		System.out.println("0.7 조각을 뺴면, ");
		System.out.println(res + "조각이 남는다.");

	}

}

// 예상된 결과는 0.3이지만,
// 실제 결과 : 0.29999999999999993

→ 정수 연산으로 변경해서 다시 계산

public class AccuracyExample2 {

	public static void main(String[] args) {
		int apple = 1;
		
		int totalPieces = apple * 10;
		int number = 7;
		int temp = totalPieces - number;
		
		double res = temp/10.0;
		
		System.out.println("사과 한 개에서 ");
		System.out.println("0.7 조각을 뺴면, ");
		System.out.println(res + "조각이 남는다.");

	}

}

// 결과 : 0.3 조각

 

📝 NaN과 Infinity 연산

- / 또는 % 연산자를 사용할 때 주의!

- 좌측 피연산자가 정수 타입인 경우 나누는 수인 우측 피연산자는 0을 사용할 수 없음

- 만일 0으로 나누면 컴파일은 정상적으로 되지만, ArithmeticException(예외)가 발생함

5 / 0    // ArithmeticException
5 % 0    // ArithmeticException

 

- 자바는 프로그램 실행 도중 예외가 발생하면 실행이 즉시 멈추고 프로그램은 종료됨

- ArithmeticException이 발생한 경우 프로그램이 종료되지 않도록 하려면 예외 처리

try {
    int z = 5 / 0;
    System.out.println("z : " + z);
} catch(ArithmeticException e) {
	System.out.println("0으로 나누면 안 됨");
}

 

- 그러나, 실수 타입인 0.0 또는 0.0f로 나누면 ArithmeticException이 발생하지 않음

- / 연산의 결과는 무한대 값을 가지며, % 연산의 결과는 Not a Number 을 가짐

- 이런 경우 이 값과 산술 연산을 하면 어떤 수와 연산해도 Infinity와 NaN이 산출되어 엉망이 됨

- 프로그램 코드에서 연산의 결과가 Infinity 또는 NaN인지 확인하려면 Double.isInfinite() 또는 Double.isNaN() 메소드 이용

5 / 0.0     // Infinity
5 % 0.0     // NaN

 

📝 입력값의 NaN 검사

- 부동소수점을 입력받을 때는 반드시 NaN 검사를 해야 함

public class InputDataCheckNaNExample1 {

	public static void main(String[] args) {
		String userInput = "NaN";
		double val = Double.valueOf(userInput);
		
		double currentBalance = 10000.0;
		
		currentBalance += val;
		System.out.println(currentBalance);

	}

}

→ NaN인지 조사하는 것으로 수정

public class InputDataCheckNaNExample2 {

	public static void main(String[] args) {
		String userInput = "NaN";
		double val = Double.valueOf(userInput);
		
		double currentBalance = 10000.0;
		
		if (Double.isNaN(val)) {
			System.out.println("NaN이 입력되어 처리할 수 없음 ");
			val = 0.0;
		}
		currentBalance += val;
		System.out.println(currentBalance);

	}

}

 

3.4.2. 문자열 연결 연산자 (+)

- 피연산자 중 한쪽이 문자열이면 + 연산자는 문자열 연결 연산자로 사용됨

- 문자열과 숫자가 혼합된 + 연산식은 왼쪽에서부터 오른쪽으로 연산이 진행됨

- 어떤 것이 먼저 연산되느냐에 따라 다른 결과가 나오므로 주의

"JDK" + 3 + 3.0;    // JDK33.0

3 + 3.0 + "JDK";    // 6.0JDK

 

3.4.3. 비교 연산자 (<, <=, >, >=, ==, !=)

- 대소 또는 동등을 비교하여 boolean 타입인 true/false를 산출함

- 대소 연산자는 boolean 타입을 제외한 기본 타입에 사용 가능

- 동등 연산자는 모든 타입에 사용 가능

- 비교 연산자는 흐름 제어문인 조건문과 반복문에서 주로 이용됨

public class CompareOperatorExample1 {

	public static void main(String[] args) {
		int num1 = 10;
		int num2 = 10;
		
		boolean res1 = (num1 == num2);
		boolean res2 = (num1 != num2);
		boolean res3 = (num1 <= num2);
		
		System.out.println("res1 : " + res1);
		System.out.println("res2 : " + res2);
		System.out.println("res3 : " + res3);
		
		char char1 = 'A';
		char char2 = 'B';
		boolean res4 = (char1 < char2);
		System.out.println("res4 : " + res4);
	}

}

 

- 비교 연산자에서도 연산을 수행하기 전에 타입 변환을 통해 피연산자의 타입을 일치시킴

'A' == 65  // true
3 == 3.0   // true

 

- 그러나 예외, 부동소수점 타입은 0.1을 정확히 표현할 수 없어 0.1f는 0.1의 근사값으로 표현되므로, 아래의 결과는 false

0.1 == 0.1f
public class CompareOperatorExample2 {

	public static void main(String[] args) {
		int v2 = 1;
		double v3 = 1.0;
		System.out.println(v2 == v3);  // true
		
		double v4 = 0.1;
		float v5 = 0.1f;
		System.out.println(v4 == v5);  // false
		System.out.println((float)v4 == v5);   // true
		System.out.println((int)(v4 * 10) == (int)(v5 * 10));   // true

	}

}

 

- String 타입의 문자열을 비교할 때는 대소 연산자 사용 불가, 동등 비교 연산자 사용 가능

- 그러나, 기본 타입인 변수의 값을 비교할 때는 == 연산자를 사용하지만, 

- 참조 타입인 String 변수를 비교할 때 == 연산자를 사용하면 다른 결과가 나올 수 있음

 

- 아래 코드의 경우, strVar1과 strVar2는 동일하지만, strVar3는 새로운 객체의 번지값을 가지고 있어 다름

String strVar1 = "신용권";
String strVar2 = "신용권";
String strVar3 = new String("신용권");

 

- 동일한 String 객체이건, 다른 String 객체이건 상관없이 String 객체의 문자열만을 비교하고 싶다면

- == 대신 equals() 메소드를 사용해야 함

- 즉, == 연산자는 값과 주소 둘다 체크, equals() 메소드는 값만 체크

public class StringEqualsExample {

	public static void main(String[] args) {
		String strVar1 = "신용권";
		String strVar2 = "신용권";
		String strVar3 = new String("신용권");
		
		System.out.println(strVar1 == strVar2);  //true
		System.out.println(strVar1 == strVar3);  //false
		System.out.println();
		System.out.println(strVar1.equals(strVar2));  //true
		System.out.println(strVar1.equals(strVar3));  //true
	}

}

 

3.4.4. 논리 연산자 (&&, ||, &, |, ^, !)

구분 연산식 설명
논리곱 (AND) && 또는 & 피연산자 모두가 true일 경우에만 true
논리합 (OR) || 또는 | 피연산자 중 하나만 true 이면 true
배타적 논리합 (XOR) ^ 피연산자가 하나는 true이고,
다른 하나가 false일 경우 true
논리부정 (NOT) ! 피연산자의 논리값을 바꿈
📍&&와 &는 산출 결과는 같지만, 연산 과정이 조금 다른데, &&는 앞의 피연산자가 false라면 뒤의 피연산자는 확인하지 않고 바로 false 산출하지만, &는 두 피연산자를 모두 평가해 산출함 → &보다 &&가 더 효율적! ||와 |도 마찬가지

 

3.4.5. 비트 연산자 (&, |, ^, ~, <<, >>, >>>)

- 데이터를 비트 단위로 연산함 (0과 1이 피연산자)

- 0과 1로 표현이 가능한 정수 타입만 비트 연산 가능

- 실수 타입인 float와 double은 비트 연산 불가

 

📝 비트 논리 연산자 (&, |, ^)

구분 연산식 설명
논리곱 (AND)  & 피연산자 모두가 1일 경우에만 1
논리합 (OR)  | 피연산자 중 하나만 1 이면 1
배타적 논리합 (XOR) ^ 피연산자가 하나는 1이고, 
다른 하나가 0일 경우 1
논리부정 (NOT) ~ 보수
📍피연산자가 boolean일 경우 일반 논리 연산자이고, 피연산자가 정수 타입일 경우에는 비트 논리 연산자로 사용됨
📍비트 연산자는 피연산자를 int 타입으로 자동 타입 변환 후 연산을 수행하므로, 수행 결과는 int 타입임

 

📝 비트 이동(shift) 연산자 (<<, >>, >>>)

- 정수 데이터의 비트를 좌측 또는 우측으로 밀어서 이동시키는 연산을 수행

구분 연산식 설명
이동(쉬프트) a << b 정수 a의 각 비트를 b만큼 왼쪽으로 이동
(빈 자리는 0으로 채워짐)
a >> b 정수 a의 각 비트를 b만큼 오른쪽으로 이동
(빈 자리는 정수 a의 최상위 부호 비트(MSB)와 같은 값으로 채워짐)
a >>> b 정수 a의 각 비트를 b만큼 오른쪽으로 이동
(빈 자리는 0으로 채워짐)
📍 >> 연산은 최상위 부호 비트가 1이면 1로, 0이면 0으로 채워짐

 

3.4.6. 대입 연산자 (=, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>=, >>>=)

- 오른쪽 피연산자의 값을 좌측 피연산자인 변수에 저장

- 모든 연산자들 중에서 가장 낮은 연산 순위를 가지고 있어 제일 마지막에 수행됨

 

3.5. 삼항 연산자

삼항연산자 ?:

- ? 앞의 조건식에 따라 콜론(:) 앞 뒤의 피연산자가 선택됨 (조건 연산식)

- 아래 두 코드는 같은 코드

int score = 95;
char grade = (score > 90) ? 'A' : 'B'
int score = 95;
char grade;

if (score > 90) grade = 'A';
else grade = 'B';