연산자 (Operator)


산술연산자

  • Binary Operator(이항)
  • 덧셈 (Addition, + )
    • 피연산자 중 하나가 문자열이면 다른 피연산자는 문자열로 변환되어 수행됨
      System.out.println("Total: " + 3 + 4);  // Total: 34
      System.out.println("Total: " + (3 + 4)); // Total: 7
    
  • 뺄셈 (Subtraction, - )
  • 곱셈 (Multiplication, * )
  • 나눗셈 (Division, / )
    • 결과는 integer, 값을 잃을 수도 있다.
    • 0으로 나누게 되면 ArithmeticException 발생
      7 / 3     // 2
      7 / 3.0f  // 2.33333f
      7 / 0     // ArithmeticException!!
      7 / 0.0   // positive infinity
      0.0/0.0   // NaN
    
  • 나머지 연산 (Modulo, %)
    • 한 숫자를 다른 숫자로 나눈 후 나눗셈의 나머지 또는 부호있는 나머지를 반환
      7 % 3     // 1
      4.3 % 2.1 // 0.1
      4 % 0     // ArithmeticException !!
      4 % 0.0   // NaN
    

비트연산자 (bitwise operator) & Shift Operators

  • Binary Operator(이항)
  • bit 단위로 논리 연산을 할 때 사용하는 연산자, 비트 단위로 전체 비트를 왼쪽이나 오른쪽으로 이동시킬 때도 사용함.
  • floating-point, boolean, array, object 는 비트 연산자를 사용할 수 없음
  • 결과는 int값이나 연산자 중 하나가 long이면 결과는 long 이다.
  • ~ (비트 NOT 연산)
    • 비트를 1이면 0으로, 0이면 1로 반전시킴
      byte b = ~12;  // ~00001100 => 11110011 ( or -13 decimal)
    
  • & (비트 AND 연산)
    • 대응되는 비트가 모두 1이면 1을 반환
      10 & 7         // 00001010 & 00000111 => 00000010 (or 2)
    
  • | (비트 OR 연산)
    • 대응되는 비트 중에서 하나라도 1이면 1 반환
      10 | 7      // 00001010 | 00000111 => 00001111 (or 15)
    
  • ^ (비트 XOR 연산)
    • 대응되는 비트가 서로 다르면 1 반환
      10 ^ 7      // 00001010 ^ 00000111 => 00001101 (or 13)
    
  • << (Left Shift 연산)

    💡 x « n 은 x * 2^n 의 결과와 같다.

    • 지정한 수만큼 비트들을 전부 왼쪽으로 이동시킴
      10 << 1       // 0b00001010 << 1 = 00010100 = 20 = 10*2
      7 << 3        // 0b00000111 << 3 = 00111000 = 56 = 7*8
      -1 << 2       // 0xFFFFFFFF << 2 = 0xFFFFFFFC = -4 = -1*4
                    // 0xFFFF_FFFC == 0b1111_1111_1111_1111_1111_1111_1111_1100
    
  • >> (Right Shift 연산)

    💡 x » n 은 x / 2^n 의 결과와 같다.

    • 부호를 유지하면서 지정한 수만큼 비트를 전부 오른쪽으로 이동시킴
      10 >> 1       // 00001010 >> 1 = 00000101 = 5 = 10/2
      27 >> 3       // 00011011 >> 3 = 00000011 = 3 = 27/8
      -50 >> 2      // 11001110 >> 2 = 11110011 = -13 != -50/4
    

관계연산자

  • 개념
    • 비교 연산자라고도 함
    • 두개의 변수 또는 리터럴을 비교하는 데 사용되는 연산자
    • 연산결과는 true 또는 false
    • 이항연산자이므로 비교하는 피연산자의 자료형이 서로 다를 경우에는 자료형의 범위가 큰 쪽으로 형변환하여 피연산자의 타입을 일치시킨 후 비교한다.
    1. 대소비교 연산자

       > : 왼쪽 항이 크면 true, 아니면 false 반환
       < : 왼쪽 항이 작으면 true, 아니면 false 반환
       >= : 왼쪽 항이 오른쪽 항보다 크거나 같으면 true, 아니면 false 반환
      
    2. 등가비교 연산자

       == : 두개 항의 값이 같으면 true, 아니면 false 반환
       != : 두개 항이 다르면 true, 아니면 false 반환	
      

