[3주차] 연산자

janjanee·2022년 8월 1일
0
post-thumbnail

2021.01.01 작성글 이전

3. 연산자

학습 목표 : 자바가 제공하는 다양한 연산자를 학습하세요.

연산자(operator)
연산을 수행하는 기호(+, -, , / 등) *피연산자(operand) : 연산자의 작업 대상(변수, 상수, 리터럴, 수식)

  • 단항 연산자 피연산자 개수 하나
  • 이항 연산자 피연산자 개수 두 개
  • 삼항 연산자 피연산자 개수 세 개

대부분의 연산자는 이항 연산자이고, 삼항 연산자는 오직 '? :' 하나 뿐이다.

-3 - 5

-3의 '-'는 '부호 연산자'이며, -3과 5사이의 '-'는 뺄셈 연산자이다. 부호 연산자 입장에서는 피연산자가 3 하나 뿐이라 '단항 연산자' 이며, 뺄셈 연산자 입장에서는 피연산자가 -3과 5 두 개로 '이항 연산자' 이다.

3-1. 산술 연산자

일반적으로 알고 있는 사칙 연산자 (+, -, *, /)와 나머지 연산자(%)가 있다.

산술 변환

산술 변환이란? 연산 수행 직전에 발생하는 피연산자의 자동 형변환
1. 두 피연산자의 타입을 같게 일치시킨다(더 큰 타입으로 일치)
2. 피연산자의 타입이 int보다 작은 타입이면 int로 변환된다.

이항 연산식에서 두 피연산자의 타입이 일치해야 연산이 가능하다.
하지만 두 피연산자의 타입이 다르다면? -> 형변환 전에 타입을 일치시켜야한다.

보통의 경우, 둘 중 더 큰 타입으로 일치시킨다. 이유는 작은 타입으로 형변환 하면 원래의 값이 손실 될 수 있기 때문이다.

int i = 30;
float f = 30.3f;

float result = f + (float)i;

큰 타입으로 형변환 하는 경우, 자동적으로 형변환 되기 때문에 형변환 연산자 생략이 가능하다.

float result = f + i;

아래와 같이 int보다 작은 타입 끼리 연산하는 경우 자동으로 int 타입으로 형변환된다.

byte + short -> int + int -> int
char + short -> int + int -> int

그 이유는 int가 가장 효율적으로 처리할 수 있는 타입이며, 범위가 좁은 타입 끼리 연산 시 오버플로우!!! 발생 가능성이 있기 때문이다.

참고: 모든 연산에서 '산술 변환'이 일어나지만 쉬프트 연산자(<<. >>), 증감 연산자(++, --)는 예외

이제부터 산술연산과 관련된 예제 코드를 살펴보도록 한다.

예제1

+, -, * 는 예상한 그대로 결과가 나왔다.

그런데, 나눗셈(/)을 살펴보면 3이다. 10 / 3 이면 3.3333... 이 나와야하는데 3의 결과가 나온다. int 타입 피연산자들을 연산했기 때문에 결과는 당연히 int 여서 소수점 아래 자리는 버린 3이라는 결과가 나온다.

소수점 아래 결과까지 확인하고 싶다면 마지막 코드처럼 두 개의 피연산자중 하나만 float로 형변환하면 연산 결과는 자동으로 큰 타입인 float 값을 반환한다.

예제2

byte 타입 피연산자를 연산한 결과 컴파일 에러가 발생하는 것을 볼 수 있다. int보다 작은 타입 정수형의 연산 결과는 int로 반환되는데, byte 타입에 대입했기 때문에 발생한다. (line 9) byte 타입으로 명시적 형변환을 하거나, int 타입에 대입을 하면 된다.

예제3

x와 y의 연산 결과가 다르다.
이유는 x의 경우 int 타입 피연산자를 곱하는데 곱한 값이 int 타입의 범위를 벗어나 오버플로우가 발생한 값이다.
이미 오버플로우가 발생한 값을 더 큰 타입인 long 타입에 대입 해봤자 결과는 똑같다.

따라서, y 처럼 한 쪽에 L 접미사를 붙여줘서 연산 결과가 long 타입이 될 수 있도록 만들면 원하는 결과를 얻을 수 있다.

예제4

