본문 바로가기
☕️ JAVA

JAVA의 연산자

by kukim 2021. 11. 1.

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

0. 연산자 우선순위

image

1. 단항 연산자

1.1 증감 연산자 ++, —

  1. 전위형 : 값이 참조되기 전에 증가시킨다.
  2. 후위형 : 값이 참조된 후에 증가시킨다.
// 1. 전위형 예
int i = 5;
int j = 0;

j = ++i;
System.out.printf("j = %d", j); // j의 값은? -> 6이다.

// 연산과정 순서
// 1. i 값 1 증가
// 2. i의 값을 참조
// 3. 연산 결과를 j에 저장

// 2. 후위형 예
int i = 5;
int j = 0;

j = i++;
System.out.printf("j = %d", j); // j의 값은? -> 6이 아닌 5이다.
// 연산과정 순서
// 1. i의 값을 참조
// 2. 연산 결과를 j에 저장
// 3. i 값 1 증가
// 후위형은 값이 참조된 후에 증가시키기 때문에 기존의 5를 j에 대입한 후 증가시킨다.

1.2 부호연산자 +, -

부호연산자는 말 그대로 부호의 반대 결과를 반환한다.

int i = -10;
i = -i;
// 이 과정을 거치면 i는 10이 된다.

int i = -10;
i = +i;
// + 부호연산자는 아무일 일어나지 않는다.

2. 산술 연산자

  • 일반적인 산술연산(+,-,*,/)는 많이 알고 있으니 JAVA에서 자주 실수하는 부분을 작성하려한다.
  • Primitive type에서 불리언형 제외하고 모두 사용 가능!
  • 참조형 중에 유일하게 문자열(String)은 산술연산 가능

산술 변환(usual arithmetic conversion)

  • 산술 변환 : 연산 직전에 피연산자의 자동 형변환
  1. 두 피연산자의 산술 연산시 타입을 같게 만들어 계산한다.(단, 쉬프트 연산, 증감 연산자 제외)
  2. 피연산자의 타입이 int보다 작은 타입이면 int로 변환된다.
long + int -> long + long = long
float + int -> float + float = float
double + float -> double + double = double

// 피연산자 타입이 int보다 작으면 int로 변환함
byte + short -> int + int = int
char + short -> int + int = int
// Case 1 : 0으로 나누었을 때 (1. 정수 0으로 나눌 때, 2. 실수 0으로 나눌 때)
System.out.println(3/0); // Error: ArithmeticException 발생!
System.out.println(3/0.0); // Infinity가 출력됨

// Case 2 : 자동 형변환 문제 : (int보다 작은 타입의 연산시 자동 형변환)
byte a = 10;
byte b = 20;
byte c = a + b; // 컴파일 Error
// int보다 작은 수의 산술 연산은 int로 변환되기 때문에 그 값을 byte에 넣으면 에러
// Fix : 산술 연산시 형변환 문제가 있으니 형변환을 명시해주자
byte c = (byte)(a + b); // 컴파일 Error

// Case 3 : 자동 형변환 문제
int a = 1_000_000;
int b = 2_000_000;

long c = a * b; // 오버플로우 발생
System.out.println(c)
// 결과 : -1454759936
// 해설 : a * b의 값이 2_000_000_000_000 long c에 담길 것 같지만 int의 오버플로우 되어 long에 들어감
// Fix : 형변환 명시하자
long c = (long)(a * b);

// Case 4 : 리터럴 명시
long a = 1_000_000 * 1_000_000; // 오버플로우 발생
long b = 1_000_000 * 1_000_000L; // long으로 변환되어 문제 없음

// Case 5 : 오버플로우 주의! (곱하기 나누기 순서에 따라)
int a = 1_000_000;
int result1 = a * a / a; // 곱하기가 먼저 나와서 오버플로우 발생 O
int result2 = a / a * a; // 나누기가 먼저 나와서 오버플로우 발생 X

// Case 6 : char의 연산
char a = 'a';
char d = 'd'
System.out.printf("%d", d-a); // char도 정수이기에 (d=100, a = 97)
// 결과 : 100 - 97 = 3 

char c1 = 'a';
char c2 = c1 + 1; // 컴파일 에러 O  -> char c2 = (char)c1 + 1; 로 해야함
char c3 = 'a' + 1; // 컴파일 에러 X