논리연산자

  • 조건문과 반복문에서 조건식 간의 결합에 사용
    • 피연산자로 boolean형 또는 boolean형 값을 결과로 하는 조건식만 허용
  • &&|| 연산보다 우선순위가 높으므로 한 조건식에 같이 사용할 때는 괄호를 사용하여 우선순위를 명확히 해 줘야 한다.
  • 효율적인 연산을 함
    • || 연산의 경우 어느 한 쪽만 true여도 전체 연산결과가 true이므로 좌측의 피연산자가 true이면 우측의 피연산자 값은 검사하지 않는다.
    • && 연산의 경우 어느 한쪽만 false여도 전체 연산결과가 false이므로 좌측의 피연산자가 false이면 우측의 피연산자 값은 검사하지 않는다.
    • 연산 속도를 위해 단락회로평가(short circuit evaluation)를 고려 하여 코딩하는 것이 좋음.
      • & 또는 | 을 사용한다면 양쪽 다 평가한다.
&& (논리곱) :  항이 모두 true인 경우에만 true, 아니면 false 반환
|| (논리합) :    하나의 항이 true이면 결과 값은 true, 아니면 flase 반환
  • 논리부정연산자 (단항연산자)
! (부정) : true를 false로 바꾸고, false인 경우 true로 변경

instanceof Operator

참조변수가 참조하는 인스턴스의 실제 타입을 체크하는데 사용하는 연산자이다.

연산결과는 true 또는 false이다. true이면 해당 타입으로 형변환이 가능하다.

"string" instanceof String      // True (all strings are instances of String)
"" instanceof Object            // True (strings are also instances of Object)
null instanceof String          // False (null is never an instance of anything)
Object o = new int[] {1,2,3};
o instanceof int[]               // True (the array value is an int array)
o instanceof byte[]              // False (the array value is not a byte array)
o instanceof Object              // True (all arrays are instances of Object)

다형성을 통해 조상타입의 참조변수로 자손타입의 인스턴스를 가리킬 수 있게 되었기 때문에 참조변수의 타입과 참조변수가 가리키는 인스턴스의 타입이 항상 같지는 않는다.

따라서 참조변수가 가르키는 인스턴스 타입이 어떤 것인지 확인하기 위해서 instanceof 연산자를 사용하는 것

// Use instanceof to make sure that it is safe to cast an object
if (object instanceof Test) {    
    Test t = (Test) object
}

assignment operator

변수에 값 또는 수식의 연산결과를 저장(in 메모리)하는데 사용한다. 왼쪽에는 반드시 변수가 위치해야 하며, 오른쪽에는 리터럴이나 변수 또는 수식이 올 수 있다.

연산 진행 방향이 오른쪽에서 왼쪽 (←) 이다. (right-associative)

  • a=b=ca=(b=c)

기본적인 assignment operator 는 = 이다. (equality operator 인 == 와 혼동 주의)

또한 다른 연산자와 결합하여 op= 와 같은 방식으로 사용이 가능하다.

i = i + 3   => i += 3
i *= 10 + j => i = i * (10 + j)
...
+= -= *= /= %=    // Arithmetic operators plus assignment
&= |= ^=          // Bitwise operators plus assignment
<<= >>= >>>=      // Shift operators plus assignment
  • x +=2x = x+2 는 거의 같다, 다만 op= 형식을 쓸 때 왼쪽피연산자가 한번만 evaluated 된다.

Lambda expression (→)

람다 표현식은 실행 가능한 자바 코드의 anonymous collection 으로, 메서드와 비슷하지만 이름이 필요하지 않고 method body에서 바로 구현이 가능한 표현식이다.

TestSomething test = () -> System.out.println("Hello, Lamda");

자바는 객체지향 언어이기 때문에 위 람다식을 변수에 할당 가능하고, 메서드의 파라미터로 전달 및 리턴타입으로 리턴할 수 있다. (람다식 자체를 리턴 또한 가능하다.)

표현 방법

  • (인자 리스트) → { body }

인자리스트

  • 인자 리스트는 없을 수도 있고, 한개 또는 여러개 일수도 있다.
  • 인자의 타입은 생략 가능, 컴파일러가 infer(추론) 하지만 명시할 수도 있다.
    • (String str, Integer one)
1. 인자가 없는 경우: () -> { body } // 구현해야 할 추상메서드가 파라미터를 아무것도 받지 않을 때	
2. 인자가 한개인 경우: (one) or one -> { body }
3. 인자가 여러개인 경우: (one, two) -> { body } 

