부호 연산자
부호 연산자는 변수의 부호를 유지하거나 변경함
연산식 | 설명 |
+피연산자 | 피연산자의 부호 유지 |
-피연산자 | 피연산자의 부호 변경 |
+연산자는 잘 사용되지 않고, -연산자는 변수값의 부호를 변경할 때 사용
주의할 점은 부호 변경 후의 타입
정수 타입(byte, short, int) 연산의 결과는 int타입
부호를 변경하는 것도 연산이므로 아래와 같이 int타입 변수에 대입해야 함
byte a = 100;
//byte result = -a; //컴파일 에러!
byte b = 100;
int result = -b;
package ch03.sec01;
public class SIgnOperationExample {
public static void main(String[] args) {
int x = -100;
x = -x;
System.out.println(x); //100
byte b = 100;
int y = -b;
System.out.println(y); //-100
}
}
증감 연산자
증감 연산자(++, - -)는 변수의 값을 1 증가시키거나 1 감소시키는 연산자
연산식 | 설명 |
++피연산자 | 피연산자의 값을 1 증가시킴 |
--피연산자 | 피연산자의 값을 1 감소시킴 |
피연산자++ | 다름 연산을 수행한 후에 피연산자의 값을 1 증가시킨 |
피연산자-- | 다른 연산을 수행한 후에 피연산자의 값을 1 감소시킨 |
변수 단독으로 증감 연산자가 사용될 경우 변수의 앞뒤 어디에 붙어도 결과는 동일하지만, 여러 개의 연산자가 포함되어 있는 연산식에서는 증감 연산자의 위치에 따라 결과가 달라짐
int x = 1;
int y = 1;
int result1 = ++x + 10; //12, x를 1 증가하고 10을 더함
int result2 = y++ + 10; //11, y와 10을 더한 후 y를 1 증가함
package ch03.sec01;
public class IncreaseDecreaseOperaiontExample {
public static void main(String[] args) {
int x = 10;
int y = 10;
int z;
x++;
++x;
System.out.println(x); //12
System.out.println("=====");
y--;
--y;
System.out.println(y); //8
System.out.println("=====");
z = x++;
System.out.println(z); //12
System.out.println(x); //13
System.out.println("=====");
z = ++x;
System.out.println(z); //14
System.out.println(x); //14
System.out.println("=====");
z = ++x + y++;
System.out.println(z); //23
System.out.println(x); //15
System.out.println(y); //9
}
}
산술 연산자
산술 연산자는 더하기(+), 빼기(-), 곱하기(*), 나누기(/), 나머지(%)로 총 5개
연산식 | 설명 |
피연산자 + 피연산자 | 덧셈 연산 |
피연산자 - 피연산자 | 뺄셈 연산 |
피연산자 * 피연산자 | 곱셈 연산 |
피연산자 / 피연산자 | 나눗셈 연산 |
피연산자 % 피연산자 | 나눗셈의 나머지를 산출하는 연산 |
산술 연산의 특징
피연산자가 정수 타입(byte, short, char, int)이면 연산의 결과는 int 타입
피연산자가 정수 타입이고 그중 하나가 long 타입이면 연산의 결과는 long 타입
피연산자 중 하나가 실수 타입이면 연산의 결과는 실수 타입
package ch03.sec02;
public class ArithmeticOperatorExample {
public static void main(String[] args) {
byte v1 = 10;
byte v2 = 4;
int v3 = 5;
long v4 = 10L;
int result1 = v1 + v2; //모든 피연산자는 int 타입으로 자동 변환 후 연산
System.out.println(result1); //14
long result2 = v1 + v2 - v4; //모든 피연산자는 long 타입으로 자동 변환 후 연산
System.out.println(result2); //4
double result3 = (double) v1 / v2; //double 타입으로 강제 변환 후 연산
System.out.println(result3); //2.5
int result4 = v1 % v2;
System.out.println(result4); //2
}
}
오버플로우와 언더플로우
오버플로우(overflow)란 타입이 허용하는 최댓값을 벗어나는 것
언더플로우(underflow)는 타입이 허용하는 최대값을 벗어나는 것
정수 타입 연산에서 오버플로우 또는 언더플로우가 발생하면 해당 정수 타입의 최솟값 또는 최댓값으로 되돌아감
byte value1 = 127; //byte의 최대값 127
value++;
System.out.println(value1); //-128, byte의 최대값에 1을 더하니 오버플로우가 발생하여 최소값인 -128이 됨
byte value2 = -128; //byte의 최소값 -128
value--;
System.out.println(value2); //127, byte의 최소값에 1을 빼니 언더플로우가 발생하여 최대값인 127이됨
package ch03.sec03;
public class OverflowUnderflowExample {
public static void main(String[] args) {
byte var1 = 125;
for(int i=0; i<5; i++) { //{}를 5번 반복 실행
var1++; //var1의 값 1 증가
System.out.println(var1); //126, 127, -128, -127, -126
}
System.out.println("=====");
byte var2 = -125;
for(int i=0; i<5; i++) {
var2--; //var2의 값 1 감소
System.out.println(var2); //-126, -127, -128, 127, 126
}
}
}
연산과정 중 오버플로우 혹은 언더플로우가 일어나지 않도록 항상 해당 타입의 범위 내에서 역산이 수행되게 코딩해야 함
아래의 코드는 int의 허용 범위를 초과한 오버플로우가 발생하기에 올바른 값을 얻으려면 long 타입으로 바꿔줘야 함
int x = 1000000;
int y = 1000000;
int z = x * y; //-727379968
long x = 1000000;
long y = 1000000;
long z = x * y; //1000000000000
정확한 계산은 정수 연산으로
산술 연산을 정확하게 계산하고 싶다면 실수 타입을 사용하지 않는 것이 좋음
package ch03.sec04;
public class AccuracyExample1 {
public static void main(String[] args) {
int apple = 1;
double pieceUnit = 0.1;
int number = 7;
double result = apple - number*pieceUnit;
System.out.println("사과 1개에서 남은 양: " + result); //0.29999999999999993
}
}
위의 코드를 보면 result 변수 값이 정확히 0.3이 되지 않음
정확한 계산이 필요하다면 정수 연산으로 변경해 아래와 같이 계산하는 것이 좋음
package ch03.sec04;
public class AccuracyExample2 {
public static void main(String[] args) {
int apple = 1;
int totalPieces = apple * 10;
int number = 7;
int result = totalPieces - number;
System.out.println("10조각에서 남은 조각: " + result); //3
System.out.println("사과 1개에서 남은 양: " + result/10.0); //0.3
}
}
나눗셈 연산 후 NaN과 Infinity 처리
나눗셈(/) 또는 나머지(%) 연산에서 좌측 피연산자가 정수이고 우측 피연산자가 0일 경우, 무한대의 값을 정수로 표현할 수 없기에 예외(ArithmeticException)가 발생
int x = 5;
int y = 0;
int result = 5 / 0; //예외 발생!
하지만 좌측 피연산자가 실수이거나 우측 피연산자가 0.0 또는 0.0f면 예외가 발생하지 않고 연산의 결과는 Infinity(무한대) 또는 NaN(Not a Number)이 됨
5 / 0.0 //Infinity
5 % 0.0 //NaN
Infinity 또는 NaN상태에서 연산을 계속 하더라도 결과는 계속 Infinity와 NaN이 되므로 데이터가 엉망이 될 수 있음
Infinity + 2 //Infinity
NaN + 2 // NaN
그렇기에 /와 % 연산의 결과가 Infinity 또는 NaN인지 먼저 확인하고 다음 연산을 수행해야 함
이를 확인하기 위해서 Double.isInfinite()와 Double.isNaN()를 사용
변수 값이 Infinity 또는 NaN일 경우 true를, 그렇지 않다면 false를 산출
boolean result = Double.isInfinite(변수);
boolean result = Double.isNaN(변수);
package ch03.sec05;
public class InfinityAndNaNCheckExample {
public static void main(String[] args) {
int x = 5;
double y = 0.0;
double z1 = x / y; //Infinity
double z2 = x % y; //NaN
System.out.println(z1 + 2);
System.out.println(z2 + 2);
if(Double.isInfinite(z1) || Double.isNaN(z1)) { //둘 중 하나가 true면 true
System.out.println("값 산출 불가");
} else {
System.out.println(z1 + 2);
}
}
}
비교 연산자
비교 연산자는 동등(==, !=) 또는 크기(<, <=, >, >=)를 평가해서 boolean 타입인 true/flase를 산출함
흐름 제어문인 조건문(if), 반복문(for, while)에서 실행 흐름을 제어할 때 주로 사용
구분 | 연산식 | 설명 |
동등 비교 | 피연산자1 == 피연산자2 | 두 연산자의 값이 같은지를 검사 |
피연산자1 != 피연산자2 | 두 연산자의 값이 다른지를 검사 | |
크기 비교 | 피연산자1 > 피연산자2 | 피연산자1이 큰지를 검사 |
피연산자1 >= 피연산자2 | 피연산자 1이 크거나 같은지를 검사 | |
피연산자1 < 피연산자2 | 피연산자1이 작은지를 검사 | |
피연산자1 <= 피연산자2 | 피연산자1이 작거나 같은지를 검사 |
피연산자의 타입이 다를 경우, 비교 연산을 수행하기 전에 타입을 일치시킴
'A' == 65 //true, 'A'가 int 타입인 65로 변환된 후 65와 비교
3 == 3.0 //true 3이 double 타입인 3.0으로 변환된 후 3.0과 비교
예외로 float 타입과 double 타입 비교는 false
부동 소수점 방식을 사용하는 실수 타입은 0.1을 정확히 표현할 수 없을 뿐만 아니라, float 타입과 double 타입의 정밀도 차이가 있어 false가 나옴
true가 나오려면 아래와 같이 피연산자를 float 타입으로 강제 타입 변환한 후 비교 연산을 해야 함
0.1f == 0.1 //false
0.1f == (float) 0.1 //true
문자열을 비교할 때는 동등 연산자(==, !=) 대신 equals()와 !equals()를 사용
boolean result = str1.equals(Set2); //str1과 str2 문자열이 같은지 검사
boolean result = ! str1.equals(Set2); //str1과 str2 문자열이 다른지 검사
package ch03.sec06;
public class CompareOperatorExample {
public static void main(String[] args) {
int num1 = 10;
int num2 = 10;
boolean result1 = (num1 == num2);
boolean result2 = (num1 != num2);
boolean result3 = (num1 <= num2);
System.out.println(result1); //true
System.out.println(result2); //false
System.out.println(result3); //true
char char1 = 'A';
char char2 = 'B';
boolean result4 = (char1 < char2);
System.out.println(result4); //true
int num3 = 1;
double num4 = 1.0;
boolean result5 = (num3 == num4);
System.out.println(result5); //true
float num5 = 0.1f;
double num6 = 0.1;
boolean result6 = (num5 == num6);
boolean result7 = (num5 == (float)num6);
System.out.println(result6); //false
System.out.println(result7); //true
String str1 = "자바";
String str2 = "Java";
boolean result8 = (str1.equals(str2));
boolean result9 = (! str1.equals(str2));
System.out.println(result8); //false
System.out.println(result9); //true
}
}
논리 연산자
논리 연산자는 논리곱(&&), 논리합(||), 배타적 논리합(^), 논리 부정(!) 연산을 수행
논리 연산은 흐름 제어문인 조건문(if), 반복문(for, while) 등에서 주로 이용됨
구분 | 연산식 |
결과 | 설명 | ||
AND (논리곱) |
true | && 또는 & | true | true | 피연산자 모두가 true일 경우에만 연산 결과가 true |
true | false | false | |||
false | true | false | |||
false | false | false | |||
OR (논리합) |
true | || 또는 | | true | true | 피연산자 중 하나만 true이면 연산 결과는 true |
true | false | true | |||
false | true | true | |||
false | false | false | |||
XOR (배타적 논리합) |
true | ^ | true | false | 피연산자가 하나는 true이고 다른 하나는 false일 경우에만 연산 결과가 true |
true | false | true | |||
false | true | true | |||
false | false | false | |||
NOT (논리 부정) |
! | true | false | 피연산자의 논리값을 바꿈 | |
false | true |
&&과 &은 산출 결과는 같지만 연산 과정이 다름
&&은 앞의 피연산자가 false라면 뒤의 피연산자를 평가하지 않고 바로 false를 산출
하지만 &은 두 피연산자 모두를 평가해서 산출 결과를 냄
따라서 &보다 &&이 더 효율적
||와 |도 마찬가지로 ||는 앞의 피연산자가 true라면 뒤의 피연산자를 평가하지 않고 바로 true를 산출
|는 두 피연산자 모두를 평가해서 산출 결과를 냄
package ch03.sec07;
public class LogicalOperatorExample {
public static void main(String[] args) {
// int charCode = 'A'; //대문자입니다.
// int charCode = 'a'; //소문자입니다.
int charCode = '5'; //0~9 숫자입니다.
if( (65<=charCode) & (charCode<=90) ) {
System.out.println("대문자입니다.");
}
if( (97<=charCode) && (charCode<=122) ) {
System.out.println("소문자입니다.");
}
if( (45<=charCode) && (charCode<=57) ) {
System.out.println("0~9 숫자입니다.");
}
// int value = 6; //2 또는 3의 배수입니다.
int value = 7; //2 또는 3의 배수가 아닙니다.
if( (value%2==0) | (value%3==0) ) {
System.out.println("2 또는 3의 배수입니다.");
}
boolean result = (value%2==0) || (value%3==0);
if( !result ) {
System.out.println("2 또는 3의 배수가 아닙니다.");
}
}
}
비트 논리 연산자
비트 논리 연산자는 bit 단위로 논리 연산을 수행
0과 1이 피연산자가 되므로 2진수 0과 1로 저장되는 정수 타입(byte, short, int, long)만 피연산자가 될 수 있고, 부동 소수점 방식으로 저장되는 실수 타입(float, double)은 피연산자가 될 수 없음
구분 | 연산식 |
결과 | 설명 | ||
AND (논리곱) |
1 | & | 1 | 1 | 두 비트 모두 1일 경우에만 연산 결과가 1 |
1 | 0 | 0 | |||
0 | 1 | 0 | |||
0 | 0 | 0 | |||
OR (논리합) |
1 | | | 1 | 1 | 두 비트 중 하나만 1이면 연산결과는 1 |
1 | 0 | 1 | |||
0 | 1 | 1 | |||
0 | 0 | 0 | |||
XOR (배타적 논리합) |
1 | ^ | 1 | 0 | 두 비트 중 하나는 1이고 다른 하나가 0일 경우 연산 결과는 1 |
1 | 0 | 1 | |||
0 | 1 | 1 | |||
0 | 0 | 0 | |||
NOT (논리 부정) |
~ | 1 | 0 | 보수 | |
0 | 1 |
45의 2진수 = 0010 1101
25의 2진수 = 0001 1001
45와 25의 비트 논리곱(&) 9 = 0000 1001
45와 25의 비트 논리합(|) 61 = 0011 1101
45와 25의 비트 배타적 논리합(^) 52 = 0011 0100
45의 비트 논리 부정(~) -46 = 1101 0010 //최상위 비트가 1이면 음수
비트 논리 연산자는 byte, short, char 타입 피연산자를 int 타입으로 자동 변환한 후 연산을 수핼
따라서 연산 결과도 int 타입이 되므로 int 변수에 대입해야 함
byte num1 = 45;
byte num2 = 25;
//byte result = num1 & num2; //컴파일 에러!
int result = num1 & num2;
비트 논리 연산이 필요한 이유
예를 들어 C 프로그램에서 자바 프로그램으로 데이터를 전달한다고 가정하고
C언어는 uint8_t타입(허용범위 0~255)이고 Java는 byte타입(허용범위 -128~127) 일 때
C 프로그램이 uint8_t 타입 136(1000 1000)을 2진수로 보내면, 자바는 2진수 -120(1000 0000)으로 읽게 됨
자바는 최상위 비트가 1이면 음수로 인식하기 때문
-120을 C 프로그램이 보낸 136으로 복원하고 싶다면 -120과 255를 비트 논리곱(&) 연산을 수행하면 됨
byte receiveData = -120;
int unsignedInt = receiveData & 255; //136
혹은 자바의 Byte.toUnsigedInt() 코드를 사용해야 함
byte receiveData = -120;
int unsignedInt = Byte.toInsignedInt(receiveData); //136
package ch03.sec08;
public class BitLogicalExample {
public static void main(String[] args) {
System.out.println((45 & 25)); //9
System.out.println((45 | 25)); //61
System.out.println((45 ^ 25)); //52
System.out.println((~45)); //-46
System.out.println("=====");
byte receiveData = -128;
//방법1: 비트 논리곱 연산으로 Unsigned 정수 얻기
int unsignedInt1 = receiveData & 255;
System.out.println(unsignedInt1); //128
//방법2: 자바 API를 이용해서 Unsigned 정수 얻기
int unsignedInt2 = Byte.toUnsignedInt(receiveData);
System.out.println(unsignedInt2); //128
int test = 136;
byte btest = (byte) test;
System.out.println(btest); //-120
}
}
비트 이동 연산자
비트 이동 연산자는 비트를 좌측 또는 우측으로 밀어서 이동시키는 연산을 수행
구분 | 연산식 | 설명 |
이동(shift) | a << b | 정수 a의 각 비트를 b만큼 왼쪽으로 이동. 오른쪽 빈자리는 0으로 채움, a * 2^b와 동일한 결과가 됨 |
a >> b | 정수 a의 각 비트를 b만큼 오른쪽으로 이동. 왼쪽 빈자리는 최상위 부호 비트와 같은 값으로 채움, a / 2^b와 동일한 결과가 됨 | |
a >>> b | 정수 a의 각 비트를 b만큼 오른쪽으로 이돔. 왼쪽 빈자리는 0으로 채움 |
int result = 1 << 3;
//0000 0001을 왼쪽으로 3칸 이동해서 0000 1000이 되고
//밀려난 왼쪽 3비트는 버려지고 빈 공간은 0으로 채움
1 << 3 = 1 * 2^3 = 8
int result = -8 >> 3;
//1111 ... 1111 1110을 오른쪽으로 3칸 이동해서
//1111 ... 1111 1111이 되고 오른쪽 3비트는 버려지고 빈 공간은 1로 채움
-8 >> 3 = -8 / 2^3 = -1
package ch03.sec09;
public class BitShiftExample1 {
public static void main(String[] args) {
int num1 = 1;
int result1 = num1 << 3;
int result2 = num1 * (int) Math.pow(2, 3);
//Math.pow(2, 3)은 2^3을 연산하고 double값을 산출
//int 값으로 얻고 싶다면 (int)로 캐스팅해야함
System.out.println(result1); //8
System.out.println(result2); //8
int num2 = -8;
int result3 = num2 >> 3;
int result4 = num2 / (int) Math.pow(2, 3);
System.out.println(result3); //-1
System.out.println(result4); //-1
}
}
int result = -8 >>> 3;
//1111 ... 1111 1000을 왼쪽으로 3칸 이동해 오른쪽 3비트는 버려지고
//빈 공간을 0으로 채워져 0001 ... 1111 1111이 됨
-8 >>> 3 = 536870911
package ch03.sec09;
public class BitShiftExample {
public static void main(String[] args) {
int value = 772; //00000000 00000000 00000011 00000100
//우측으로 24bit 이동하고 끝 1바이트만 읽음, 00000000
byte byte1 = (byte) (value >>> 24);
int int1 = byte1 & 255;
System.out.println(int1); //0
//우측으로 16bit 이동하고 끝 1바이트만 읽음, 00000000
byte byte2 = (byte) (value >>> 16);
int int2 = Byte.toUnsignedInt(byte2);
System.out.println(int2); //0
//우측으로 8bit 이동하고 끝 1바이트만 읽음, 00000011
byte byte3 = (byte) (value >>> 8);
int int3 = byte3 & 255;
System.out.println(int3); //3
//끝 1바이트만 읽음, 00000100
byte byte4 = (byte) value;
int int4 = Byte.toUnsignedInt(byte4);
System.out.println(int4); //4
}
}
대입 연산자
대입 연산자는 우측 피연산자의 값을 좌측 피연산자인 변수에 대입
우측 피연산자에는 리터럴 및 변수, 그리고 다른 연산식이 올 수 있음
대입 연산자에는 단순히 값을 대입하는 단순 대입 연산자와 정해진 연산을 수행한 후 결과를 대입하는 복합 대입 연산자가 있음
구분 | 연산식 | 설명 |
단순 대입 연산자 | 변수 = 피연산자 | 우측 피연산자의 값을 변수에 저장 |
복합 대입 연산자 | 변수 += 피연산자 | 우측의 피연산자의 값을 변수의 값과 더한 후에 다시 변수에 저장 (변수 = 변수 + 피연산자) |
변수 -= 피연산자 | 우측의 피연산자의 값을 변수의 값에서 뺀 후에 다시 변수에 저장 (변수 = 변수 - 피연산자) |
|
변수 *= 피연산자 | 우측 피연산자의 값을 변수의 값과 곱한 후에 다시 변수에 저장 (변수 = 변수 * 피연산자) |
|
변수 /= 피연산자 | 우측의 피연산자의 값으로 변수의 값을 나눈 후에 다시 변수에 저장 (변수 = 변수 / 피연산자) |
|
변수 %= 피연산자 | 우측의 피연산자의 값으로 변수의 값을 나눈 후에 나머지를 변수에 저장 (변수 = 변수 % 피연산자) |
|
변수 &= 피연산자 | 우측의 피연산자의 값과 변수의 값을 &연산 후 결과를 변수에 저장 (변수 = 변수 & 피연산자) |
|
변수 |= 피연산자 | 우측의 피연산자의 값과 변수의 값을 |연산 후 결과를 변수에 저장 (변수 = 변수 | 피연산자) |
|
변수 ^= 피연산자 | 우측의 피연산자의 값과 변수의 값을 ^ 연산 후 결과를 변수에 저장 (변수 = 변수 ^ 피연산자) |
|
변수 <<= 피연산자 | 우측의 피연산자의 값과 변수의 값을 << 연산 후 결과를 변수에 저장 (변수 = 변수 << 피연산자) |
|
변수 >>= 피연산자 | 우측의 피연산자의 값과 변수의 값을 >> 연산 후 결과를 변수에 저장 (변수 = 변수 >> 피연산자) |
|
번수 >>>= 피연산자 | 우측의 피연산자의 값과 변수의 값을 >>> 연산 후 결과를 변수에 저장 (변수 = 변수 >>> 피연산자) |
package ch03.sec10;
public class AssignmentOperatorExample {
public static void main(String[] args) {
int result = 0;
result += 10;
System.out.println(result); //10
result -= 5;
System.out.println(result); //5
result *= 3;
System.out.println(result); //15
result /= 5;
System.out.println(result); //3
result %= 3;
System.out.println(result); //0
}
}
삼항(조건) 연산자
삼항 연산자(피연산자 ? 피연산자 : 피연산자)는 총 3개의 피연산자를 가짐
? 앞의 피연산자를 boolean 변수 또는 조건식이 오기에 조건 연산자라고 함
이 값이 true면 콜론(:) 앞의 피연산자가 선택되고, false면 콜론 뒤의 피연산자가 선택됨
package ch03.sec11;
public class ConditionalOperationExample {
public static void main(String[] args) {
int score = 85;
char grade = (score > 90) ? 'A' : ( (score > 80) ? 'B' : 'C');
System.out.println(score + "점은 " + grade + "등급입니다.");
//85점은 B등급입니다.
}
}
연산의 방향과 우선순위
연산자 | 연산 방향 | 우선순위 |
증감(++, - -), 부호(+, -), 비트(~), 논리(!) | ← ← | 높음 ↑ ↑ ↓ ↓ 낮음 |
산술(*, /, %) | → | |
산술(+, -) | → | |
쉬프트(<<, >>, >>>) | → | |
비교(<, >, <=, >=, instanceof) | → | |
비교(==, !=) | → | |
논리(&) | → | |
논리(^) | → | |
논리(|) | → | |
논리(&&) | → | |
논리(||) | → | |
조건(?:) | → | |
대입(=, +=, -=, *= /=, %=, &=, ^=, |=, <<=, >>=, >>>=) | ← ← |
int var1 = 1;
int var2 = 3;
int var3 = 2;
int result = var1 + var2 * var3; //var2 * var3을 연산한 후 var1을 더함
int result = (var1 + var2) * var3; //var1과 var2를 더한 후 var3을 곱험
'공부기록 > 자바' 카테고리의 다른 글
Chapter 06. 클래스 (0) | 2022.12.18 |
---|---|
Chapter 05. 참조 타입 (0) | 2022.11.23 |
Chapter 04. 조건문과 반복문 (0) | 2022.11.22 |
Chapter 02. 변수와 타입 (0) | 2022.11.21 |
Chapter 01. 자바 시작하기 (0) | 2022.11.19 |
댓글