Java - 연산자

박종호·2024년 1월 29일

Java

목록 보기
2/8
post-thumbnail

⚙️ 연산자와 연산식

연산자

연산자는 앞장에서 배운 기본 자료형을 더하거나 빼는 등의 계산을 하기 위해서 사용합니다. 자바 언어에서는 다양한 연산자를 제공해주는데 타입별 사용 가능한 연산자가 있습니다.

구분연산자비고
결과가 boolean숫자 비교 연산자<, <=, >, >=
숫자 동등 연산자==, !=
결과가 int 혹은 long기본 사칙 연산자+, -, *, /, %
증감 연산자++, --
비트 연산자&,, ^, ~, <<, >>, >>>
기타 연산자삼항 연산자? :
형 변환 연산자(타입)
문자열 더하기 연산자+

수많은 연산자 분류는 피연산자 수에 따라 단항, 이항, 삼항 연산자로 구분합니다.

단항 연산자 : ++x;
이항 연산자 : x + y;
삼항 연산자 : (sum > 90) ? "A" : "B"

연산식

연산식은 하나의 값을 산출합니다. 연산자 수가 아무리 많아도 두 개 이상의 값을 산출하는 연산식은 없습니다.

int result = x + y;

그리고 연산식은 다른 피연산자 위치에도 올 수 있습니다. 아래 코드를 볼까요?

boolean result = (x + y) < 5;

비교 연산자인 < 의 좌측 피연산자로 (x + y)라는 연산식이 사용되었고, x와 y 변수의 값을 더하고나서 5보다 작은지 검사 후 결과값을 result 변수에 true 나 false (boolean 타입) 형태로 저장합니다.'

🎆 연산의 방향

하나의 연산식에는 다양한 연산자가 복합적으로 구성된 경우가 많습니다. 이 경우에는 우선순위를 알아야 연산을 할 수 있는데요.

보통 산술 연산식에서 덧셈, 뺄셈 연산자 보다는 곱셈, 나눗셈 연산자가 우선 처리된다는 것을 알고있죠. 자바의 연산식에서도 마찬가지입니다.

아래의 연산식에서는 어떤 연산이 먼저 처리될까요? && 일까요, < 일까요, > 일까요?

x > 0 && y < 0

&& 보다는 <, >가 우선순위가 높기 때문에 x > 0, y < 0이 먼저 처리되고 각각에 산출된 값으로 && 연산을 합니다.

우선순위가 같은 연산자는 어떻게 될까요? 수학과 비슷합니다. 왼쪽에서부터 오른쪽 방향으로 연산을 합니다. 아래 연산식의 경우죠.

100 * 2 / 3 % 5

하지만 단항 연산자(++, - -, ~, !), 부호 연산자(+, -), 대입 연산자(=, +=, -=, …)는 오른쪽에서 왼쪽으로 연산됩니다. 예를 들어 다음과 같은 연산식을 보시죠.

a = b = c = 5;

순서가 어떻게 될까요?

c = 5, b = c, a = b 순서로 실행이 됩니다. (←) 실행되고 난 후에는 a, b, c값이 모두 5가 되지요.

이와 같이 어떤 연산자를 사용하느냐에 따라 연산의 방향과 우선순위가 정해져있기 때문에 복잡한 연산식에서는 주의가 필요합니다.

연산의 우선순위

1. 단항, 이항, 삼항 연산자 순으로 우선순위를 가진다.
2. 산술, 비교, 논리, 대입 연산자 순으로 우선순위를 가진다. 
3. 단항과 대입 연산자를 제외한 모든 연산의 방향은 왼쪽에서 오른쪽(->)이다.
4. 복잡한 연산식에는 괄호()를 사용해서 우선순위를 정해준다. 

다음은 연산자의 연산 방향과 우선순위를 정리한 표 입니다.