// Case 7 : %(나머지)연산자, 음수양수 구분 X
10 % 8 // 나머지 2
10 % -8 // 나머지 2
-10 % 8 // 나머지 -2
-10 % 8 // 나머지 -2
// 나머지 연산 뒤의 피연산자는 부호 무시되고 연산된다.

3. 비트연산자

<< 연산자 : 비트를 왼쪽으로 이동

>> 연산자 : 비트를 오른쪽으로 이동

>>> 연산자 : 부호 없는 비트이동 연산(부호 비트까지 비트 이동)

// >>, << 예제
int op = 12; 
// op = 0000 0000 0000 0000 0000 0000 0000 1100

int left_shift = op << 1; 
// left_shift = 0000 0000 0000 0000 0000 0000 0001 1000 좌측 쉬프트 연산

int right_shift = op >> 1; 
// right_shift = 0000 0000 0000 0000 0000 0000 0000 0110 우측 쉬프트 연산

// >>> 예제
int num1 = -125; // 1111 1111 1111 1111 1111 1111 1000 0011
int num2 = -125; // 1111 1111 1111 1111 1111 1111 1000 0011

num1 = num1 >> 1;
//1111 1111 1111 1111 1111 1111 1100 0001 : num1 = -65

num2 = num2 >>> 1;
// 0111 1111 1111 1111 1111 1111 1100 0001 : num2 = 2147483585

& : AND 비트연산

| : NOT 비트연산

~ : XOR 비트연산

// 이해를 돕기위해 단순하게 2진수로 작성하였다.
// & 연산자
1010 1011 & 0000 1111
= 0000 1011

// | 연산자
10101011 | 00001111
= 1010 1111

// ~ 연산자
~ 1010 1011
= 0101 0100

4. 비교 연산자

  • 두 피연산자 비교, 연산결과는 오직 true, false
  • , <, ≥, ≤, == 이 있다.
  • 일반적으로 아는 상식 말고 JAVA에서 유의할 점만 작성하였다.
10 == 10.0f // true : int 10이 자동 형변환으로 float로 바뀌어 연산됨
10.0 == 10.0f // true : double와 float간의 정밀도 차이가 있지만 소수점이 0이기 때문
0.1 == 0.1f // false : double와 float간의 정밀도 차이로 소수점에서 차이가 발생하기 때문

'A' == 65 // true

문자열 비교연산과 연산자 오버로딩

// Reference type : String 비교
String name1 = "kukim";
String name2 = "kukim";
String name3 = new String(name1);
String name4 = new String("kukim");

name1 == name2; // true
name1 == name3; // false
name1 == name4; // false
name1 == "kukim"; // true

// Reference type에서 == 연산자는 실제 문자열이 아닌 주소값을 비교한다.
// name1 == name2는 실제로 같은 메모리 주소를 가르키고 있기 때문에 true로 나온다
// 이는 String pool이라는 자바의 힙 메모리에 저장된 문자열 풀인데 매번 kukim이라는 문자열을 새롭게 만드는 것이 아니다.

// 완전 새롭게 메모리 공간을 할당하여 문자열을 생성하기 위해선 new String으로 만들어야한다.
// 따라서 name3,4는 완전 새로운 힙 영역에 할당하였기 때문에 메모리 주소가 달라서 false이다.

// 문자열의 메모리 주소 비교가 아닌 실제 문자열 값을 비교하려면
// equals() 메서드를 사용해야한다.
name1.equals(name2); // true
name1.equals(name3); // true
name1.equals(name4); // true
name1.equals("kukim"); // true

// 왜그럴까?
C언어에서 문자열 s1[6] = "kukim" 과 s2[6] = "kukim"이 같은지 비교해본다면
s1 == s2으로 비교하면 위와 마찬가지로 문자열의 첫 번째 메모리 주소를 가르키는 곳을 비교하기 때문에
두 메모리 주소가 달라서 false가 나온다. 문자열은 하나의 메모리에 kukim이 다 들어있는 것이 아닌
메모리 하나에 하나씩 k,u,k,i,m 으로 들어있기 때문에
문자열 값을 직접 비교하려면 s1[0] == s2[0] 그 값 자체가 같은지 하나씩 비교해야 한다. 
따라서 반복문을 통해 하나하나 같은지 비교하고 끝까지 모두 같으면 true을 반환하는 함수를 작성해야한다. ex) strcmp()