사칙연산의 피연산자로 숫자 뿐만 아니라 문자도 가능하다. 문자는 해당 문자의 유니코드(부호없는 정수)로 바뀌어 저장되므로 숫자의 연산과 동일하다.

3-2. 비트 연산자 & | ^ ~ << >>

비트연산자는 피연산자를 비트단위로 논리 연산한다.

  • 피연산자로 실수는 허용하지 않는다. 정수(문자 포함)만 허용된다.

| (OR연산자) 피연산자 중 한 쪽 값이 1이면, 1을 결과로 얻는다. 그 외에는 0을 얻는다.
& (AND연산자) 피연산자 양 쪽이 모두 1이어야만 1을 결과로 얻는다. 그 외에는 0을 얻는다.
^ (XOR연산자) 피연산자의 값이 서로 다를 때만 1을 결과로 얻는다. 같을 때는 0을 얻는다.
~ (비트전환 연산자) 0은 1로 1은 0으로 바꾼다. 논리부정 연산자와 비슷하다. 1의 보수 연산자라고도 한다.

xyx | yx & yx ^ y~x
111100
101010
011011
000001

연산 결과는 비트 끼리의 연산 결과 이므로 45와 25를 2진수로 표현한 값을 계산해보면 알 수 있다.

// 45
00000000 00000000 00000000 00101101

// 25
00000000 00000000 00000000 00011001

// 45 & 25
00000000 00000000 00000000 00001001

// 45 | 25
00000000 00000000 00000000 00111101

// 45 ^ 25
00000000 00000000 00000000 00110100

// ~45
00000000 00000000 00000000 11010010

쉬프트 연산자 <<, >>, >>>

피연산자의 각 자리(2진수 표현)를 오른쪽 또는 왼쪽으로 이동시키는 연산자

x << y 정수 x의 비트를 y만큼 왼쪽으로 이동 (빈자리는 0으로 채워짐)
x >> y 정수 x의 비트를 y만큼 오른쪽으로 이동 (빈자리는 정수 x의 최상위 부호 비트(MSB)와 같은 값으로 채워짐)
x >>> y 정수 x의 각 비트를 y만큼 오른쪽으로 이동 (빈자리는 0으로 채워짐)

위의 코드를 2진수로 나타내면 다음과 같다.

// 1
00000000 00000000 00000000 00000001

// 1<<3
00000000 00000000 00000000 00001000     // 8

// -8
11111111 11111111 11111111 11111000

// -8 >> 3
11111111 11111111 11111111 11111111     // -1

// -8 >>> 3
00011111 11111111 11111111 11111111     // 536870911
  • x << n은 x * 2^n의 결과와 같다.
  • x >> n은 x / 2^n의 결과와 같다.

3-3. 관계 연산자

피연산자를 비교하여 관계를 확인하는 연산자이다. 관계 연산자의 연산 결과는 true와 false 둘 중 하나이다.

대소비교 <, >, <=, >=

값의 크기를 비교한다. 기본형 중 boolean을 제외한 나머지 자료형에서 사용가능한데 참조형에서는 사용불가.

등가비교 ==, !=

값이 같은지 다른지를 비교하는 연산자이다. 모든 자료형에서 사용할 수 있다. 기본형일 경우 값이 같은지를 비교, 참조형일 경우 객체의 주소값이 같은지를 비교

각 관계 연산에 대한 결과는 true, false 인 것을 확인할 수 있다.

위의 코드를 보면 10.0과 10.0f의 관계 연산 결과는 true 이다. 그런데 바로 아래 0.1과 0.1f의 관계 연산 결과는 false이다.

결과가 다른 이유는 10.0f는 오차없이 저장할 수 있는 값이라 double로 형변환해도 그대로 10.0이 되지만, 0.1f는 저장할 때 2진수로 변환하는 과정에서 오차가 발생한다.

더 자세히 설명하면

  1. 0.1과 0.1f 연산 시 0.1f float 타입이 -> double 타입으로 변하게 된다.
  2. float타입의 값을 double타입으로 형변환하면, 부호와 지수가 달라지지 않고 가수의 빈자리를 0으로 채운다.
0 01111011 10011001100110011001101

0 0001111111011 1001100110011001100110100000000000000000000000000000
  1. 따라서 0.1f를 double타입으로 형변환했어도 값이 변하지 않았기 때문에 결과는 false이다.

