Preface
이번 장에선 자바의 다양한 연산자를 공부했다.
기본적인 연산자는 파이썬과 동일하지만, 정수끼리의 연산, 정수와 실수의 연산, 실수끼리의 연산 등 세부적인 부분에선 신경써야 할 조건들이 조금 있는 것 같다.
또, 비트 연산자를 공부하며 해당 연산자의 사용성에 대한 의문이 생겼다.
실무에서 코드를 작성할 때 이진수 값을 다룰 상황이 없을 것이라 생각했지만, 현재 개발자로 재직중이신 분들께 여쭤본 결과 '여러 선택지가 적용 가능한 상태를 DB에 저장할 때 사용한다'라는 답변을 얻을 수 있었다.
해당 상황을 특정 예시를 들어 설명해 주셨지만, 진수끼리의 변환 방법을 완벽히 숙지한 상태가 아니라 100% 이해할 수 없었다.
유튜브를 통해 진수 변환 방법을 익힌 후 비트 연산자를 다시 한 번 공부할 생각이다.
1. 연산의 방향과 우선순위
- 프로그램에선 연산자의 연산 방향과 연산자 간의 우선순위가 정해져 있다.
1) 대부분의 연산자는 왼쪽에서부터 오른쪽으로 연산을시작한다.
2) 단항 연산자(++, --, ~, !), 부호 연산자(+, -), 대입 연산자(=, +=, -= etc.)는 오른쪽에서 왼쪽으로 연산된다.
3) 괄호 부분의 연산읜 최우선순위를 갖기 때문에 다른 연산자보다 우선 연산된다.
- 연산의 방향과 우선순위
1) 단항, 이항, 삼항 연산자 순으로 우선순위를 가진다.
2) 산술, 비교, 논리, 대입 연산자 순으로 우선순위를 가진다.
3) 단항과 대입 연산자를 제외한 모든 연산의 방향은 왼쪽에서 오른쪽이다.
4) 복잡한 연산식에는 괄호를 사용해서 우선순위를 정해준다.
2. 단항 연산자
- 단항 연산자: 피연산자가 하나뿐인 연산자
1) 부호 연산자(+. -)
2) 증감 연산자(++, --)
3) 논리 부정 연산자(!)
4) 비트 반전 연산자(~)
- 부호 연산자: 양수 및 음수를 표시하는 연산자.
→ 정수 또는 실수 타입 변수 앞에 붙일 수도 있는데, 이 경우 + 연산자는 변수 값의 부호를 유지하지만, - 연산자는 변수 값의 부호를 반대로 바꾼다.
package ch3;
public class signOperator {
public static void main(String[] args) {
int x = -100;
int result1 = +x;
int result2 = -x;
System.out.println("result1 = " + result1);
System.out.println("result2 = " + result2);
short s = 100;
// short result3 = -s; 부호 연산자의 산출 타입은 int 타입이 된다.
int result3 = -s;
System.out.println("result3 = " + result3);
}
}
- 증감 연산자: 변수의 값을 1 증가시키거나 1 감소시키는 연산자
package ch3;
public class increaseDecreaseOperatot {
public static void main(String[] args) {
int x = 10;
int y = 10;
int z;
System.out.println("----------");
x++;
++x;
System.out.println("x = " + x); // 12
System.out.println("----------");
y--;
--y;
System.out.println("y = " + y); // 8
System.out.println("----------");
z = x++; // z에 x값이 먼저 할당된 후 x 값이 증가한다.
System.out.println("z = " + z);
System.out.println("x = " + x);
System.out.println("----------");
z = ++x;
System.out.println("z = " + z); // 14
System.out.println("x = " + x); // 14
System.out.println("----------");
z = ++x + y++;
System.out.println("z = " + z); // 23
System.out.println("x = " + x); // 15
System.out.println("y = " + y); // 9
}
}
- 논리 부정 연산자: true를 false로, false를 true로 변경하는 연산자
- 비트 반전 연산자: 정수 타입(byte, short, int, long)의 피연산자에만 사용되며, 피연산자를 2진수로 표현했을 때 비트값인 0을 1로, 1은 0으로 반전한다.
1) 산출 결과는 부호가 반대인 새로운 값이다.
2) Integer.toBinaryString( ) 메소드: 정수값을 총 32비트의 이진 문자열로 리턴하는 메소드
→ 앞의 비트가 모두 0이면 0은 생략되고 나머지 문자열만 리턴한다.
3. 이항 연산자
- 피연산자인 두 개인 연산자
1) 산술 연산자(+, -, *, /, %)
2) 문자열 연결 연산자(+)
3) 대입 연산자
4) 비교 연산자
5) 논리 연산자
6) 비트 논리 연산자
7) 비트 이동 연산자
- 산술 연산자의 특징
1) long을 제외한 정수 타입 연산은 int 타입으로 산출되고, 피연산자 중 하나라도 실수 타입이면 실수 타입으로 산출된다.
2) 실수를 산출 결과로 얻고 싶다면 피연산자 중 최소한 하나는 실수 타입이어야 한다.
3) 리터럴(소스코드에서 직접 할당한 값) 간의 연산은 타입 변환 없이 해당 타입으로 계산한다.
package ch3;
public class arithmeticOperator {
public static void main(String[] args) {
int v1 = 5;
int v2 = 2;
int result1 = v1 + v2;
System.out.println("result1 = " + result1);
int result2 = v1 - v2;
System.out.println("result2 = " + result2);
int result3 = v1 * v2;
System.out.println("result3 = " + result3);
int result4 = v1 / v2;
System.out.println("result4 = " + result4);
int result5 = v1 % v2;
System.out.println("result5 = " + result5);
double result6 = (double) v1 / v2;
System.out.println("result6 = " + result6);
// 실수값을 산출 결과로 얻고 싶으면 피연산자 중 최소한 하나는 실수 타입이어야 한다.
System.out.println(4.0 % 0);
}
}
- 실제 코드에선 피연산자의 값을 직접 리터럴로 주기 보단 사용자에게 입력받거나 프로그램 실행 도중에 생성되는 데이터로 산술 연산이 수행되므로, 바로 산술 연잔자를 사용하기 보단 메소드를 이용해 오버플로우를 탐지하는 것이 좋다.
package ch3;
public class checkOverflow {
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;
}
}
- 정확한 계산을 할 땐 정수 타입을 사용하는 것이 좋다
→ 부동소수점 타입(float, double)은 0.1을 정확히 표현할 수 없어 근사치로 처리한다.
- 좌측 피연산자가 정수 타입인 경우 나누는 수인 우측 피연산자는 0을 사용할 수 없다.
→ 컴파일은 되지만, ArithmeticException(예외)가 발생한다.
- 나누는 수인 우측 피연산자로 실수 타입인 0.0 또는 0.0f를 사용하면 예외가 발생하는 대신 다른 현상이 발생한다.
1) ' / ' 연산의 결과: Infinity(무한대)
2) '%' 연산의 결과: NaN(Not a Number)
→ 연산의 결과로 위 두 값 중 하나가 나오면 연산을 중지해야 한다.
- / 와 % 연산자의 결과가 Infinity 또는 NaN인지 확인할 수 있는 메소드
1) Double.isInfinity( ): 값이 Infinity면 true를 리턴
2) Double.isNaN( ): 값이 NaN이면 true를 리턴
package ch3;
public class infinityAndNaNCheck {
public static void main(String[] args) {
int x = 5;
double y = 0.0;
double z = x / y;
// double z = x % y;
System.out.println(Double.isInfinite(z));
System.out.println(Double.isNaN(z));
if (Double.isInfinite(z) || Double.isNaN(z)) {
System.out.println("값 산출 불가");
} else {
System.out.println(z + 2);
}
}
}
- 부동소수점(실수)을 입력받을 때는 반드시 NaN 검사를 해야 한다.
- "NaN" 문자열은 Double.valueOf( ) 메소드에 의해 double 타입으로 변환되면 NaN이 된다.
1) NaN은 산술 연산이 가능하다.
2) NaN인지 검사할 때 == 연산자를 사용하면 안 된다.
→ NaN은 != 연산자를 제외한 모든 비교 연산자를 사용할 경우 false 값을 리턴한다.
package ch3;
public class inputDataCheckNaN {
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);
}
}
- 문자열 연결 연산자: 문자열을 서로 결합하는 연산자
1) 피연산자 중 한쪽이 문자열이면 + 연산자는 문자열 연결 연산자로 사용되어 다른 피연산자를 문자열로 변환하고 서로 결합한다.
2) 어떤 것이 먼저 연산되느냐에 따라 다른 결과가 나온다.
package ch3;
public class stringConcat {
public static void main(String[] args) {
String str1 = "JDK" + 6.0;
String str2 = str1 + " 특징";
System.out.println(str2);
String str3 = "JDK" + 3 + 3.0;
String str4 = 3 + 3.0 + "JDK";
System.out.println(str3);
System.out.println(str4);
}
}
- 비교 연산자: 대소 또는 동등을 비교해서 boolean 타입인 true/false를 산출하는 연산자
1) 연산을 수행하기 전에 타입 변환을 통해 피연산자의 타입을 일치시킨다.
2) 부동소수점 타입은 0.1을 정확히 표현할 수 없으므로 피연산자를 모두 float 타입으로 강제 타입 변환시킨 후 비교 연산을 하거나, 정수로 변환해서 비교해야 한다.
3) String 타입의 문자열을 비교할 때는 대소 연산자를 사용할 수 없다.
- == 연산자는 해당 변수가 참조하는 메모리 주소값을 비교하므로, 새로운 String 객체를 생성한 경우엔 동일한 문자열이라도 각각의 변수가 다른 주소를 참조한다.
→ 문자열 자체를 비교하는 equals( ) 메소르를 사용해야 한다.
package ch3;
public class stringEquals {
public static void main(String[] args) {
String strVar1 = "asd";
String strVar2 = "asd";
String strVar3 = new String("asd");
System.out.println(strVar1 == strVar2);
System.out.println(strVar1 == strVar3);
// == 연산자는 값의 주소를 비교하므로 새로운 객체는 다른 주소를 갖는다.
System.out.println();
System.out.println(strVar1.equals(strVar2));
System.out.println(strVar1.equals(strVar3));
}
}
- 논리 연산자: 논리곱(&&), 논리합(||), 베타적 논리합(^), 논리 부정(!) 연산을 수행하는 연산자
→ 피연산자는 boolean 타입만 사용할 수 있다.
1) 논리곱: 피연산자 모두가 참인 경우에만 연산 괄과가 참
2) 논리합: 피연산자 중 하나만 참이면 연산 결과는 참
3) 베타적 논리합: 피연산자가 하나는 참이고 다른 하나는 거짓일 때만 참
4) 논리 부정: 피연산자의 논리값을 바꿈
- 비트 연산자
1) 데이터를 비트(bit) 단위로 연산한다.
→ 0과 1이 피연산자
2) 정수 타입만 비트 연산을 할 수 있다.
3) 비트 논리 연산자(&, |, ^, ~): 0과 1을 연산한다.
4) 비트 이동 연산자(<<, >>, >>>): 비트를 좌측 또는 우측으로 이동한다.
- 비트 논리 연산자: 피연산자가 boolean 타입일 땐 일반 논리 연산자, 정수 타입일 땐 비트 논리 연산자로 사용된다.
1) 논리곱: 두 비트 모두 1일 경우에만 연산 결과가 1
2) 논리합: 두 비트 중 하나만 1이면 연산 결과는 1
3) 베타적 논리합: 두 비트 중 하나는 1이고 다른 하나가 0일 경우 연산 결과는 1
4) 논리 부정(~): 보수
- 비트 이동 연산자
1) <<: 정수 a의 각 비트를 b만큼 왼쪽으로 이동
→ 빈자리는 0으로 채워진다.
2) >>: 정수 a의 각 비트를 b만큼 오른쪽으로 이동
→ 빈자리는 정수 a의 최상위 부호 비트와 같은 값으로 채워진다.
3) >>>: 정수 a의 각 비트를 b만큼 오른쪽으로 이동
→ 빈자리는 0으로 채워진다.
- 대입 연산자는 모든 연산자들 중에서 가장 낮은 연산 순위를 가지므로 제일 마지막에 수행된다.
→ 연산의 진행 방향이 오른쪽에서 왼쪽이다.
4. 삼항 연산자
- 삼항 연산자(?:): 세 개의 피연산자를 필요로 하는 연산자로, 조건식에 따라 콜론 앞뒤의 피연산자가 선택된다.
→ true일 땐 콜론 앞, false일 땐 콜론 뒤의 값이 선택된다.
package ch3;
public class conditionalOperation {
public static void main(String[] args) {
int score = 85;
char grade = (score > 90) ? 'A' : ((score > 80) ? 'B' : 'C');
System.out.println(score + "점은 " + grade + "등급입니다.");
// int score = 85;
// char grade;
// if (score > 90) {
// grade = 'A';
// } else {
// if (score > 80) {
// grade = 'B';
// } else {
// grade = 'C';
// }
// }
}
}
'Java > 이것이 자바다' 카테고리의 다른 글
타입 변환 메소드 (2) | 2022.10.07 |
---|---|
참조 타입 (0) | 2022.10.07 |
조건문과 반복문 (0) | 2022.10.02 |
자바 시작하기, 변수와 타입 (0) | 2022.09.28 |
이것이 자바다 출처 (0) | 2022.09.26 |
댓글