Cute Running Puppy

Language/[Java] 자바의 정석

[자바의 정석] chapter 03. 연산자 operator

R.silver 2023. 1. 8. 16:57
반응형

1. 연산자 (operator)

연산을 수행하는 기호

1.1 연산자와 피연산자

연산자 (operator): 연산을 수행하는 기호

피연산자 (operand): 연산의 작업 대상 (변수, 상수, 리터럴, 수식)

연산자는 피연산자로 연산을 수행하고 나면 항상 결과 값을 반환한다

1.2 식과 대입연산자

식: 연산자와 피연산자를 조합하여 계산하고자 하는 바를 표현한 것

식 평가: 식을 계산하여 결과를 얻는 것

작성한 식을 프로그램에 포함시키기: 식의 끝에 ‘;’ 붙이기

평가된 값은 대입 연산자=’를 사용해야만 저장된다

1.3 연산자의 종류

분류1. 산술, 비교, 논리, 대입 연산자

분류2. 단항, 이항, 삼항 연산자

1.4 연산자의 우선순위와 결합 규칙

연산자가 둘 이상일 경우, 연산자의 우선순위에 의해 연산순서가 결정된다

주의해야 할 연산자 우선순위의 예와 설명

  1. x << 2 + 1

    << 연산자는 덧셈 연산자보다 우선순위가 낮기에 해당 식은 x << (2+1)과 같다

  2. data & 0xFF == 0

    & 연산자는 == 연산자보다 운선순위가 낮기에 해당 식은 data & (0xFF == 0) 와 같다

  3. x < -1 || x > 3 && x < 5

    OR 연산자는 AND 연산자보다 우선순위가 낮기에 해당 식은 x < -1 || (x > 3 && x < 5) 와 같다

    수식에 AND와 OR 연산자가 동시에 사용된다면 괄호를 사용하여 순위를 명확히 하는 것이 좋다

+) 괄호는 연산자가 아니다. 연산자의 우선순위를 임의로 정할 때 사용하는 기호이다

연산자의 결합 규칙

왼쪽 → 오른쪽: 대부분의 연산자

오른쪽 → 왼쪽: 단항 연산자, 대입 연산자

****정리****

  1. 산술 > 비교 > 논리 > 대입 순서로 연산이 진행된다
  2. 단항 > 이항 > 삼항 순서로 연산이 진행된다
  3. 단항 연산자와 대입 연산자를 제외한 모든 연산의 진행 방향은 왼쪽 → 오른쪽

1.5 산술 변환 (usual arithmetic conversion)

이항 연산자는 두 피연산자의 타입이 일치해야 연산이 가능하다

대부분의 경우 자동적으로 피연산자의 타입 중 큰 타입으로 형변환이 진행된다

(작은 타입으로 형변환이 될 경우 값이 손실 될 수 있다)

**산술 변환 (일반 산술 변환) 규칙**

  1. 두 피연산자의 타입을 같게 일치시킨다 (보다 큰 타입으로 일치)

    피연산자의 값 손실을 최소화 하기 위해

  2. 피연산자의 타입이 int 보다 작은 타입이면 int로 변환된다

    정수를 가장 효율적으로 처리하기 위해

    (예를 들어 char, short은 연산 중 오버플로우가 발생할 수 있다)

주의

연산결과의 타입은 피연산자의 타입과 일치한다

int / int → int (연산결과가 실수라면 정수부 부분을 제외한 부분이 버려진다)


2. 단항 연산자

2.1 증감 연산자 ++ --

피연산자에 저장된 값을 1 증가 도는 감소시키는 연산자

오직 대입연산자와 증감연산자만이 피연산자의 값을 변화시킨다

타입 설명 사용 예
전위형 값이 참조되기 전에 증가 j = ++i;
후위형 값이 참조된 후에 증가 j = i++;

증감 연산자가 수식이나 메서드 호출 중간에 포함될 때 차이가 발생한다

독립적인 하나의 문장으로 사용된 경우에는 전위형과 후위형의 차이가 없다

int i = 5, j = 0;