float와 double 타입의 값을 비교하려면 어떻게 해야할까?

double타입 값 -> float타입으로 형변환한 다음에 비교

****

문자열 비교

문자열을 비교할 때는 '==' 대신 equals()메서드를 사용하자.

3-4. 논리 연산자

둘 이상의 조건이 결합된 경우, 'AND' 또는 'OR'을 연결하여 하나의 식으로 표현

|| (OR)   피연산자 중 어느 한 쪽만 true이면 true를 결과로 반환
&& (AND)  피연산자 양쪽 모두 true이어야 true를 결과로 반환
x > 5 && x < 10 

또는 

5 < x && x < 10

5 < x < 10 이렇게 표현하는 것은 X

x는 5보다 크고 10보다 작다. 라는 식을 AND(&&) 조건으로 연결

논리 연산자를 이용하여 여러 조건의 식을 연산한 결과이다.

효율적인 연산(short circuit evaluation)

논리 연산자는 효율적인 연산을 한다. 예를들어, OR 연산의 경우 두 피연산자 중 한 쪽의 조건이 참이면, 전체 연산결과가 참이므로 다음 피연산자의 연산은 넘어간다.

x || y 에서 x가 true 면, 결과는 항상 true니까 y의 값은 평가하지 않음.

x && y 에서 x가 false 면, 결과는 항상 false니까 y의 값은 평가하지 않음.

논리 부정 연산자 !

'!' 연산자는 결과가 true면 false로 false면 true로 반환한다. 부정이니까 반대로 결과를 반환한다고 생각하면 되겠다.

3-5. instanceof

참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아내기 위해 사용하는 연산자 어떤 타입에 대한 instanceof연산의 결과가 true 라는 것은 검사한 타입으로 형변환이 가능하다는 뜻

바로 예제 코드를 살펴보자.

car, dog, corona 세 가지 인스턴스를 생성 후 각 인스턴스의 타입을 출력한다. 각각의 타입에 맞게 결과가 출력되는 것을 볼 수 있다.

Ambulance와 FireEngine 클래스는 Car를 상속받고 있다. printType 메소드에서 각 인스턴스가 Car의 어떤 자손 클래스인지 확인할 수 있다.

3-6. assignment(=) operator

대입연산자는 변수와 같은 저장공간에 값 또는 수식의 연산결과를 저장하는데 사용

대입 연산자는 연산자들 중 가장 낮은 우선순위를 가지고 있기 때문에 가장 나중에 실행된다.

x = y = 3

위의 식에서 순서는 y = 3 이 먼저 실행 되고 x = y 가 마지막으로 실행된다.

대입 연산자는 다른 연산자와 결합하여 op= 와같은 방식으로 사용될 수 있다.

i = i + 3
i += 3

예를 들어 위의 식을 줄여서 아래의 += 로 사용할 수 있다.

아래는 복합 대입 연산자의 종류이다.

op==
i += 3i = i + 3
i -= 3i = i - 3
i *= 3i = i * 3
i /= 3i = i / 3
i %= 3i = i % 3
i <<= 3i = i << 3
i >>= 3i = i >> 3
i &= 3i = i & 3
i ^= 3i = i ^ 3
i |= 3i = i | 3
i *= 10 + ji = i * (10 + j)

3-7. 화살표(->) 연산자

Java 8부터 등장한 연산자. 람다식에서 사용되는 연산자 이다.

첫번째 코드는 Runnable 인터페이스의 익명 구현 객체를 생성하는 코드이다.
****3개의 모드가 모두 동일한 코드인데, 자바 8 부터는 람다식을 사용하여

두번째 코드처럼 축약해서 사용할 수 있다. 이 때 화살표 연산자가 사용된다.
자바스크립트를 사용할 때도 사용했던 표현식이다. (자바스크립트에서는 화살표 연산자가 => )

리턴 값이 블럭없이 한줄로 표현 가능할 때는 세번째 코드처럼 표현도 가능하다

3-8. 3항 연산자

조건식, 식1, 식2 세 개의 피연산자를 갖고 있다.

조건식의 결과에 따라 반환하는 값이 달라진다.

result = x > y ? x : y;