연산자연산 방향우선 순위
증감(++, --), 부호(+, -), 비트(~), 논리(!)높음
산술(*, /, %)
산술(+, -)
쉬프트(<<, >>, >>>)
비교(<, >, <=, >=)
비교(==, !=)
논리(&)
논리(^)
논리()
논리(&&)
논리()
조건(?:)
대입(=, +=, -=, *=, /=, %=, &=, ^=, …)낮음

연산자에 너무 많은 종류가 있고 우선순위도 다 외우기 힘들기 때문에, 되도록이면 본인이 원하는 연산 순서대로 소괄호로 묶어서 명시적으로 개발하는 버릇을 들이는 것을 추천합니다. 그리고 한 라인에 너무 많은 계산을 해서 소괄호가 제대로 닫혔는지 안닫혔는지 알 수 없을 정도라면, 분리하는 것도 추천드립니다. 프로그래밍 에서의 가독성은 협업할 때 굉장히 중요하니까요.

Quiz

다음 코드를 실행 했을 때 처리되는 result 연산식의 순서는 어떻게 될까요?

int var1 = 1;
int var2 = 3;
int var3 = 2;
int result = var1 + var2 * var3;

✨ 단항 연산자

단항 연산자는 피연산자가 하나뿐인 연산자를 말합니다. 여기에는 부호 연산자(+, -), 증감 연산자(++, --), 논리 부정 연산자(!), 비트 반전 연산자(~)가 있습니다.

부호 연산자(+, -)

부호 연산자에는 양수나 음수를 표시하는 +, -가 있습니다.

int i1 = +100;
int i2 = -100;

double d1 = +3.14;
double d2 = -10.5;

부호 연산자는 변수값의 부호를 유지하거나 바꾸기 위해 사용하기도 합니다.

다음의 예시를 보고 풀어보겠습니다.

public static void main(String[] args) {
		int x = -100;
		int result1 = +x;
		int result2 = -x;

		System.out.println(result1);
		System.out.println(result2);
}

증감 연산자(++, --)

증감 연산자는 변수값을 1 증가(++) 시키거나 감소(--) 시키는 연산자를 말합니다. boolean 타입을 제외한 모든 기본 타입의 피연산자에 사용할 수 있습니다.

연산식설명
++피연산자다른 연산을 수행하기 전에 피연산자의 값을 1 증가시킴
--피연산자다른 연산을 수행하기 전에 피연산자의 값을 1 감소시킴
피연산자++값을 먼저 읽어온 후에 피연산자의 값을 1 증가시킴
피연산자--값을 먼저 읽어온 후에 피연산자의 값을 1 감소시킴

++ 연산자는 피연산자의 기존 값에 1을 더해서 그 결과를 다시 피연산자에 저장합니다.
예를들어 num 변수의 기존 값이 5라면, ++num 연산 후 num변수의 값은 6이 됩니다. 그래서 ++연산자를 증가 연산자라고 부릅니다.

package chapter03;

public class IncreaseDecreaseOperatorExample {
    public static void main(String[] args) {
        int x = 10;
        int y = 10;
        int z;

        System.out.println("----------------------");
        x++;
        ++x;
        System.out.println("x=" + x);

        System.out.println("----------------------");
        y--;
        --y;
        System.out.println("y=" + y);

        System.out.println("----------------------");
        z = x++;
        System.out.println("z=" + z);
        System.out.println("x=" + x);

        System.out.println("----------------------");
        z = ++x;
        System.out.println("z=" + z);
        System.out.println("x=" + x);

        System.out.println("----------------------");
        z = ++x + y++;
        System.out.println("z=" + z);
        System.out.println("x=" + x);
        System.out.println("y=" + y);
    }
}

위 코드는 어떻게 출력될까요?

생각해봅시다.
출력 결과는?

논리 부정 연산자(!)

논리 부정 연산자는 true를 false로, false를 true로 변경합니다. 그렇기 때문에 boolean 타입에만 사용할 수 있습니다.

논리 부정 연산자는 조건문, 제어문에서 사용되어 조건식의 값을 부정할 때 사용합니다. 그렇게 실행 흐름을 제어하죠.

그리고 두 가지 상태(true / false)를 번갈아가며 변경하는 토글(Toggle) 기능 구현시에도 주로 사용합니다.