바디

  • 화살표 오른쪽에 함수 내용을 구현한다.
  • 코드가 한줄인 경우 {} 를 생략 가능하고, return 도 생략 가능하다.
  • 코드가 여러 줄인 경우 {} 를 사용하여 묶는다.

특징

💡 final 또는 effective final 인 경우에만 로컬 변수 참조 가능 람다는 람다를 감싸는 별도의 scope 이 존재하여 쉐도잉을 하지 않는다.

  • 로컬변수 참조 (local variable capture) 란

      private void run() {
          final int baseNumber = 25;
            
          // 로컬클래스
          class LocalClass {                      
              void printBaseNumber() {
                  System.out.println(baseNumber);  // 로컬변수 참조
              }
          }
          // 익명클래스
          Consumer<Integer> integerConsumer = (i) -> {
              System.out.println(baseNumber)       // 로컬 변수 참조
          };
      }
    
  • 람다의 로컬변수 참조

    람다 표현식을 사용할 때 local variable capture를 하려면 그 변수가 final이거나 effective final인 경우에만 참조할 수 있다.

    • effective final

      자바 8부터 지원하는 기능으로 사실상 final인 변수이다. final 키워드를 사용하지 않은 변수를 익명클래스 구현체 또는 람다에서 참조할 수 있다.

      private void run() {	
          int baseNumber = 25;                    // effective final
                    
          IntConsumer printInt = (i) -> {
              System.out.println(i + baseNumber); // 참조 가능
          }
            
          // 컴파일 에러 발생
          IntConsumer printNotFinal = (~~baseNumber~~) -> {   // Variable 'baseNumber' 
              System.out.println(baseNumber)                  // is already defined 
          };                                                  // in the scope
      }
    

    위 에러가 발생하는 이유는 다음과 같다.

    💡 람다는 run() 메소드와 scope이 같음 (쉐도잉이 적용 안됨)
    → 같은 scope에서는 동일한 변수명을 사용할 수 없음 (concurrency 문제 발생 할 수있음)
    → 컴파일에러 발생

    • 쉐도잉이란 ?

        private void run() {	
            // ①
            int baseNumber = 25;
                  
            // 로컬클래스
            class LocalClass {                      
                void printBaseNumber() {
                    // ②
                    int baseNumber = 12;
                    System.out.println(baseNumber);    // 12 출력 
                }
            }
            // 익명클래스
            Consumer<Integer> integerConsumer = (i) -> {
                int baseNumber = 12;
                System.out.println(baseNumber)        // 12 출력
            };
        }
      

      위 코드에서 로컬클래스와 익명클래스는 run() 메소드 안에 있지만 run 메소드 scope과 다른 scope에 있어서 ① baseNumber 가 ② baseNumber로 가려져서 12가 출력이 된다. 이 것을 쉐도잉 이라 한다.

Conditional operator

삼항연산자는 세 개의 피연산자를 필요로 하기 때문에 삼항 연산자라 불린다.

if문 으로 바꿔 쓸 수 있으며 간단한 if문을 삼항 연산자로 사용하면 코드를 보다 간단히 할 수 있다.

표현방법

  • 조건식 ? 피연산자1 : 피연산자2

      int max = (x > y) ? : x : y;
      String name = (name != null) ? name : "unknown"
    

    조건식의 연산 결과가 true이면 결과가 피연산자1, false 이면 피연산자2가 결과값이 된다.

    피연산자들에는 주로 값이 오지만 경우에 따라 연산식이 올 수도 있다.

  • if문 ↔ 삼항연산자

// if문 사용 
if (x > 0 ) {
    result = x;
} else {
    result = -x;
}

// 삼항연산자 사용
result = (x > 0 ) ? x : -x;

연산 진행방향

3 * 4 * 5   // 연산 진행방향이 왼쪽에서 오른쪽인 경우 ① (3 * 4) ② 12 * 5

x = y = 3   // 연산 진행방향이 오른쪽에서 왼쪽인 경우 ① y = 3   ② x = y
  1. 산술 > 비교 > 논리 > 대입 (대입은 제일 마지막에 수행)
  2. 단항 (1) > 이항(2) > 삼항(3) (단항 연산자의 우선순위가 이항 연산자보다 높다)
  3. 단항 연산자와 대입 연산자를 제외한 모든 연산의 진행방향은 왼쪽에서 오른쪽이다.
1. -x + 3    // 단항 연산자가 이항 연산자보다 우선순위가 높음 -> x의 부호를 바꾼 다음 덧셈 수행
            // - 는 뺄셈 연산자가 아니라 부호 연산자임.  

연산자 우선순위