조건식 x > y 가 true 이면 x를 반환하고 false 이면 y를 반환한다.

  • 삼항 연산자는 if문이랑 바꿔 쓸 수 있으며, if 문 대신 삼항 연산자를 사용하면 코드를 간략하게 짤 수 있다.

3-9. 연산자 우선 순위

연산식에 사용되는 '연산자'가 두 개 이상일 경우, 연산자의 우선순위에 의해서 연산순서가 결정된다.
수학에서 곱셈, 나눗셈이 덧셈과 뺼셈보다 우선순위가 높은 것은 연산식에서도 동일하다.

아래의 표를 보면 대부분 상식적인 선(우리가 교과과정을 통해 배운 수학)에서 연산식의 우선순위를 알 수 있다.

설명
-x + 3단항 연산자가 이항 연산자보다 우선순위가 높다. x의 부호가 먼저 바뀌고, 덧셈이 수행된다.
x + 5 * y곱셈이 먼저 수행되므로 5 * y 가 수행된 후, 덧셈이 수행된다.
x + 3 > y - 2비교 연산자(>)보다 산술 연산자인 덧셈, 뺄셈이 먼저 수행된다.
x > 3 && x < 8논리 연산자(&&) 보다 비교 연산자가 먼저 수행된다.
result = x + y * 9대입 연산자는 연산자 들 중에 가장 우선순위가 낮다.

상식만으로 판단하기 어려운 우선순위 몇 가지도 존재하는데 아래 표를 확인해보자.

설명
x << 2 + 1쉬프트 연산자(<<)는 덧셈 연산자보다 우선순위가 낮다.
data & 0xFF == 0비트 연산자(&)는 비교 연산자(==)보다 우선 순위가 낮다.
x < -1 || x > 3 && x < 5논리 연산자 중에서 AND('&', '&&')가 OR('|', '||') 보다 우선 순위가 높다.

만약, 하나의 식에 우선순위가 같은 연산자 들이 여러개 있는 경우는 어떤 순서로 연산을 수행하는지 살펴보자.

  • 대부분 왼쪽에서 오른쪽의 순서로 연산을 수행
  • 단항 연산자와 대입 연산자만 오른쪽에서 왼쪽의 순서로 수행
// 1
3 + 4 - 5

// 2
x = y = 3

1번의 경우는 3 + 4 가 수행 후 7 - 5가 수행된다. 2번의 경우는 대입 연산자이므로 오른쪽부터 시작해 y = 3이 수행된 후 x = 3이 수행된다.

정리하자면 다음과 같다.

1. 산술 > 비교 > 논리 > 대입  대입은 가장 마지막에 수행된다.
2. 단항 > 이항 >  삼항 단항 연산자의 우선순위가 이항 연산자 보다 높다.
3. 단항, 대입 연산자를 제외한 모든 연산자의 진행 순서는 왼쪽에서 오른쪽이다.

우선순위와 연산방향이 정해져있어도 여러 가지 연산자들이 섞여있다면 헷갈릴 수 있다.
그럴 때, 괄호()를 사용해서 먼저 처리해야 할 연산식을 묶는다. (참고로 괄호 자체가 연산자는 아니다.)

연산자 우선순위는 표를 보며 달달 외우기 보다는 사용하다 보면 자연스럽게 익숙해지므로 외울 필요는 없다.
헷갈릴 때는 괄호를 쓰자!

(10 + 8) * 9

x < 8 || (x > 3 && x < 10)

3-10. switch 연산자 (Java 13)

Java 13 switch 연산자(표현식)은 Java 12 switch 표현식에 yield 키워드를 확장한 것이다.

테스트 환경이 Java15인데 Java12의 break 구문은 컴파일 오류가 나는것을 확인할 수 있다.
대신 아래와 같이 yield 키워드를 사용할 수 있다.

Java 12의 멀티 레이블 규칙 (',' 콤마로 구분)이나 화살표(arrow) 식은 13버전 에서도 사용 가능하다.

아래는 예제 코드 실행 결과이다.

References

  • 남궁성, 『자바의 정석』, 도우출판(2016)
  • 신용권, 『이것이 자바다』, 한빛미디어(2018)
  • Java 13 Switch Expressions
  • 출처가 없는 이미지는 직접 그림
profile
얍얍 개발 펀치

0개의 댓글