public class DenyLogicOperatorExample {
		public static void main(String[] args) {
				boolean play = true;
				System.out.println(play);

				play = !play;
				System.out.println(play);

				play = !play;
				System.out.println(play);			
		}
}

위 코드는 어떻게 출력될까요?

🎊 이항 연산자

이항 연산자는 피연산자가 두 개인 연산자를 의미합니다.

산술 연산자 (+, -, *, /, %)

산술 연산자는 사칙연산과 유사합니다.

산술 연산자설명
+덧셈
-뺄셈
*곱셈
/나눗셈
%왼쪽의 피연산자를 오른쪽의 피연산자로 나눈 나머지

% 연산자가 생소하실텐데 나눗셈을 수행하고 몫이 아닌 나머지를 돌려주는 연산자입니다. 10 나누기 3은 몫이 3이고 나머지는 1이므로, 10 % 3 == 1 입니다.

만약 피연산자들의 타입이(byte, int, long, double, …) 동일하지 않다면 아래와 같은 규칙으로 연산이 됩니다.

🧐이항 연산자의 특징!

  1. 피연산자들이 모두 int보다 크기가 작을 경우 int로 변환 후 연산
    1. byte + byte → int + int
  2. 피연산자 중에 long 타입이 있을 경우 모두 long으로 변환 후 연산
    1. int + long → long + long
  3. 피연산자 중에 float 혹은 double 타입이 있을 경우 크기가 큰 실수 타입으로 변환 후 연산
    1. int + float → float + float

위의 규칙에 따라 다음 코드는 에러가 발생합니다.

byte a = 1;
byte b = 1;
byte c = a + b; // 에러. byte는 int로 변환 후 연산되기 때문

예제를 좀 더 살펴봅시다.

int a = 10;
int b = 4;
int c = a / b;  // 2
double d = a / b;   // 2.0

10 나누기 4는 2.5 이지만 c 변수에는 소수점 이하 부분을 버리고 2라는 결과가 저장됩니다. 그럼 d처럼 double 타입을 사용하면 2.5가 저장될까요? 아닙니다. int 타입의 연산이기 때문에 결과는 2가 되고 2를 실수화하여 2.0이 저장됩니다.

만약 2.5를 결과로 얻고 싶다면 어떻게 해야할까요? 피연산자 중 하나가 실수 타입이면 됩니다. 규칙에 따르면 피연산자 중 실수 타입이 있으면 모두 실수 타입으로 변환 후 연산합니다.

int a = 10;
int b = 4;
double c = (double)a / b;  // 2.5

이러한 결과값도 있겠다!

char 타입도 산술 연산이 가능합니다. 예를 들어 ‘A’ 는 65라는 유니코드를 가지기 때문에 ‘A’ + 1 은 66이 됩니다. 자바에서는 리터럴 간의 연산은 타입 변환 없이 계산하기 때문에 ‘A’ + 1 은 유니코드 66에 해당하는 ‘B’ 가 됩니다.

char c1 = 'A' + 1;   // 'B' (유니코드 66은 문자 B)
char c2 = 'A';
char c3 = c2 + 1;   // 에러

c3 변수의 경우 에러가 발생하는데 이유는 천천히 생각해보셨으면 합니다. 위의 규칙을 참고해주시면 됩니다.

오버플로우

int a = 1000000;
int b = 1000000;
int c = a * b;

System.out.println(c);  // -727379968

위의 코드에서 곱셈의 결과가 엉뚱한 값임을 볼 수 있습니다. 이는 곱셈의 결과가 int 타입에 저장할 수 있는 범위를 초과하였기 때문입니다. 이와 같은 현상을 오버플로우라고 하며 int 타입을 long으로 수정하면(크기가 큰 타입으로 바꾸면) 해결할 수 있습니다.

NaN, Infinity

NaN, Infinity는 실수 타입 연산 중에 발생할 수 있습니다.

double a = 10;
double b = 0;

