이항 연산자
1. 산술 연산자 ( +, -, *, /, % )
% : 나머지 연산 (나눗셈 후 몫이 아닌 나머지 반환)
long 타입을 제외한 정수 타입(byte, short, char) 연산은 모두 int 타입으로 산출되고
피연산자중 하나라도 실수 형이 존재한다면 실수 타입(double)으로 산출된다.
자바 가상 기계(JVM)가 기본적으로 32비트 단위로 계산하기 때문이다.
int int1 = 10;
int int2 = 4;
int result1 = int1 / int 2; // 2
double result2 = int1 / int2; // 2.0
정수형 피연산자의 나눗셈 결과는 소수부분(0.5)이 버려진 정수 부분만 산출된다.
만약 double 타입의 변수로 결과를 받는다 하더라도 결과값은 2를 실수화 한 2.0으로 변환되어 저장된다.
즉, 2.5 결과를 얻기 위해서는 피연산자 중 하나가 실수이어야 한다.
char 타입도 정수 타입이므로 산술 연산이 가능하다.
하지만 연산 결과 역시 int 타입으로 반환되므로 주의해야 한다.
public class CharOperationExam {
public static void main(String[] args) {
char c1 = 'A' + 1; // 66 에 해당되는 문자 저장
char c2 = 'A';
// char c3 = c2 + 1; 컴파일 에러 연산결과가 int로 반환되기 때문
System.out.println("c1 : "+c1); // c1 : B
System.out.println("c2 : "+c2); // c2 : A
//System.out.println("c3 : "+c3);
}
}
'A' + 1 에서 리터럴 간의 연산은 타입 변환 없이 해당 타입으로 계산된다. (char 유니코드 값 가짐 A = 65)
c3 결과를 얻으려면, char c3 = (char) (c2 + 1); // 강제 타입 변환(캐스팅)을 해야 한다.
오버플로우 탐지
산술 연산 시 산출값이 산출 타입으로 충분히 표현 가능해야 한다.
산출 타입으로 표현할 수 없을 때 오버플로우가 발생하고 쓰레기 값을 얻을 수 있다.
int 타입에 저장할 수 없는 범위의 값이기 때문에, 변수 x, y 중 최소 하나라도 long 타입이어야 하고,
변수 z 가 long 타입이어야 한다.
따라서 오버플로우 발생 시 탐지할 예외 처리가 필요하다.
public class CheckOverflowExam {
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;
}
}
정확한 계산은 정수를 사용한다.
정확한 계산 요구 시 부동소수점(실수)는 사용하지 않아야 한다.
public class AccuracyExam {
public static void main (String[] args) {
int apple = 1;
double pieceUnit = 0.1;
int number = 7;
double result = apple - number * pieceUnit; // 1 - 0.7
System.out.println("사과 한 개에서 0.7 조각을 빼면?");
System.out.println(result + "조각이 남는다."); // 0.29999999..
}
}
이진 포맷의 가수를 사용하는 부동소수점 타입(float, double)은 0.1을 정확히 표현할 수 없어,
근사치로 처리한다.
따라서 정확한 계산이 필요하다면, 정수 연산으로 변경해서 아래와 같이 해야한다.
int apple = 1;
int totalPieces = apple * 10;
int number = 7;
int temp = totalPieces - number; // 10 - 7 정수형 타입!
double result = temp / 10.0 // 3 / 10.0 -> 0.3 정확한 결과 출력
System.out.println("사과 한 개에서 0.7 조각을 빼면?");
System.out.println(result + "조각이 남는다."); // 0.3
NaN, Infinity 연산
/ 또는 % 연산자 사용 시 주의!!
좌측 피연산자가 정수 타입인 경우, 나누는 수인 우측 피연산자는 0 사용 불가 (0으로 나눌 수 없다)
만약 0 을 사용하게 되면 컴파일은 정상적이지만, 실행 시 예외(ArithmeticException)가 발생한다.
5 / 0 // X
5 % 0 // X
따라서 예외 처리 위해 catch 블록 사용한다.
try {
//int z = x / y; //y가 0일 경우 예외 발생!! (0으로 나눌수 없음)
int z = x % y;
System.out.println("z : "+z);
} catch(ArithmeticException e) { //예외 처리
System.out.println("0으로 나누면 안됨!");
}
실수 타입인 0.0 또는 0.0f 로 나누면 예외(ArithmeticException) 이 발생하지 않고,
/ 연산의 결과는 Infinity (무한대)
% 연산의 결과는 NaN (Not a Number) 를 가진다.
5 / 0.0 // Infinity
5 % 0.0 // NaN
이 값과 산술 연산도 가능하고, 어떤 수와 연산해도 Infinity, NaN 을 출력하므로 주의해야 한다.
Infinity + 2; // Infinity
NaN + 2; //NaN
연산 결과가 Infinity 또는 NaN인지 확인하려면 Double.isInfinite( ), Double.isNaN( ) 메소드를 사용한다.
(true / false 리턴)
public class InfinityNaNCheckExam {
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)); //true
System.out.println(Double.isNaN(z)); //false
System.out.println(z + 2); // Infinity 문제되는 코드
}
}
if 문으로 Infinity 또는 NaN일 때 연산을 못하도록 한다.
if (Double.isInfinite(z) || Double.isNaN(z)) {
System.out.println("값 산출 불가");
} else {
System.out.println(z + 2); // 값 산출 가능
}
입력값의 NaN 검사
부동소수점(실수)를 사용자로 부터 입력받을 때는 반드시 NaN 검사를 한다.
NaN은 산술 연산이 가능하기 때문에 주의해야 한다.
public class InputDataCheckExam {
public static void main(String[] args) {
String userInput = "NaN"; //사용자로부터 입력받은 값
double val = Double.valueOf(userInput); // 입력값을 double 타입으로 변환, NaN이 입력
double currentBalance = 10000.0;
currentBalance += val; //currentBalance에 NaN이 저장되어 버린다.
System.out.println(currentBalance); //NaN
}
}
NaN 체크 부분을 추가한다 Double.isNaN(val)
if (Double.isNaN(val)) {
System.out.println("NaN 이 입력되어 처리할 수 없음.");
val = 0.0; // 0.0으로 초기화 시킴
}
* 참고
NaN 인지 검사할 때 == 연산자를 사용하면 안된다!!
NaN 은 != 연산자를 제외한 모든 비교 연산자를 사용할 경우 false 값을 반환하기 때문이다.
따라서 Double.isNaN( ) 메소드를 이용하는 것이 좋다. (입력 값이 NaN이면 true 리턴)
2. 문자열 연결 연산자 ( + )
피연산자 중 한쪽이 문자열이면 + 연산자는 문자열 연결 연산자로 사용되어
다른 피 연산자를 문자열로 변환하고 서로 결합한다.
String str1 = "JDK" + 14.0; // "JDK14.0"
String str2 = str1 + " 특징"; // "JDK14.0 특징"
문자열과 숫자가 혼합된 + 연산식은 왼쪽에서부터 오른쪽으로 진행된다.
"JDK" + 3 + 3.0; // "JDK33.0"
하지만, 문자열 이전에 먼저 숫자가 나오면, 산술 연산 + 가 되어버린다.
3 + 3.0 + "JDK"; // "6.0JDK" (3 + 3.0) 이 실수로 반환되어 6.0 이 된다.
public class StringConcatExam {
public static void main(String[] args) {
String str1 = "JDK" + 14.0;
String str2 = str1 + " 특징";
System.out.println(str2); //"JDK14.0 특징"
String str3 = "JDK" + 3 + 3.0;
String str4 = 3 + 3.0 + "JDK";
System.out.println(str3); // "JDK33.0"
System.out.println(str4); // "6.0JDK"
}
}
3. 비교 연산자 ( <, <=, >, >=, ==, != )
대소 비교 연산자는 boolean 타입을 제외한 기본 타입에 사용할 수 있고
동등 비교 연산자는 모든 타입에서 사용할 수 있다.
산출 값으로 boolean 타입을 반환한다 (true / false)
구분 |
연산식 |
설명 |
동등 비교 |
피연산자1 == 피연산자2 |
두 피연산자 값이 같은지 비교 |
피연산자1 != 피연산자2 |
두 피연산자 값이 다른지 비교 |
|
크기 비교 |
피연산자1 > 피연산자2 |
피연산자1 이 큰지 비교 |
피연산자1 >= 피연산자2 |
피연산자1이 크거나 같은지 비교 |
|
피연산자1 < 피연산자2 |
피연산자1이 작은지 비교 |
|
피연산자1 <= 피연산자2 |
피연산자1이 작거나 같은지 비교 |
피연산자가 char 타입이면, 유니코드 값으로 비교 연산을 수행한다.
'A' < 'B' // (65 < 66) true!
비교 연산 수행 전, 타입 변환을 통해 피연산자들의 타입을 일치시킨다. (자동 타입 변환 (큰 타입으로))
'A' == 65 // 'A' 는 int 로 변환되면 65 이므로 true 반환
3 == 3.0 // 3 은 int 이므로 double로 변환되면 3.0 이므로 true 반환
* 예외)
0.1 == 0.1f // false 반환 ( 0.1 (double) 0.1f (float) )
이진 포맷의 가수를 사용하는 모든 부동소수점 타입은 0.1을 정확히 표현할 수가 없다.
0.1f는 0.1의 근사값으로 표현되어 0.1000000014901... 와 같은 값이 되어 0.1 보다 큰 값이 된다.
따라서 피연산자 모두 float 타입으로 강제 변환 후 비교하거나 정수로 변환해서 비교해야 한다.
public class CompareOperatorExam {
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 float 변환
System.out.println((int)(v4 * 10) == (int)(v5 * 10)); //true int 변환
}
}
String 타입 문자열 비교 시 대소 비교 연산자 ( <, <=, >, >=)는 사용 할 수 없고
동등 비교 연산자 ( ==, != )는 사용할 수 있으나,
문자열이 같은지, 다른지 비교하는 용도로 사용되지 않는다. String이 참조 타입이기 때문!!
비교 연산자는 저장된 메모리 번지값을 비교하게 된다. (변수에 저장된 값이 번지값이기 때문)
String strVal1 = "케피";
String strVal2 = "케피";
String strVal3 = new String("케피");
문자열 리터럴이 동일하다면 자바는 동일한 String 객체를 참조하도록 되어있다.
strVal1 과 strVal2 는 동일한 String 객체의 번지값을 가지고 있다.
하지만, strVal3는 새로운 객체 생성자인 new로 생성한 새로운 String 객체의 번지 값을 가진다.
strVal1 == strVal2 //true!
strVal2 == strVal3 //false..
String 객체의 문자열만 비교하고 싶다면 equals( ) 메소드를 사용해야 한다.
boolean result = str1.equals(str2); // str1 : 원본 문자열, str2 : 비교할 문자열
strVal1.equals(strVal2); //true!
strVal2.equals(strVal3); //true!
4. 논리 연산자 ( &&, ||, &, |, ^, ! )
논리 연산자의 피연산자는 boolean 타입만 사용할 수 있다 (boolean 값이 반환되는 연산식만 가능)
구분 |
연산자 |
설명 |
AND (논리곱) |
피연산자1 && 또는 & 피연산자2 |
피연산자 모두가 true 일때 true 반환 |
OR(논리합) |
피연산자1 || 또는 | 피연산자2 |
피연산자 중 하나만 true 이면 true 반환 |
XOR(베타적논리합) |
피연산자1 ^ 피연산자2 |
피연산자 하나는 true, 하나는 false 일 때만 true 반환 |
NOT(부정) |
! 피연산자 |
피연산자의 논리값을 바꾼다. |
&& 연산자 : 앞의 피연산자1이 false 이면, 뒤의 피연산자2를 검사하지 않고 바로 false 반환한다. (효율적)
& 연산자 : 두 피연산자를 모두 검사해서 결과값을 반환한다.
|| 연산자 : 앞의 피연산자1이 true 이면, 뒤의 피연산자2를 검사하지 않고 바로 true 반환한다. (효율적)
| 연산자 : 두 피연산자를 모두 검사해서 결과값을 반환한다.
5. 비트 연산자 ( &, |, ^, ~, <<, >>, >>> )
데이터를 비트 단위로 연산한다. 따라서 0과 1로 표현이 가능한 정수 타입만 비트연산이 가능하다.
- 비트 논리 연산자 ( &, |, ^, ~ )
(true, false)
&, |, ^, ~ 연산자는
피연산자가 boolean 이면, 일반 논리 연산자가 되고, 피연산자가 정수 타입일 경우 비트 논리 연산자가 된다.
구분 |
연산식 |
설명 |
AND |
비트 & 비트 |
두 비트 모두 1일 때, 결과값 1 |
OR |
비트 | 비트 |
두 비트 중 하나면 1이면, 결과값 1 |
XOR |
비트 ^ 비트 |
두 비트중 하나는 1이고 다른 하나가 0 일 경우, 결과값 1 |
NOT |
~ 비트 |
보수 (비트 반전) |
ex)
45 : 0010 1101
25 : 0001 1001
각 자리수의 비트를 하나씩 비교한다.
45 & 25 : 0000 1001 = 9
45 | 25 : 0011 1101 = 61
45 ^ 25 : 0011 0100 = 52
~45 : 1101 0010 = -46 (맨 앞 비트는 부호 비트!)
비트 연산자는 피연산자를 int 타입으로 자동 타입 변환 후 연산을 수행한다!
byte, short, char 타입을 비트 논리 연산하게 되면, int 타입으로 변환된다. (32비트(4byte)로 연산 진행)
byte num1 = 45;
byte num2 = 25;
byte result = num1 & num2; // X 컴파일 에러!! int result = num1 & num2; int 형으로 바꾸어야 한다.
- 비트 이동 연산자( <<, >>, >>> )
비트 이동(shift) 연산자는 정수 데이터의 비트를 좌측 또는 우측으로 밀어서 이동시키는 연산을 수행한다.
구분 |
연산식 |
설명 |
비트 이동 |
a << b |
정수 a의 각 비트를 b 만큼 왼쪽으로 이동 (빈자리는 0으로 채워진다.) |
a >> b |
정수 a의 각 비트를 b 만큼 오른쪽으로 이동 |
|
a >>> b |
정수 a의 각 비트를 b 만큼 오른쪽으로 이동 (빈자리는 0으로 채워진다.) |
ex)
int result = 1 << 3; // 3칸 이동
00000000 .... 00000001
<< 3
000 00000000 .... 00001000 = 8
밀려난 비트는 버려지고, 새로운 빈자리는 0으로 채워진다.
int result = -8 >> 3;
11111111 .... 11111000
>> 3
11111111 .... 11111111 000 = -1
새로 채워지는 빈자리는 최상위 부호 비트 (1) 로 채워지고 밀려난 비트는 버려진다.
int result = -8 >>> 3;
11111111 .... 11111000
>>> 3
00011111 ... 11111111 000 = 536870911
새로 채워지는 빈자리는 0으로 채워지고 밀려난 비트는 버려진다.
6. 대입 연산자 ( =, +=, -=, *=, /=. %=, &=, ^=, |=, <<=, >>=, >>>= )
오른쪽 피연산자의 값을 좌측 피연산자인 변수에 저장한다.
오른쪽 피연산자 : 리터럴, 변수, 연산식
구분 |
연산식 |
설명 |
단순 대입 연산자 |
변수 = 피연산자 |
피연산자값을 변수에 저장 |
복합 대입 연산자 |
변수 += 피연산자 |
피연산자 값을 변수의 값과 더한 후 다시 변수에 저장 (변수 = 변수 + 피연산자) (누적) |
변수 -= 피연산자 |
(변수 = 변수 - 피연산자) |
|
변수 *= 피연산자 |
(변수 = 변수 * 피연산자) |
|
변수 /= 피연산자 |
(변수 = 변수 / 피연산자) |
|
변수 %= 피연산자 |
(변수 = 변수 % 피연산자) |
|
변수 |= 피연산자 |
우측 피연산자값과 변수의 값과 | 연산 후 다시 변수에 저장 (변수 = 변수 | 피연산자) |
|
변수 ^= 피연산자 |
(변수 = 변수 ^ 피연산자) |
|
변수 <<= 피연산자 |
(변수 = 변수 << 피연산자) |
|
변수 >>= 피연산자 |
(변수 = 변수 >> 피연산자) |
|
변수 >>>= 피연산자 |
(변수 = 변수 >>> 피연산자) |
대입 연산자는 모든 연산자들 중 가장 낮은 우선순위를 갖는다. (제일 마지막에 수행)
또한 연산 진행 방향은 오른쪽에서 왼쪽이므로 a = b = c = 5; 는
c = 5, b = c, a = b 순으로 진행된다.
삼항 연산자 ( ? : )
? 앞의 조건식(피연산자1)에 따라 : 앞 뒤의 피연산자2, 3이 선택된다.
조건식이 true 이면 연산자 결과는 피연산자2 가 되고
조건식이 false 이면 연산자 결과는 피연산자3 이 된다.
조건식 |
? |
값 또는 연산식 |
: |
값 또는 연산식 |
(피연산자1) |
(피연산자2) |
(피연산자3) |
* 삼항 연산자는 if 문 보다 더욱 효과적이다.
int score = 95; char grade = (score > 90) ? 'A' : 'B'; |
= |
int score = 95; char grade; if (score > 90) { grade = 'A'; } else { grade = 'B'; } |
'Java 기본 문법 - 참조 서적 [이것이 자바다 - 한빛미디어] > 1. 변수, 기본 데이터 타입, 연산자' 카테고리의 다른 글
5. Java 자바 - 단항 연산자 (0) | 2020.04.24 |
---|---|
4. Java 자바 - 연산자 종류, 연산자 우선순위 (0) | 2020.04.22 |
3. Java 자바 - 자동 타입 변환, 강제 타입 변환 (2) | 2020.04.21 |
2. Java 자바 - 기본 데이터 타입 (0) | 2020.04.20 |
1. Java 자바 - 변수와 리터럴 (1) | 2020.04.20 |