int ft_strcmp(char *s1, char *s2)
{
    while (*s1 && *s2 && *s1== *s2) // s1, s2가 널이 아니고 그 값이 같을때
    {
        s1++; // s1, s2의 주소 하나씩 증가
        s2++;
    }
    return ((unsigned char)*s1 - (unsigned char)*s2)); // 만약 s1과 s2가 끝가지 모두 똑같다면 0을 return
}

image

  • 연산자 오버로딩 : 피연산자의 자료형에 따라 연산자의 동작을 바꾸는 것
  • 참고 : JAVA String pool
// +A)
C#에서는 문자열 간의 == 비교 연산이 메모리 주소가 아닌 문자열 값 자체를 비교하는데 이는 어떻게 가능한가?
그것은 바로 "연산자 오버로딩"을 통해서 가능하다.
"연산자 오버로딩"이란 피연산자의 자료형에 따라 연산자의 동작을 바꿀 수 있다.
JAVA에서도 연산자 오버로딩이 있는데 이는 바로 '+' 연산자와 '+=' 연산자이다.

String hello = "Hello! My name is ";
String name = "Kukim";
String hello_kukim = hello + name;
// hello_kukim 에는 Hello! My name is Kukim 이 들어가 있다.
이처럼 보통 + 연산자는 정수값이 더해져 결과가 나오지만 연산자 오버로딩을 통해
피연산자의 종류가 String일 때 정수값을 더하는것이 아닌 두 String을 붙일 수 있게 연산자를 변경한 것이다.
// JAVA는 이미 정해진 연산자 오버로딩만 사용할 수 있도 커스텀은 불가능하다.

5. 논리연산자

  • 논리연산자는 AND, OR, NOT이 있다.
// && : AND
// || : OR
// ! | NOT

int i = 8;
(i % 2 == 0) || (i % 3 == 0) // 결과는 무엇인가? true!
// 8 % 2 == 0 || 8 % 3 ==0
// 0 == 0 || 2 == 0
// true || false
// ture
  • Short Circuit evaluation

// 아래의 결과는 무엇이 나올까?
int i = 0;
int j = 0;
int k = 0;

if (++i || ++j || ++k)
    Syetem.out.printf("i = %d, j = %d, k = %d", i, j, k);

// i = 1, j = 1, k = 1 이 나올까?

// 정답 : i = 1, j = 0, k = 0 이다.
// 왜냐하면 맨 앞의 ++i  연산을 먼저 수행하는데 i = 1이 되었다. 다음 수행할 연산은 OR 연산이다.
// 이때 OR 연산자의 경우 앞의 피연산자가 1인 경우 뒤의 ++j 피연산자를 평가하지 않고 그자리에서 종료한다.
// 따라서 뒤의 ++j, ++k 피연산자들은 평가되지않고 종료된다.ttt

6. 삼항 연산자

  • 삼항 연산자는 하나밖에 없는데 이는 조건 연산자 라고도 한다.
조건식 ? 식1 : 식2

조건식이 true이면 식1을 조건식에 대입
조건식이 false이면 식2을 조건식에 대입

// result = (x > y) ? x : y;
(x > y)이 true 이면 result = x;
(x > y)이 false 이면 result = y;
result = x;

삼항연산자는 if문 과 같다.

if (x > y)
    result = x;
else
    result = y;

7. 대입 연산자

  • 대입연산자는 다른연산자와 다르게 결합법칙 순서가 ← 이다.
x = y = 3 이라면
1. y = 3
2. x = 3  순서대로 평가된다.

// 복합 대입연산자
i *=5 + j; // i = i * (5 + j);
i +=5; // i = i + 5;
i -=5; // i = i - 5;
i *=5; // i = i * 5;
i /=5; // i = i / 5;
i %=5; // i = i % 5;
i <<=5; // i = i << 5;
i >>=5; // i = i >> 5;
i &=5; // i = i & 5;
i |=5; // i = i | 5;
i ^=5; // i = i ^ 5;

Quiz

  • 시작과 끝지점의 숫자가 주어졌을 때, 중앙값을 구하는 코드이다. (1)에 알맞은 코드를 넣으시오. (단, 짝수일 경우 중앙값 = n/2 이다)