System.out.println(a / b);  // Infinity
System.out.println(a % b);  // NaN

NaN은 Not a Number이고, Infinity는 무한대라는 의미입니다. NaN 또는 Infinity가 연산의 결과로 나오면 다음 연산을 수행해서는 안됩니다. 이 값에 어떤 연산을 해도 NaN 또는 Infinity가 되기 때문입니다.

double a = 10;
double b = 0;

System.out.println(a / b + 1);  // Infinity
System.out.println(a % b + 1);  // NaN

NaN, Infinity를 체크할 수 있는 방법은 아래와 같습니다.

double a = 10;
double b = 0;

System.out.println(Double.isInfinite(a / b));  // true
System.out.println(Double.isNaN(a % b));  // true

오 ㅋㅋ

문자열 연결 연산자 (+)

+ 연산자는 산술 연산자, 부호 연산자인 동시에 문자열 연결 연산자이기도 합니다. 피연산자 중 한쪽이 문자열이면 + 연산자는 문자열 연결 연산자로 사용되어 문자열을 결합합니다.

String str1 = "Hello!";
String str2 = str1 + "Nice to meet you";

System.out.println(str2);  // Hello!Nice to meet you

만약 + 연산자를 이용하여 문자열과 숫자를 연결하면 어떻게 될까요? 문자열과 숫자가 혼합된 경우 왼쪽에서부터 오른쪽으로 연산이 진행됩니다.

// 문자열 "Hello"와 123이 먼저 연산되어 "Hello123"이 되고,
// 이것을 다시 456과 연산하여 "Hello123456"이 됩니다.
System.out.println("Hello" + 123 + 456);  // Hello123456

// 숫자 123과 456이 먼저 연산되어 579가 되고,
// 이것을 문자열 "Hello"와 연산하여 "579Hello"가 됩니다.
System.out.println(123 + 456 + "Hello");  // 579Hello

비교 연산자 (<, <=, >, >=, ==, !=)

비교 연산자는 피연산자들을 비교하여 boolean 타입인 true, false 를 산출합니다.

크기 비교

연산식설명
A > BA가 B보다 큰 지 검사
A >= BA가 B보다 크거나 같은지 검사
A < BA가 B보다 작은 지 검사
A <= BA가 B보다 작거나 같은지 검사

동등 비교

연산식설명
A == BA와 B가 같은지 검사
A != BA와 B가 다른지 검사

만약 피연산자가 char 타입이면 유니코드 값으로 비교 연산을 수행합니다.

('A' < 'B') -> (65 < 66)

비교 연산 시 연산을 수행하기 전에 둘 중에 크기가 더 큰 타입으로 일치시킵니다. 예를 들어 아래와 같이 char 타입과 int 타입을 비교할 때는 int로 타입을 일치시킨 후 비교합니다.

'A' == 65  // true
10 == 10.0  // true (int타입인 10을 double 타입인 10.0으로 변환 후 비교)

아래의 예제는 double 타입과 float 타입을 비교합니다.

0.1 == 0.1f  // false

오른쪽의 0.1f가 double로 변환되어 0.1 == 0.1 의 결과로 true가 되어야할 것 같지만 그렇지 않습니다. 그 이유는 모든 부동소수점 타입은 0.1을 정확히 표기할 수 없어서 0.1f는 0.1의 근사 값이(예를 들어 0.100000000149와 같은 값) 되기 때문입니다.

피연산자를 모두 float 타입으로 변환하거나 int로 변환하면 이런 문제를 해결할 수 있습니다.

(float)0.1 == 0.1f  // true
(int)(0.1 * 10) == (int)(0.1f * 10)  // true

String 타입의 문자열은 대소 비교를 할 수 없고, 동등 비교는 할 수 있습니다. 다만 문자열이 같은지 다른지를 비교하는 용도로 사용할 때는 주의해야합니다. 아래의 예제로 확인해보겠습니다.

String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");

System.out.println(str1 == str2);  // true
System.out.println(str1 == str3);  // false

