연산자
연산식
- 연산자와 피연산자를 이용하여 연산의 과정을 기술한 것
- 반드시 하나의 값을 산출한다.
- 따라서 하나의 값이 올 수 있는 곳이면 어디든지(변수에 저장되는 값 위치, 다른 연산식의 피연산자 위치) 값 대신에 연산식을 사용할 수 있다.
연산 방향과 우선순위
프로그램에서는 연산자의 연산 방향과 연산자 간의 우선 순위가 정해져 있다.
연산자 | 연산 방향 | 우선순위 |
---|---|---|
증감(++ , -- ), 부호(+ , - ), 비트(~ ), 논리(! ) | ← | 높음 |
산술(* , / , % ) | → | |
산술(+ , - ) | → | |
쉬프트(<< ,>> , >>> ) | → | |
비교(< ,> , <= , >= , instanceof ) | → | |
비교(== , != ) | → | |
논리(& ) | → | |
논리(^ ) | → | |
논리(| ) | → | |
논리(&& ) | → | |
논리(|| ) | → | |
조건(?: ) | → | |
대입(= , += , -= , *= , /= , %= , &= , ^= , |= , <<= , >>= , >>>= ) | ← | 낮음 |
- 단항, 이항, 삼항 연산자 순으로 우선순위를 가진다.
- 산술, 비교, 논리, 대입 연산자 순으로 우선 순위를 가진다.
- 단항과 대입 연산자를 제외한 모든 연산의 방향은 왼쪽에서 오른쪽이다(→).
- 복잡한 연산식에는 괄호()를 사용하여 우선순위를 정해준다.
단항 연산자
부호 연산자
+
: 피연산자의 부호 유지-
: 피연산자의 부호 변경- 피연산자 타입: boolean 타입과 char 타입 제외 기본 타입
- 산출 타입: int 타입
1
2
3
short s = 100;
//short result = -s; //컴파일 에러
int result = -s; //ok!
증감 연산자
++피연산자
: 다른 연산을 수행하기 전 피연산자 값을 1 증가(해서 그 결과를 다시 피연산자에 저장한다.)--피연산자
: 다른 연산을 수행하기 전 피연산자 값을 1 감소피연산자++
: 다른 연산을 수행한 후에 피연산자 값을 1 증가피연산자--
: 다른 연산을 수행한 후에 피연산자 값을 1 감소피연산자 타입: boolean 타입 제외 기본 타입
++i
와i = i + 1
의 바이트코드는 동일하다. 따라서 연산 속도는 동일하다.
논리 부정 연산자 (!)
- 조건문과 제어문에서 사용되어 조건식의 값을 부정하도록 해서 실행흐름을 제어할 때, 두 가지 상태를 번갈하면서 변경하는 토글(toggle) 기능 구현할 때 주로 사용된다.
1
2
3
4
5
6
7
8
boolean play = true;
System.out.println(play); //true
play = !play;
System.out.println(play); //false
play = !play;
System.out.println(play); //true
비트 반전 연산자(~)
- 피연산자 타입: 정수타입(
byte
,short
,int
,long
) - 산출 타입:
int
타입 피연산자는 연산을 수행하기 전에 int 타입으로 변환되고, 비트 반전이 일어난다.
- 부호 반대 정수 구하기: ~피연산자 + 1
Integer.toBinaryString()
: 정수값을 32비트 이진 문자열로 리턴하는 메서드이다(앞의 0은 생략된다).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void main(String[] args){
int v1 = 10;
int v2 = ~v1;
int v3 = ~v1 + 1;
System.out.println(toBinaryString(v1) + " (십진수: " + v1 + ")" );
System.out.println(toBinaryString(v2) + " (십진수: " + v2 + ")" );
System.out.println(toBinaryString(v3) + " (십진수: " + v3 + ")" );
int v4 = -10;
int v5 = ~v4;
int v6 = ~v4 + 1;
System.out.println(toBinaryString(v4) + " (십진수: " + v4 + ")" );
System.out.println(toBinaryString(v5) + " (십진수: " + v5 + ")" );
System.out.println(toBinaryString(v6) + " (십진수: " + v6 + ")" );
}
// 32개의 문자열 모두 얻는 메서드 만들기 (0 생략 X)
public static String toBinaryString(int value) {
String str = Integer.toBinaryString(value);
while(str.length() < 32) {
str = "0" + str;
}
return str;
}
// 결과
//00000000000000000000000000001010 (십진수: 10)
//11111111111111111111111111110101 (십진수: -11)
//11111111111111111111111111110110 (십진수: -10)
//11111111111111111111111111110110 (십진수: -10)
//00000000000000000000000000001001 (십진수: 9)
//00000000000000000000000000001010 (십진수: 10)
이항연산자
산술연산자(+, -, *, /, %)
long
을 제외한 정수 타입은int
타입으로 산출되고, 피연산자 중 하나로 실수타입이면 실수 타입으로 산출된다.- 피연산자들이 모두 정수타입이고, int타입(4byte)보다 크기가 작은 타입일 경우 모두 int타입으로 변환 후 연산을 수행한다.
- byte + byte => int + int => int
- JVM이 기본적으로 32비트
- 피연산자들이 모두 정수 타입이고, long 타입이 있을 경우 모두 long 타입으로 변환 후, 연산을 수행한다. 따라서 연산의 산출 타입은 long이다.
- int + long => long + long => long
- 피연산자 중 실수 타입이 있을 경우, 크기가 큰 실수 타입으로 변환 후 연산을 수행한다. 따라서 연산의 산출 타입은 실수 타입이다.
- int + double => double + double => double
- 피연산자들이 모두 정수타입이고, int타입(4byte)보다 크기가 작은 타입일 경우 모두 int타입으로 변환 후 연산을 수행한다.
- 자바는 리터럴 간의 연산은 타입 변환 없이 해당 타입으로 계산한다.
1
2
3
4
5
6
7
8
9
10
11
public class CharOperatorExample {
public static void main(String[] args) {
char c1 = 'A' + 1;
char c2 = 'A';
//char c3 = c2 + 1; // 컴파일 에러!
char c4 = (char) (c2 + 1);
System.out.println("c1: " + c1); //B
System.out.println("c2: " + c2); //A
System.out.println("c4: " + c4); //B
}
}
오버플로우 탐지
- 산술 연산 주의점: 연산 후 산출값이 산출 타입으로 표현 가능한 지
- 메소드를 이용하여 산술연산 전에 피연산자의 값을 조사해서 오버플로우를 탐지하도록 하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 산술연산 전에 오버플로우 탐지
public class CheckOverflowExample {
public static void main(String[] args) {
try {
int result = safeAdd(2000000000, 2000000000);
System.out.println(result);
} catch(ArithmeticException e) {
System.out.println("오버플로우가 발생하여 정확하게 계산할 수 없음")
}
}
public static int safeAdd(int left, int right) {
if (right > 0) {
if (left > (Integer.MAX_VALUE - right)) {
throw new ArithmeticException("오버플로우 발생");
}
} else {
if (left < (Integer.MIN_VALUE - right)) {
throw new ArithmeticException("오버플로우 발생");
}
}
return left + right;
}
}
// 실행 결과
// 오버플로우가 발생하여 정확하게 계산할 수 없음
정확한 계산은 정수 사용
1
2
3
4
5
6
7
8
9
10
11
12
int apple = 1;
double pieceUnit = 0.1;
int number = 7;
double result = apple - number * pieceUnit;
System.out.println("사과 한 개에서 0.7조각을 빼면");
System.out.println(result + "조각이 남는다.");
// 실행 결과
// 사과 한 개에서 0.7조각을 빼면
// 0.29999999999999993조각이 남는다.
이진 포맷을 사용하는 부동소수점 타입(float, double)은 0.1을 정확히 표현할 수 없어 근사치로 처리한다.
1
2
3
4
5
6
7
8
9
10
// 정확하게 계산할 때에는 부동소수점 타입을 사용하지 않는다.
int apple = 1;
int totalPieces = apple * 10;
int number = 7;
int temp = totalPieces - number;
double result = temp/10.0;
System.out.println("사과 한 개에서 0.7조각을 빼면");
System.out.println(result + "조각이 남는다.");
NaN과 Infinity 연산(/, %)
- 좌측 피연산자가 정수 타입인 경우 나누는 수인 우측 피연산자는 0을 사용할 수 없다.
1
2
5 / 0 -> ArithmeticException 발생
5 % 0 -> ArithmeticException 발생
1
2
3
4
5
6
7
8
//예외처리
try {
//int z = x / y;
int z = x / y;
System.out.println("z: " + z);
} catch (ArithmeticException e) {
System.out.println("0으로 나누면 안됨");
}
- 실수 타입인 0.0 또는 0.0f로 나누면 예외가 발생하지 않고 다음과 같은 값이 나온다.
1
2
5 / 0.0 => Infinity
5 % 0.0 => NaN
- 이 값은 어떤 수와 산술연산 하더라도 Infinity와 NaN이 산출된다.
- 따라서
Double.isInfinite()
,Double.isNaN()
메서드를 활용하여 연산의 결과가 Infinity 또는 NaN이면 다음 연산을 수행하지 못하도록 처리한다.
1
2
3
4
5
if (Double.isInfinite(z) || Double.isNaN(z)) {
System.out.println("값 산출 불가");
} else {
System.out.println(z);
}
입력값의 NaN 검사
- 부동소수점을 입력 받을 때는 반드시 NaN 검사를 해야 한다.
- “NaN” 문자열은 Double.valueOf() 메서드에 의해 double 타입으로 변환되면 NaN가 된다. NaN은 산술연산이 가능하기 때문에 데이터가 엉망이 될 수 있다.
1
2
3
4
5
6
7
8
String userInput = "NaN";
double val = Double.valueOf( userInput );
double currentBalance = 10000.0;
currentBalance += val;
System.out.println(currentBalance); //NaN
// 원래 데이터가 없어진다!
- 따라서 입력 받을 때 “NaN”인지 조사하고 “NaN”이라면 NaN과 산술연산을 수행하지 않도록 처리해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
// NaN이 입력되어 처리할 수 없음
// 10000.0
NaN인지 조사할 때 == 연산자를 사용하면 안 된다. NaN은 != 연산자를 제외한 모든 비교연산자를 사용할 경우 false값을 리턴하기 때문이다.
문자열 연결 연산자
- 문자열과 숫자가 혼합된 + 연산식은 왼쪽에서부터 오른쪽으로 연산이 진행된다.
1
2
"JDK" + 3 + 3.0; //JDK33.0
3 + 3.0 + "JDK"; //6.0JDK
비교 연산자(<, <=, >, >=, ==, !=)
- 산출 타입: boolean타입
- 피연산자 타입
- 대소 연산자: boolean 타입을 제외한 기본 타입
- 동등 연산자: 모든 타입
부동소수점 비교
- 비교연산자에서도 연산을 수행하기 전에 타입 변환을 통해 피연산자의 타입을 일치시킨다.
- 예외: 이진 포맷의 가수를 사용하는 모든 부동소수점 타입은 0.1을 정확하게 표현할 수 없어 0.1f는 0.1의 근사값으로 표현된다.
- 해결책: 피연산자를 모두 float타입으로 강제 타입 변환한 후 비교연산을 하거나, 정수로 변환해서 비교한다.
1
2
3
4
5
6
7
8
9
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 객체의 문자열만을 비교하고 싶다면 == 연산자 대신 ` equals()` 메서드를 사용해야 한다.
논리 연산자 (&&, ||, &, |, ^, !)
- 피연산자 타입: boolean 타입
- 종류
- AND(&&, &): 피연산자 모두가 true일 경우에만 연산 결과 true
OF( , ):피연산자 중 하나만 true이면 연산 결과는 true - XOR(^): 피연산자가 하나는 true이고 다른 하나가 false일 때 연산 결과는 true
- NOT(!): 피연산자와 논리값을 바꿈
- &&와 &는 산출 결과는 같지만 연산 과정이 다르다.
&&
,||
:&&
는 앞의 피연산자가 false라면 뒤의 피연산자를 평가하지 않고 바로 false라는 산출 결과를 낸다.||
는 앞의 피연산자가 true이면 뒤의 피연산자를 평가하지 않고 바로 true라는 산출 결과를 낸다. => 더 효율적!&
,|
: 두 피연산자 모두 평가하여 산출 결과를 낸다.
1
2
3
4
5
6
7
8
9
10
int charCode = 'A';
if ( (charCode >= 65) & (charCode <= 90))
System.out.println("대문자입니다.");
if ( (charCode >= 97) && (charCode <= 122))
System.out.println("소문자입니다.");
if ( !(charCode < 48) && !(charCode > 57))
System.out.println("0~9 숫자입니다.");
비트 연산자
- 피연산자 타입: 정수 타입
- 0과 1로 표현이 가능한 정수 타입만 비트 연산이 가능하다. 실수 타입(float, double)은 비트연산을 할 수 없다.
비트 논리 연산자 (&, |, ^, ~)
- 종류
&
: 두 비트 모두 1일 경우에만 연산 결과가 1|
: 두 비트 중 하나만 1이면 연산 결과는 1^
: 두 비트 중 하나는 1이고 다른 하나가 0일 경우 연산 결과는 1~
: 보수
- 피연산자(byte, short, char)를 int타입으로 자동 타입 변환 후 연산을 수행한다.
비트 이동 연산자(<<<
, >>
, >>>
)
- 종류
a << b
: 정수 a의 비트를 b만큼 왼쪽으로 이동(빈자리는 0으로 채워진다.)a >> b
: 정수 a의 비트를 b만큼 오른쪽으로 이동 (빈자리는 정수 a의 최상위 부호 비트(MSB)와 같은 값으로 채워진다.)a >>> b
: 정수 각 비트를 b만큼 오른쪽으로 이동 (빈자리는 0으로 채워진다.)
대입 연산자(=, +=, -=, *=, /=, %=, &=, ^=, |=, «=, »=, »>=)
- 종류
- 단순 대입 연산자: 단순히 오른쪽 피연산자의 값을 변수에 저장
- 복합 대입 연산자: 정해진 연산을 수행한 후 결과를 변수에 저장
- 모든 연산자들 중 우선순위가 가장 낮다: 제일 마지막에 수행된다.
- 진행방향이 오른쪽에서 왼쪽이다.
삼항 연산자
조건식(피연산자1) ? 값 또는 연산식(피연산자2) : 값 또는 연산식(피연산자3)
삼항연산자는 if문으로 변경해서 작성할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
int score = 95;
// 삼항연산자 사용
char grade = (score > 90) ? 'A' : 'B';
// if문 사용
char grade;
if(score > 90)
grade = 'A';
else
grade = 'B';