본문 바로가기
개발자 꿈나무의 하루/01_Boot Camp

(네이버클라우드 부트캠프) 10일차 - Java프로그래밍 기초(연산자)

by kk_naks 2024. 6. 7.

1. 연산자

  1.1 연산자의 종류

     - 연산자의 종류 

연산자명 종류 설명
산술연산자 + , - , * , / ,% 두값을 사칙연산해주는 연산자
관계연산자 >, <, >=, <=, ==, != 두값을 비교하는 연산자
논리연산자 &&, ||, !, ^, 두값의 논리값을 비교하는 연산자
비트연산자 >>, >>>, <<, &, |, ^, ~ 비트의 값을 연산하는 연산자
조건연산자(삼항연산자) 조건 ? 값1 : 값2 조건에 따라 값을 결정하는 연산자
증감연산자 i++, ++i, i--, --i 값을 1증가 혹은 1감소시키는 연산자
할당연산자 +=,-=,*=, /=, %= 값을 계산하고 대입하는 연산자

 

  1.2 연산자의 우선순위

     - 연산자 우선순위에 따라 코드를 수행한다. 

2. 산술연산자

  2.1 산술연산자의 기본종류 

     - 산술연산은 기본적인 사칙연산을 나타낸다.

package com.eomcs.lang.ex05;

//# 산술 연산자 : +, -, *, /, %
//
public class Exam0110 {
  public static void main(String[] args) {
    System.out.println(100 + 27);
    System.out.println(100 - 27);
    System.out.println(100 * 27);
    System.out.println(100 / 27);
    System.out.println(100 % 27); // 나눈 나머지 
  }
}

 

  2.2  형변환

     - 묵시적 형변환

- 자바에서 정수는 기본 int형이다. 
- 정수연산을 할때 int(4byte)형의 메모리를 만든 후, 연산을 수행한다. 
package com.eomcs.lang.ex05;