str1, str2, str3가 모두 동일한 문자열이지만 str1과 str2는 같고, str1과 str3는 다르다는 결과를 볼 수 있습니다. 자바는 문자열이 동일하다면 동일한 String 객체를 참조합니다. 그래서 str1과 str2는 동일한 String 객체의 주소 값을 가지고 있습니다. 반면 str3는 new 로 생성한 새로운 String 객체이므로 새로운 주소를 가지고 있습니다. 아래에 그림으로 표현하였습니다.

이와 같이 String 타입에 동등 연산을 할 경우 객체의 주소 값이 같은지 다른지를 확인합니다.

그렇다면 문자열 자체가 같은지 다른지 비교하려면 어떻게 해야할까요? equals() 메소드를 이용하면 됩니다.

String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");

System.out.println(str1.equals(str2));  // true
System.out.println(str1.equals(str3));  // true

논리 연산자 (&&, ||, &, |, ^, !)

논리 연산자는 조건식을 연결할 때 사용하는 연산자입니다.

논리 연산자의 피연산자는 boolean 타입만 사용할 수 있으며 종류와 기능은 아래와 같습니다.

AND (&&)

피연산자가 모두 true일 경우에만 결과가 true 입니다.

xyx && y
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse

OR (||)

피연산자 중 하나만 true이면 결과는 true 입니다.

xyx
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse

XOR (배타적 논리합)

피연산자가 하나는 true이고 다른 하나는 false일 경우에만 결과가 true 입니다.

연산식결과
true ^ truefalse
true ^ falsetrue
false ^ falsefalse

NOT (논리 부정)

피연산자의 논리 값을 바꿉니다.

연산식결과
!truefalse
!falsetrue

다음의 문제를 보면서 논리연산자를 익혀봅시다.

  1. x는 5보다 크 13보다 작다.
    • x > 5 그리고 x < 13으로 연결된 조건이므로 **x > 5 && x < 13** 으로 쓸 수 있습니다.
  2. i는 2의 배수 또는 3의 배수이다.
    • ‘또는’으로 두 조건이 연결되어있으므로 ‘||’(OR)을 사용하면 됩니다.

비트 연산자 (&, |, ^, ~, <<, >>, >>>)

데이터를 비트 단위로 연산할 때 사용합니다. 이진수로 표현이 가능한 int 타입만 비트 연산을 할 수 있습니다. (비트는 0과 1로 이루어져있습니다.)

비트 논리 연산자

비트 논리 연산자에는 &, |, ^, ~ 가 있습니다. &, |, ^ 연산자는 피연산자가 boolean이면 일반 논리 연산자이고, 피연산자가 정수이면 비트 논리 연산자로 동작합니다.

xyx & yxy
11110
10011
01011
00000

AND (논리곱)

두 비트가 모두 1일 경우에만 결과가 1입니다.

연산식결과
1 & 11
1 & 00
0 & 00

OR (논리합)

두 비트 중 하나만 1이면 결과가 1입니다.

연산식결과
11
10
00

XOR (배타적 논리합)

두 비트 중 하나는 1이고 다른 하나는 0일 경우 결과가 1입니다.

연산식결과
1 ^ 10
1 ^ 01
0 ^ 00

NOT (논리 부정)

0은 1로, 1은 0으로 바꿉니다.

연산식결과
~10
~01

예를 들어 15와 30을 비트 논리 연산해봅시다. 15를 이진수로 표현하면 01111 이고, 30을 이진수로 표현하면 11110 입니다.

0111111110 의 비트 논리 연산 결과는 아래와 같습니다.

논리곱 (&)

  • 01111 & 11110 == 01110

논리합 (|)

  • 01111 & 11110 == 11111

배타적 논리합 (^)

  • 01111 & 11110 == 10001

논리 부정 (~)

  • ~01111 == 10000

비트 이동 연산자(쉬프트 연산자)

비트 이동 연산자는 정수 데이터의 비트를 좌측 또는 우측으로 밀어서 이동시키는 연산을 수행합니다. 비트를 좌측으로 이동하면 자릿수가 늘어나고, 우측으로 이동하면 자릿수가 줄어드는 효과겠죠.