j = i++;
System.out.println("i = " + i + ", j = " + j); // i = 6, j = 5

i = 5, j = 0; 
j = ++i;
System.out.println("i = " + i + ", j = " + j); // i = 6, j = 6

하나의 식에서 증감연산자의 사용을 최소화하고,

식에 두 번 이상 포함된 변수에 증감연산자를 사용하는 것을 피해야 한다

2.2 부호 연산자 + -

피연산자의 부호를 반대로 변경한 결과를 반영하는 연산자

boolean, char 형을 제외한 기본형에서만 사용 가능하다


3. 산술 연산자

주의 할 점 위주로 서술

3.1 사칙 연산자 +-*/

  1. 연산결과의 타입은 피연산자의 타입과 일치
int a = 10;
int b = 4;

system.out.println("%d / %d = %d%n", a, b, a / b); // 10 / 4 = 2

나누기 연산에서 두 피연산자가 모두 int 형일 때 연산 결과도 int이기에 소수점 이하는 버려진다

올바른 연산 결과를 얻기 위해선느 피연산자 중 어느 한 쪽을 실수형으로 변경해야 한다

system.out.println("%d / %d = %d%n", a, (float)b, a / (float)b); 
// 10 / 4.000000 = 2.500000

두 피연산자의 타입이 일치하지 않으면 int 타입 보다 범위가 넓은 float 타입으로 일치시킨 후 연산이 수행되기에 연산 결과가 float 형이 된다

  1. 0으로 나누기

피연산자가 정수형인 경우, 나누는 수로 0을 사용할 수 없다 → ArithmeticException

부동 소수점 값인 0.0f, 0.0d로 나누는 것은 가능하지만 그 결과는 Infinity이다

  1. 명시적 형 변환이 필요한 경우
byte a = 10;
byte b = 20;
byte c = a + b;  // 컴파일 에러 
System.out.println(c);

int형 보다 작은 정수형은 계산시 int형으로 변환 된 뒤 계산이 진행된다

byte는 계산 시 int 형으로 변환되어 a + b의 결과는 int 형이 된다

위의 코드에서 int 형인 연산 결과를 byte에 형 변환 없이 저장하려 했기에 에러가 발생했다

큰 자료형의 값을 작은 자료형의 변수에 저장하려면 명시적으로 형 변환 연산자를 사용해서 변환해주어야 한다

byte c = (byte) (a + b);
  1. 형변환시 발생하는 데이터 손실
byte a = 10;
byte b = 30;
byte c = (byte) (a * b);
System.out.println(c); // 44

a * b 의 연산 결과인 300이 출력되지 않고 44가 출력되었다

이는 300이 byte가 표현할 수 있는 값의 범위를 넘어섰기에 발생한 문제로 이를 해결하기 위해서는

큰 값에서 작은 값으로 형 변환을 진행할 때 충분히 큰 자료형을 사용해야 한다

  1. 연산 결과를 넘어서는 타입 사용
int a = 1_000_000;
int b = 2_000_000;
long c = a * b; // 전혀 다른 값이 출력됨
System.out.println(c);

연산 결과를 담는 long이 2X10^12을 저장하기에 충분히 크기에 문제가 발생하지 않을 것 같지만 올바르지 않은 결과가 출력된다는 문제가 발생했다

int 타입과 int 타입의 연산 결과는 int 형이기에 연산 결과가 int 형의 범위를 넘어섰기 때문이다

이미 오버플로우가 발생한 값을 아무리 long 타입에 변수에 저장해도 소용이 없다

그러므로 연산 결과가 long 형으로 자동 변환되더라도 올바르지 않은 값이 들어가게 된다

long c = (long)a * b;

올바른 연산 결과를 얻기 위해서는 이와 같이 변수 a 또는 b의 값을 long 형으로 형변환 해야 한다

  1. 문자 연산

문자를 숫자로 변환하려면 ‘0’을 빼주면 된다

'2' - '0' -> 50 - 48 -> 2 

대문자를 소문자로 변경하려면 32를 빼주고,