//# 산술 연산자 : 기본 연산 단위
//
public class Exam0130 {
  public static void main(String[] args) {

    byte b;
    b = 5; // OK!
    b = 6; // OK!
    // - 리터럴 5, 6은 4바이트 정수 값이다.
    // - 정수 리터럴은 기본이 4바이트 크기이지만,
    // - byte 변수에 저장할 수 있다면 허락한다!

    b = 5 + 6; // OK!
    // - 리터럴끼리 산술 연산한 결과도 리터럴로 간주한다.
    // - 그 결과 값이 변수의 범위 내의 값이면 허락한다.

    System.out.println(b);
    // - 이유? 리터럴 값은 컴파일 단계에서 그 값이 얼마인지 확인할 수 있기 때문이다.
    // - 변수의 경우는 컴파일 단계에서 값을 확인할 수 없다.

    byte x = 5, y = 6, z;
    z = x; // OK!
    z = y; // OK!

    //    z = x + y; // 컴파일 오류!
    //
    // "자바의 정수 연산은 최소 단위가 4바이트이다."
    // "그래서 byte나 short의 연산 단위가 기본으로 4바이트이다."
    //
    // - 자바는 정수 변수에 대해 산술 연산을 수행할 때,
    //   그 변수의 값이 4바이트 보다 작다면(byte나 short 라면),
    //   4바이트로 만들어 연산을 수행한다.
    // - 즉 임시 4바이트 정수 메모리를 만든 다음에 
    //   그 메모리에 값을 담은 후에 연산을 수행한다.
    // - 따라서 x + y는 바로 실행하지 않고
    //   임의의 4바이트 정수 메모리를 만든 다음에 
    //   그 메모리에 x와 y 값을 넣고 연산을 수행한다.
    // - 연산 결과도 당연히 4바이트이다.
    //   그래서 4바이트 값을 1바이트 메모리에 넣지 못하기 때문에
    //   컴파일 오류가 발생하는 것이다!

    // short도 마찬가지이다.
    short s1 = 5;
    short s2 = 6;
    short s3;
    s3 = s1; // OK!
    s3 = s2; // OK!
    //    s3 = s1 + s2; // 컴파일 오류!

// 결론!
// - 숫자의 크기에 상관없이 작은 숫자를 다루더라도 
//   정수를 다룰 때는 그냥 int를 사용하라!
// - byte는 보통 파일의 데이터를 읽어 들일 때 사용한다.
- 자바의 최소 연산 단위는 int이다.
- int 보다 작은 크기의 메모리 값을 다룰 때는 내부적으로 int로 자동 형변환을 수행한 다음에 연산을 수행한다.
- 내부적으로 자동 형변환하는 것을  "암시적 형변환(implicit type conversion)"이라 부른다.
  => byte + byte = int
  => short + short = int
  => byte + short = int
- 연산 결과는 항상 피연산자의 타입과 같다.
  => int + int = int
  => long + long = long
  => float + float = float
  => double + double = double
- 다른 타입과 연산을 수행할 때는 내부적으로 같은 타입으로 맞춘 다음에 실행한다.

  2.3  오버플로우

     - 오버플로우 발생원인 : 선언된 변수타입보다 큰 값을 대입하게 되면 오버플로우가 발생

package com.eomcs.lang.ex05;

//# 산술 연산자 : 연산의 결과 타입
//
public class Exam0142 {
  public static void main(String[] args) {
    int x = Integer.MAX_VALUE; // 0x7fffffff = 약 +21억
    int y = Integer.MAX_VALUE; // 0x7fffffff = 약 +21억

    int r1 = x + y; // 0x7fffffff + 0x7fffffff = 0xfffffffe = -2
    System.out.println(r1); // int(4byte) + int(4byte) = int(4byte)

    long r2 = x + y;  // 0x7fffffff + 0x7fffffff = 0xfffffffe = -2
    System.out.println(r2); // int(4byte) + int(4byte) = int(4byte)
	
    // x+y의 값이 레지스터상에서 이미 오버플로우가 발생하였다.
    // r2의 크기는 8byte이지만, 레지스터에 있는 -2를 전달 받기 때문에 r2는 -2가 된다.

  2.4 부동소수점의 계산

     - 오버플로우

package com.eomcs.lang.ex05;

//# 산술 연산자 : 연산의 결과 타입
//
public class Exam0143 {
  public static void main(String[] args) {

    float f1 = 987.6543f;
    float f2 = 1.111111f;
    System.out.println(f1);
    System.out.println(f2);

    float r1 = f1 + f2;
    // f1과 f2에 들어 있는 값이 유효자릿수라 하더라도
    // 연산 결과가 유효자릿수가 아니라면 값이 잘리거나 반올림 된다.
    // => float과 float의 연산 결과는 float이기 때문이다.
    //      987.6543
    //    +   1.111111
    //   ---------------
    //      988.765411  <=== float의 유효자릿수인 7자리를 넘어간다.
    //      988.7654    <=== 유효자릿수를 넘어가는 수는 짤린다
    }
}

 

     - 자릿수 삭제

package com.eomcs.lang.ex05;

//# 산술 연산자 : 연산의 결과 타입
//
public class Exam0143 {
  public static void main(String[] args) {

    float f1 = 987.6543f;
    float f2 = 1.111111f;
    System.out.println(f1);
    System.out.println(f2);

    double r2 = f1 + f2;
    System.out.println(r2);
    // 기대값: 988.765411
    // 결과값: 988.765380859375
    // 기대한 결과가 나오지 않은 이유?
    // => float과 float의 연산 결과는 float이다.
    // => double 변수에 저장하기 전에 이미 float 값이 되면서 일부 값이 왜곡되었다.

    // 그런데 r1 변수와 달리 뒤에 이상한 숫자가 많이 붙는 이유는 무엇인가?
    // => IEEE 754의 이진수 변환 문제때문이다.
    // => 4바이트 float 부동소수점을 8바이트 double 부동소수점 변수에 저장할 때 
    //    왜곡된 값이 들어 갈 수 있다.
    // => float을 double 변수에 넣을 때 왜곡이 발생하기 때문에 
    //    가능한 double 변수로 값을 바꾼 다음에 연산을 수행하라.
    //    더 좋은 것은 처음부터 double 변수를 사용하라!

    // 다음과 같이 처음부터 double 변수를 사용하라!
    double d1 = 987.6543;
    double d2 = 1.111111;
    double r5 = d1 + d2; // = 988.765411
    System.out.println(r5);
    // 그럼에도 실제 출력해보면 맨 뒤에 극한의 작은 수가 붙는다.
    // 이유? IEEE 754 이진수 변환 문제이다. 고민하지 말라!
    // 어떻게 처리할 건데? 맨 뒤에 붙은 극한의 작은 수는 그냥 잘라 버린다.
  }
}

3. 관계연산자

  3.1 관계연산자의 기본종류

package com.eomcs.lang.ex05;

//# 관계 연산자(relational operators: <, <=, >, >=), 등위 연산자(equality operators: ==, !=)
//
public class Exam0210 {
  public static void main(String[] args) {
    int a = 10;
    int b = 20;

    // 비교의 결과는 true 또는 false이다.
    // 즉 boolean 값이다.
    boolean r1 = a < b; // true

    //    int r2 = a < b; // 컴파일 오류!

    System.out.println(a < b); // true
    System.out.println(a <= b); // true
    System.out.println(a > b); // false
    System.out.println(a >= b); // false
    System.out.println(a == b); // false
    System.out.println(a != b); // true

  }
}

  3.2 부동소수점의 비교 

     - 부동소수점의 비교 및 논리연산은 IEE-754변환(정규화 과정에서 발생)에 따라 극한의 소수값이 발생할 수 있다. 

package com.eomcs.lang.ex05;

public class Exam0220 {
  public static void main(String[] args) {
    double d1 = 987.6543;
    double d2 = 1.111111;
    System.out.println((d1 + d2) == 988.765411);
    // 결과는 false이다.
    // 이유?
    // - 부동소수점 값을 IEEE 754 명세에 따라 2진수로 바꿔 메모리에 담을 때
    //   정규화(소수점 이하의 수를 2진수로 바꾸는) 과정에서
    //   정수로 딱 떨어지지 않는 경우가 있다.
    //   즉 극한의 미세 소수점이 붙을 수 있다. 
    // - CPU나 OS, JVM의 문제가 아니다.
    // - IEEE 754 명세에 따라 부동소수점을 처리하는 모든 
    //   컴퓨터에서 발생하는 문제이다.
    // - 이런 부동소수점을 계산할 때 기대하는 값과 다른 값이 나올 수 있다.
    // - 또한 연산한 결과를 메모리에 담을 때도 정규화 과정에서
    //   극한의 미세 소수점이 붙을 수 있다.
    System.out.println(d1);
    System.out.println(d2);
    System.out.println(d1 + d2); 
    // 987.6543 + 1.111111 = 988.7654110000001
    }
}

 

     - 이를 해결하기 위해, 극한의 작은값은 삭제를 해준다. 

// 관계 연산자 : 부동소수점 비교 
package com.eomcs.lang.ex05;

public class Exam0220 {
  public static void main(String[] args) {
    double d1 = 987.6543;
    double d2 = 1.111111;
    System.out.println((d1 + d2) == 988.765411); //false
 
    double x = 234.765411;
    double y = 754.0;
    System.out.println((x + y) == 988.765411);  //true
  
    System.out.println((d1 + d2) == (x + y)); // false

    // 소수점 뒤에 붙은 극소수의 값을 무시하면 된다.
    // => JVM이 자동으로 처리하지 않는다.
    // => 다음과 같이 개발자가 직접 처리해야 한다.
    double EPSILON = 0.00001;
    System.out.println(Math.abs((d1 + d2) - (x + y)) < EPSILON);
  }
}

4. 논리연산자

  4.1 논리연산자의 기본종류

package com.eomcs.lang.ex05;

//# 논리 연산자 : &&, ||, !(not), ^(XOR; exclusive-OR)
//
public class Exam0310 {
  public static void main(String[] args) {
    // AND 연산자 
    // - 두 개의 논리 값이 모두 true일 때 결과가 true가 된다.
    System.out.println(true && true);
    System.out.println(true && false);
    System.out.println(false && true);
    System.out.println(false && false);

    System.out.println("-----------------------");

    // OR 연산자 
    // - 두 개의 논리 값 중 한 개라도 true이면 결과는 true가 된다.
    System.out.println(true || true);
    System.out.println(true || false);
    System.out.println(false || true);
    System.out.println(false || false);

    System.out.println("-----------------------");

    // NOT 연산자 
    // - true는 false로 false는 true로 바꾼다.
    System.out.println(!true);
    System.out.println(!false);

    System.out.println("-----------------------");

    // exclusive-OR(XOR)연산자 
    // - 배타적 비교 연산자라 부른다.
    // - 두 개의 값이 다를 때 true이다.
    System.out.println(true ^ true);
    System.out.println(false ^ false);
    System.out.println(true ^ false);
  }
}

  4.2 논리연산자 &&와 &

     - 두개를 사용하면 값을 비교하기 위해 사용한다.
     - 한개를 사용하면 논리값과 비트값을 비교하기 위해 사용한다.

package com.eomcs.lang.ex05;

//# 논리 연산자 : &, |
//
public class Exam0320 {
  public static void main(String[] args) {
	int a = 8; // 0b 0000 0000 0000 1000
    int b = 5; // 0b 0000 0000 0000 0101
    
	System.out.println(a | b); // 0b 1101
    System.out.println(a == 8 && b ==5); // true
    System.out.println(false & true); //false
    System.out.println(false && false); //false
  }
}

 

     - 한개를 사용하면 논리연산자 전체를 수행하지만, 두개를 사용하면 먼저나온 값에 따라 후행 조건이 결정된다.

package com.eomcs.lang.ex05;

//# 논리 연산자 : || vs |
//
public class Exam0340 {
  public static void main(String[] args) {
    boolean a = true;
    boolean b = false;
    boolean r = a || (b = true); 
    // a = true, b = false, r = true
    System.out.printf("a=%b, b=%b, r=%b\n", a, b, r);

    a = true;
    b = false;
    r = a | (b = true); 
    // a = true, b = true, r = true
    System.out.printf("a=%b, b=%b, r=%b\n", a, b, r);
  }
}

5. 비트연산자

  5.1 비트연산자의 기본종류

     - 논리연산자

package com.eomcs.lang.ex05;

//# 비트 연산자 & 활용: 그림의 한 픽셀에서 빨강 색을 제거하고 싶다.
//
public class Exam0354 {
  public static void main(String[] args) {
    int pixel = 0x003f4478; // 각 바이트의 값이 '00RRGGBB' 이라 가정하다.
    System.out.println(pixel & 0x0000ffff);
    // pixel = 00000000_00111111_01000100_01111000
    //       & 00000000_00000000_11111111_11111111
    //         00000000_00000000_01000100_01111000
    
    int pixel2 = 0x003f4478; // 각 바이트의 값이 '00RRGGBB' 이라 가정하다.
    System.out.println(pixel2 | 0x00000057);
    // pixel2 = 00000000_00111111_01000100_01111000
    //        | 00000000_00000000_00000000_01010111
    //          00000000_00111111_01000100_01111111
    
  }
}

 

     - 시프트연산자 

>> >>> <<
- 자바에서는 unsigned 타입이 없기 때문에 최상위 비트에 0으로 삽입하기 위해서 >>> 가 있다. 
package com.eomcs.lang.ex05;

//# 비트 이동 연산자 : >>, >>>, <<
//
public class Exam0410 {
  public static void main(String[] args) {
    // << 비트 이동 연산자 사용법
    // - 왼쪽으로 비트를 이동시킨다.
    // - 오른 쪽 빈자리는 0으로 채운다.
    // - 왼쪽 경계를 넘어간 비트는 자른다.
    //
    int i = 1;
    //      [00000000000000000000000000000001] = 1

    System.out.println(i << 1);
    //     0[0000000000000000000000000000001 ]
    //      [00000000000000000000000000000010] = 2

    System.out.println(i << 2);
    //    00[000000000000000000000000000001  ]
    //      [00000000000000000000000000000100] = 4

    System.out.println(i << 3);
    //   000[00000000000000000000000000001   ]
    //      [00000000000000000000000000001000] = 8

    System.out.println(i << 4);
    //  0000[0000000000000000000000000001    ]
    //      [00000000000000000000000000010000] = 16

    i = 11; // [00000000000000000000000000001011]
    System.out.println(i << 1); //   0[00000000000000000000000000010110] => 22
    System.out.println(i << 2); //  00[00000000000000000000000000101100] => 44
    System.out.println(i << 3); // 000[00000000000000000000000001011000] => 88

    // 왼쪽 이동
    // - 1비트 이동은 곱하기 2 한 것과 같은 효과를 준다.
    // - 값을 배수로 증가시킬 때 곱하기 연산을 하는 것 보다
    //   왼쪽 비트 이동 연산을 하는 것이 빠르기 때문에
    //   실무에서는 이 비트 이동 연산을 자주 사용한다.
    // - 비트 이동 => '2**이동비트'를 곱한 것과 같은 결과를 만든다.
  }
}

 

     - 시프트연산자 범위

- 시프트연산자의 최대 범위는 선언된 자료형의 크기와 동일하다 .
- 그 이상의 시프트는 (%)연산자로 나머지 만큼만 이동한다.
package com.eomcs.lang.ex05;

// # 비트 이동 연산자 : 비트 이동의 유효 범위
//
public class Exam0412 {
  public static void main(String[] args) {

    System.out.println(3 << 1);
    // 000000000 00000000 00000000 00000011 = 3
    // 0|000000000 00000000 00000000 00000011x = 비트이동
    // 000000000 00000000 00000000 000000110 = 6

    System.out.println(3 << 33); // 6
    System.out.println(3 << 65); // 6
    System.out.println(3 << 97); // 6
    }
}

 

     - 시프트연산자 효과

- 시프트 연산자는 2의 승수만큼 곱하고 나누는 효과를 준다.
- 산술연산은 연산레지스터를 경유하지만, 시프트연산은 바로 비트로 적용하여 속도가 빠르다.
- 연산자 우선순위는 낮기때문에 ()로 우선순위를 주어야 한다. 
- 최하단 비트가 1일 경우, 나머지는 버림을 한다 
  ex) //  0001 >> 2는 0100으로 바뀐다.  
package com.eomcs.lang.ex05;

//# 비트 이동 연산자 : >>, >>>, <<
//
public class Exam0421 {
  public static void main(String[] args) {
    // 음수일 경우,
    // - 빈자리는 1로 채운다.
    
    int i = 105; // [00000000000000000000000001101001]

    System.out.println(i); //                   => 105

    System.out.println(i >> 1);
    // [ 0000000000000000000000000110100]1
    // [00000000000000000000000000110100]       => 52
    //
    int i = -87; // [11111111111111111111111110101001]

    System.out.println(i); //                   => -87

    System.out.println(i >> 1);
    // [ 1111111111111111111111111010100]1
    // [11111111111111111111111111010100]1      => -44


    // 음수 값에 대해 오른쪽으로 비트 이동
    // => 2**n으로 나눈 것과 같다.
    // => 소수점이 있을 경우 그 수 보다 바로 밑 정수 값이 결과이다.
    // => 왼쪽 빈자리가 부호비트로 채워진다.
  }
}

6. 조건연산자

  6.1 조건연산자의 기본종류

package com.eomcs.lang.ex05;

// # 조건 연산자 => ? :
//
public class Exam0510 {
  public static void main(String[] args) {
    // 조건연산자
    // => 조건 ? 표현식1 : 표현식2
    // => 조건이 참이면 표현식1을 실행하고,
    // 조건이 거짓이면 표현식2를 실행한다.
    //
    int age = 20;

    // 조건연산자의 왼쪽은 변수이어야 한다.
    String a = "성년", b = "미성년";
	//case1. 표현식으로 받는경우
    String message = age > 18 ? a : b;
    System.out.printf("나이 %d는(은) %s이다.\n", age, message);
    
    //case2. 매개변수로 받는경우
	test(age > 18 ? "성년" : "미성년");
    static void test(String value) {
    System.out.println(value + " 입니다.");}
  }
}

 

     - 표현식과 문장의 관계

- 표현식(expression) : 작업을 수행한 후 결과를 리턴하는 문장이다.
- 문장(statement) : 작업을 수행시키는 명령이다.
- 문장과 표현식의 관계 : statement 중에서 결과를 리턴하는 statement를 expression이라 부른다.

  6.2 조건연산자을 사용할 수 없는경우

case 1.
int age = 20;
//    (age > 18) ? "성년" : "미성년";
// 조건 연산자를 수행한 후 그 결과를 받을 변수를 받드시 선언해야 한다.
// => 선언하지 않으면 문법 오류!

case 2.
int age = 20;
// age > 18 ? System.out.println("성인이다.") : System.out.println("미성년자이다.");
// 표현식은 반드시 값을 리턴해야 한다.

case 3.
int age = 20;
//    String str = age > 18 ? System.out.println("성인이다.") : System.out.println("미성년자이다.");
// 조건 연산자의 결과 값이 왼편의 변수 타입과 일치해야 한다.
// => 결과 값이 없으면 문법 오류!

7. 증감연산자

  7.1 증감연산자의 기본종류

package com.eomcs.lang.ex05;

//# 증감 연산자 : 후위(post-fix) 증가 연산자
//
public class Exam0610 {

  public static void main(String[] args) {
    int i = 2;
    //case1. 
    i++; 
    // int temp = i;
    // i = i + 1;
    // temp에 대한 호출이 없으면
    // temp는 가상메모리에서 삭제
    
    --i;
    // i = i - 1;
    // temp = i; 
    // temp는 가상메모리에서 삭제
    }
}

 

     - i++의 원리

- i++ : i 가 바로 증가하는 것이 아니라 레지스터에 i의 값을 임시로 저장하고, 레지스터의 i값을 사용하는 것이다.
- 이는 어셈블리어를 확인 하면 확인할 수 있다. 
package com.eomcs.lang.ex05;

//# 증감 연산자 : 후위(post-fix) 증가 연산자
//
public class Exam0610 {

  public static void main(String[] args) {
    int i = 2;
    i++; // i => 3
    System.out.println(i++); // 3
    // int temp = i; //<-- 임시 변수를 만들어 현재 i 값을 저장한다.
    // i = i + 1;
    // System.out.println(temp);
    System.out.println(i); // 5
  }
}

8. 할당연산자

  8.1 할당연산자의 종류

- 할당연산자는 대입과 연산을 합친 문법이다.
- 축약형이라고 생각하면된다.
package com.eomcs.lang.ex05;

//# 할당(배정,대입) 연산자  : +=  -=  *=  /=  %=  &=  |=  ^=  <<=  >>=  >>>=
//
public class Exam0710 {
  public static void main(String[] args) {
    int i = 2;

    i = i + 20;
    i += 20; // += 연산자를 사용하면 위의 코드를 축약할 수 있다.
    System.out.println(i);

    i = 2;
    i *= 5;
    System.out.println(i);

  }
}