연산식결과
a << b정수 a의 각 비트를 b만큼 왼쪽으로 이동 (빈자리는 0으로 채워짐)
a >> b정수 a의 각 비트를 b만큼 오른쪽으로 이동 (빈자리는 최상위 부호 비트(MSB)와 같은 값으로 채워짐)
a >>> b정수 a의 각 비트를 b만큼 오른쪽으로 이동시키며, 새로운 비트는 전부 0으로 채워짐

정수 8을 왼쪽으로 2비트 이동시켜봅시다. 정수 8을 2진수로 표현하면 00001000 입니다.

비트 전체를 왼쪽으로 이동할 때 맨 왼쪽 2비트는 버려지고, 맨 오른쪽에 새로 생긴 2비트는 0으로 채워집니다. 연산 결과를 10진수로 변환해보면 32가 됨을 알 수 있습니다.

이번에는 정수 -8을 오른쪽으로 2비트 이동시켜봅시다.

비트 전체를 오른쪽으로 이동할 때 맨 오른쪽 2비트는 밀려서 버려지고, 맨 왼쪽에 새로 생긴 2비트는 최상위 부호비트(MSB)와 동일한 값으로 채워집니다. (최상위 부호비트가 0이면 양수, 1이면 음수입니다.) 연산 결과의 최상위 부호비트가 1이므로 2의 보수를 적용해보면 -2라는 값이 됨을 알 수 있습니다. (컴퓨터가 음수를 저장하기위해 2의 보수라는 방법을 사용합니다.)

이번에는 정수 -8을 >>> 연산자로 2비트 오른쪽으로 이동시켜봅시다.

비트 전체를 오른쪽으로 이동할 때 맨 오른쪽 2비트는 밀려서 버려지고, 맨 왼쪽에 새로 생긴 2비트는 무조건 0으로 채워집니다. 맨 왼쪽 최상위 부호비트가 0이 되었기 때문에 음수였던 값이 양수로 바뀌었음을 알 수 있습니다.

👌 삼항 연산자

삼항 연산자는 세 개의 피연산자가 있는 연산자를 말합니다.

아래 연산식에서 ? 앞의 조건식에 따라 콜론( : ) 앞 뒤의 연산자가 선택되는 조건 연산식 이라고 할 수 있습니다.

예시를 하나 볼게요.

public class Example {
	public static void main(String[] args) {
		int score = 95;
		char grade = (score > 90) ? 'A' : 'B';
		System.out.println("당신의 학점은 : " + grade);
	}
}

(score > 90) 을 연산하면 true 이고, true일 경우에는 첫번째 값 또는 연산식이 결과가 됩니다.

삼항연산자는 if조건절과 비슷하게 조건에 따른 값 선택을 하는 연산이기 때문에 if문으로 변경해서 작성할 수도 있겠죠. 위의 코드를 if문으로 변경해보겠습니다.

public class Example {
	public static void main(String[] args) {
		int score = 95;
		char grade;
		if (score > 90) {
			grade = 'A';
		} else {
			grade = 'B';
		}
		
		System.out.println("당신의 학점은 : " + grade);
	}
}

이처럼 삼항연산자는 if문으로 변경해서 작성할 수도 있지만, 간단한 연산식이라면 삼항연산자 한 줄로 간단하게 사용하는것이 더욱 효율적입니다. 가독성을 해치지 않는 선에서 실무에서도 굉장히 많이 사용하는 연산자입니다.

그럼 아래 코드에 대한 출력을 맞춰보세요. 여러분들이 문제를 맞췄다면 삼항연산자의 개념은 이해를 한겁니다.

public class Example {
	public static void main(String[] args) {
		int score = 95;
		char grade = (score > 90) ? 'A' : ((score > 85) ? 'B' : 'C'); 

		System.out.println("당신의 학점은 : " + grade);
	}
}
profile
https://www.linkedin.com/in/penameyo/

0개의 댓글