소문자를 대문자로 변경하려면 32를 더해주면 된다

char lowerCase = 'a';
char upperCase = (char) (lowerCase - 32);
  1. 상수, 리터럴 간의 연산에서의 형변환
char c1 = 'a';
// char c2 = c1 + 1; // 컴파일 에러 
char c3 = 'a' + 1; 

상수 또는 리터럴 간의 연산은 컴파일러가 컴파일 이전에 계산해서 대체함으로써 보다 효율적인 코드를 만든다 (실행 과정에서 변하는 값이 아니다)

그러므로 c2는 컴파일러가 미리 계산을 할 수 없기에 형변환을 해주어야 하며,

c3는 리터럴 간의 연산이기에 컴파일러가 미리 계산한다. 그러므로 c3에서는 형 변환이 필요 없다

  1. 소수점 n자리까지 꺼내기
(int) (pi * 1000) / 1000f
// (int) (3141,592f) / 1000f
// 3141 / 1000f
// 3.141

int / int의 특성 이용

  • 연산의 의 결과는 int
  • 나눗셈의 결과는 버린다
  1. 반올림 하기
(int) (pi * 1000 + 0.5) / 1000.0
// (int) (314**1,5**92f + 0.5) / 1000.0
// (int) (314**2.0**92) / 1000.0
// 314**2** / 1000.0
// 3.142

+) Math.round() 메서드 사용

매개변수로 받은 값을 소수점 첫째자리에서 반올림하는 함수

3.2 나머지 연산자 %

나누는 수에 0을 사용할 수 없다

음수도 허용되나, 부호가 무시되어 결과는 음수의 절대값으로 나눈 나머지의 결과와 같다

짝수, 홀수, 배수 검사 등에 사용된다


4. 비교 연산자

주로 조건문과 반목문의 조건식에 사용

연산결과는 오직 true, false 둘 중 하나이다

피연산자의 타입이 서로 다를 경우에는 자료형의 범위가 큰 쪽으로 자동 형변환하여 피연산자의 타입을 일치키신 후에 비교

4.1 대소비교 연산자 < > <= >=

boolean과 참조형을 제외한 나머지 자료형에 사용할 수 있다

4.2 등가비교 연산자 == !=

기본형, 참조형 모두 사용 가능하다. 즉, 모든 자료형에 사용 가능하다

참조형의 경우 객체의 주소 값을 저장하기에 두 개의 피연산자 (참조변수)가 같은 객체를 가리키고 있는지를 알 수 있다

기본형과 참조형은 서로 형변환이 가능하지 않기에 등기비교 연산자로 기본형과 참조형을 비교할 수 없다

System.out.println(10.0 == 10.0f) // true
System.out.println(0.01 == 0.01f) // false 

실수형은 정수형과 달리 근사값으로 저장되어 오차가 발생할 수 있기에 위와 같은 오차가 생기는 것이다

0.1f는 저장할 때 2진수로 변환하는 과정에서 오차가 발생한다

double 타입도 오차가 발생하지만 float 타입보다 적은 오차로 저장된다

float 타입과 double 타입을 비교하기 위해서는

  1. double 타입을 float 으로 변환한 뒤 비교
  2. 어느 정도의 오차를 무시하고 앞 자리만 비교

**문자열 비교**

두 문자열을 비교할 때에는 ‘==’ 가 아닌 equlals() 라는 메서드를 사용해야한다

비교 연산자는 두 문자열이 완전히 같은지를 비교(주소)하는 것이기에 문자열의 내용을 비교하기 위해서는 equals()를 사용해야 한다

+) 대소문자를 구별하지 않고 비교하려면 equalsIgnoreCase() 를 사용하면 된다


5. 논리 연산자

둘 이상의 조건을 AND, OR로 연결하여 하나의 식으로 표현할 수 있기 해준다

5.1 논리연산자 - &&, ||, !

