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. 논리 연산자 (&&, ||, &, |, ^, !)
📍&&와 &는 산출 결과는 같지만, 연산 과정이 조금 다른데, &&는 앞의 피연산자가 false라면 뒤의 피연산자는 확인하지 않고 바로 false 산출하지만, &는 두 피연산자를 모두 평가해 산출함 → &보다 &&가 더 효율적! ||와 |도 마찬가지
구분 연산식 설명 논리곱 (AND) && 또는 & 피연산자 모두가 true일 경우에만 true 논리합 (OR) || 또는 | 피연산자 중 하나만 true 이면 true 배타적 논리합 (XOR) ^ 피연산자가 하나는 true이고,
다른 하나가 false일 경우 true논리부정 (NOT) ! 피연산자의 논리값을 바꿈
3.4.5. 비트 연산자 (&, |, ^, ~, <<, >>, >>>)
- 데이터를 비트 단위로 연산함 (0과 1이 피연산자)
- 0과 1로 표현이 가능한 정수 타입만 비트 연산 가능
- 실수 타입인 float와 double은 비트 연산 불가
📝 비트 논리 연산자 (&, |, ^)
📍피연산자가 boolean일 경우 일반 논리 연산자이고, 피연산자가 정수 타입일 경우에는 비트 논리 연산자로 사용됨
구분 연산식 설명 논리곱 (AND) & 피연산자 모두가 1일 경우에만 1 논리합 (OR) | 피연산자 중 하나만 1 이면 1 배타적 논리합 (XOR) ^ 피연산자가 하나는 1이고,
다른 하나가 0일 경우 1논리부정 (NOT) ~ 보수
📍비트 연산자는 피연산자를 int 타입으로 자동 타입 변환 후 연산을 수행하므로, 수행 결과는 int 타입임
📝 비트 이동(shift) 연산자 (<<, >>, >>>)
- 정수 데이터의 비트를 좌측 또는 우측으로 밀어서 이동시키는 연산을 수행
📍 >> 연산은 최상위 부호 비트가 1이면 1로, 0이면 0으로 채워짐
구분 연산식 설명 이동(쉬프트) a << b 정수 a의 각 비트를 b만큼 왼쪽으로 이동
(빈 자리는 0으로 채워짐)a >> b 정수 a의 각 비트를 b만큼 오른쪽으로 이동
(빈 자리는 정수 a의 최상위 부호 비트(MSB)와 같은 값으로 채워짐)a >>> b 정수 a의 각 비트를 b만큼 오른쪽으로 이동
(빈 자리는 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';
'2022 여름방학 자바 스터디' 카테고리의 다른 글
[이것이 자바다] Ch10. 예외 처리 (0) | 2022.08.16 |
---|---|
[이것이 자바다] Ch06. 클래스 (0) | 2022.07.28 |
[이것이 자바다] Ch05. 참조 타입 (0) | 2022.07.15 |
[이것이 자바다] Ch04. 조건문과 반복문 (0) | 2022.07.14 |
[이것이 자바다] Ch02. 변수와 타입 (0) | 2022.07.07 |