int start = 2_000_000_000;
int end = 2_100_000_000;

int mid = (...1...);
  • num의 값에 따라 'plus', 'minus', '0'을 출력하는 코드이다. 삼항 연산자를 이용해서 (1)에 알맞는 코드를 넣으시오.
class Exercise2 {
public static void main(String[] args) {
int num = 10;

System.out.println( (...1...));
}
}
// 실행결과
'plus'
  • 화씨를 섭씨로 변환하는 코드이다. (1)에 알맞는 코드를 넣으시오. C = 5/9 * (F - 32) 단, 변환 결과값은 소수점 셋째자리에서 반올림해야한다. (Math.round() 사용 불가)
class Exercise3 {
public static void main(String[] args) {
int fahrenheit = 100;

float celcius = (...1...);

System.out.println("Fahrenheit:"+fahrenheit);
System.out.println("Celcius:"+celcius);
}
}
// 실행결과
Fahrenheit:100
Celcius:37.78

// 문제풀이
float celcius = (int)((5/9f * (fahrenheit - 32))*100 + 0.5) / 100f;

1. 100 . 값에 을 곱한다
37.77778 * 100
2. 1 0.5 . 의 결과에 를 더한다
3777.778 + 0.5 → 3778.278
3. 2 int . 의 결과를 타입으로 변환한다
(int)3778,278 → 3778
4. 3 100f .(100 .) 의 결과를 로 나눈다 으로 나누면 소수점 아래의 값을 잃는다
3778 / 100f → 37.78
Answer
  • 시작과 끝지점의 숫자가 주어졌을 때, 중앙값을 구하는 코드이다. (1)에 알맞은 코드를 넣으시오. (단, 짝수일 경우 중앙값 = n/2 이다)
int start = 2_000_000_000;
int end = 2_100_000_000;

int mid = (...1...);

// 정답
int mid = (start + end) / 2; // 라고 작성할 수 있지만 오버플로우 발생
int mid = start + (end - start) / 2; // 오버플로우 문제 해결
int mid = (start + end) >> 1; // JAVA의 unsigned 비트연산을 통해 /2 로 해결
  • num의 값에 따라 'plus', 'minus', '0'을 출력하는 코드이다. 삼항 연산자를 이용해서 (1)에 알맞는 코드를 넣으시오.
class Exercise2 {
public static void main(String[] args) {
int num = 10;
System.out.println( (...1...));

// 정답
System.out.println(num > 0 ? " ": 양수 (num < 0 ? " " : "0") 음수 );
}
}
// 실행결과
'plus'
  • 화씨를 섭씨로 변환하는 코드이다. (1)에 알맞는 코드를 넣으시오. C = 5/9 * (F - 32) 단, 변환 결과값은 소수점 셋째자리에서 반올림해야한다. (Math.round() 사용 불가)
class Exercise3 {
public static void main(String[] args) {
int fahrenheit = 100;

float celcius = (...1...);
// 정답
float celcius = (int)((5/9f * (fahrenheit - 32))*100 + 0.5) / 100f;

System.out.println("Fahrenheit:"+fahrenheit);
System.out.println("Celcius:"+celcius);
}
}
// 실행결과
Fahrenheit:100
Celcius:37.78

// 문제풀이
float celcius = (int)((5/9f * (fahrenheit - 32))*100 + 0.5) / 100f;

1. 100 . 값에 을 곱한다
37.77778 * 100
2. 1 0.5 . 의 결과에 를 더한다
3777.778 + 0.5 → 3778.278
3. 2 int . 의 결과를 타입으로 변환한다
(int)3778,278 → 3778
4. 3 100f .(100 .) 의 결과를 로 나눈다 으로 나누면 소수점 아래의 값을 잃는다
3778 / 100f → 37.78



Reference 🌏

'☕️ JAVA' 카테고리의 다른 글

📕 소프트웨어의 품질과 그 특성들  (0) 2022.01.08
JAVA의 클래스  (0) 2021.11.01
JAVA의 제어문  (0) 2021.11.01
JAVA의 변수와 타입  (0) 2021.11.01
JAVA의 컴파일과 실행  (1) 2021.11.01

댓글