논리연산자는 피연산자로 boolean 형 또는 boolean 형을 결과로 하는 조건식만 허용한다

  1. 가독성 높이기

    x > 10 && x < 2010 < x && x < 20

  1. 배수 확인

    2의 배수 또는 3의 배수이다

    i % 2 == 0 || i % 3 == 0

    2의 배수 또는 3의 배수이지만 6의 배수는 아니다

    (i % 2 == 0 || i % 3 == 0) && i % 6 != 0

    +) &&는 || 보다 우선순위가 높다

****효율적인 연산 (short circuit evaluation)****

  1. OR

    좌측 피연산자가 true이면 우측 피연산자의 값은 평가하지 않는다

  2. AND

    좌측 피연산자가 false이면 우측 피연산자의 값은 평가하지 않는다

이러한 특성으로 같은 조건식이더라도 피연산자의 위치에 따라 연산속도가 달라질 수 있다

예를 들어 OR 연산의 경우 연산결과가 true일 확률이 높은 피연산자를 연산자의 왼쪽에 놓아야 더 빠른 연산결과를 얻을 수 있다

**논리 부정 연산자 !**

참과 거짓이 차례로 반복되는 특성을 활용하여 toggle button을 구현할 수 있다

조건문과 반복문의 조건식에서 주로 사용된다

논리 부정 연산자를 적절히 활용해서 보다 이해하기 쉬운 식으로 만들자

5.3 비트 연산자 & | ^ ~ << >>

피연산자를 비트단위로 논리 연산하는 연산자

피연산자로 실수는 허용하지 않으며 이진수로 변환하여 연산한다

  1. |

    특정 비트의 값을 변경할 때 사용

  2. &

    특정 비트의 값을 뽑아낼 때 사용

  3. ^

    간단한 암호화에 사용

    (같은 값으로 두고 XOR 연산을 수행하면 원래의 값으로 돌아오는 성질 활용)

+) 비트 연산에서도 산술 변환(타입 일치)이 일어날 수 있다

** 비트 전환 연산자 ~ **

논리 부정 연산자 !와 유사

1의 보수 연산자라고도 부른다

  • 양의 정수 p를 음의 정수로 변환하는 방법

    ~p + 1

  • 음의 정수 n을 양의 정수로 변환하는 방법

    ~n - 1

  • ~~p

    연산 결과가 int가 되는 것에 주의하자

쉬프트 연산자 << >>

<< 연산자의 경우 피연산자의 부호에 관계 없이 각 자리를 왼쪽으로 이동시키며 빈칸을 0으로 채우면 된다

>> 연산자의 경우 오른쪽으로 이동시키기에 부호가 변경될 수 있다

이때, 부호를 유지하기 위해 왼쪽 피연산자가 음수인 경우 빈자리를 1으로 채운다 (양수일 경우 0)

쉬프트 연산자는 다른 이항 연산자들과 달리 피연산자의 타입을 일치시킬 필요가 없기에 우측 피연산자에는 산술 변환이 적용되지 않는다

쉬프트 연산자는 가독성이 좋지 않기 때문에 보다 빠른 실행 속도가 요구되어지는 곳에서만 사용하는 것이 좋다


6. 그 외의 연산자

6.1 조건 연산자 ?:

세 개의 피연산자를 필요로 하는 삼항 연산자이며, 삼항 연산자는 조건 연산자 하나뿐이다

첫 번째 피연산자의 조건식인 평가 결과에 따라 다른 결과를 반환한다

조건 연산자는 if문으로 변경할 수 있지만 조건 연산자를 사용하면 코드를 간결하게 작성할 수 있다

중첩이 가능하나 가독성을 위해 꼭 필요한 경우에만 중첩을 사용하자

6.2 대입연산자 = op=

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

가장 낮은 우선순위를 가지고 있기에 식에서 가장 나중에 수행된다

lvalue 와 rvalue

lvalue: 대입연산자의 왼쪽 피연산자

반드시 변수처럼 값을 변경할 수 있는 것이여야 한다

rvalue: 대입연산자의 오른쪽 피연산자

변수, 식, 상수 등 모든 것이 가능하다

**복합 대입 연산자**

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

예) +=, -=

반응형