본문 바로가기
공부기록/자바

Chapter 03. 연산자

by 루팽 2022. 11. 21.

부호 연산자

부호 연산자는 변수의 부호를 유지하거나 변경함

연산식  설명
+피연산자 피연산자의 부호 유지
-피연산자 피연산자의 부호 변경

+연산자는 잘 사용되지 않고, -연산자는 변수값의 부호를 변경할 때 사용

주의할 점은 부호 변경 후의 타입

정수 타입(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

댓글