본문 바로가기
Java/basic

자바 연산자, JDK 13+의 switch operator

by Ellery 2022. 10. 30.
목차
1. 자바의 연산자 종류(산술, 비트, 논리, 관계, instanceof, assignment, arrow, tenary operator)
2. 기존의 switch expression과 JDK 13+의 switch operator
3. 연산자 우선순위

1. 자바에서의 연산자

  • 산술연산자 + - * / , Infinity 혹은 NaN(not a number)
    Java에서 / 또는 % 연산자를 사용할 때 제수가 정수 0인 경우에는 RuntimeException의 하위 클래스인 ArithmeticException이 발생한다. 그런데 만약 제수가 정수 0이 아니라 실수 0.0f 혹은 0.0d인 경우에는 Infinity 혹은 NaN(not a number)가 반환된다.
    이 값에 추가적인 산술연산을 하더라도 Infinity 혹은 NaN으로 바뀐 이후에는 추가 연산을 해도 값이 바뀌지 않는다. 또한 이 값들은 비교연산을 할 수 없는 값으로서 비교연산 시에 무조건 false를 반환하게 된다.
    • 부동소수점 표준인 IEEE 754에서는 NaN이라는 값을 정의해 놓았고, 자바에서도 float, double 표준에 이를 구현해놓았다.
    • 나누기, 모듈러 연산 시에 Double.isInFinity(result) 혹은 Double.isNaN(result) 메서드로 체크할 수 있지만 처음부터 실수 나누기, 모듈러 연산을 안하도록 정수로 변환하고 연산하도록 하는게 좋을 것 같다.
  • 비트 연산자
    • ~, &, |, ^
    • >> : 비트 값들을 오른쪽으로 이동시키고, 왼쪽의 빈 공간을 양수의 경우 0, 음수는 1로 채운다.
    • <<: 왼쪽 비트가 끝이 잘리고 없어짐.
    • >>> (bitwise right shift with zero extension): 비트 값들을 이동시킨 후에 빈 공간은 무조건 0으로 채운다.
      • unsigned shift를 이용해서 두 수에 대하여 평균값을 구해볼 수 있다. (간지가 난다!)
      • XOR 배타논리합을 이용해서 집합 내 중복되지 않는 원소를 리턴하는 함수를 작성할 수 있다.
    int s = 20억, e = 21억
    int mid = s + (e - s) / 2;
    int mid = (s + e) >>> 1; // Bitwise right shift with zero extension 이건 간지용
    // 구글에 면접보거나 아마존에서 일할 때나 쓰자. 일반적인 개발자들이 잘 모름... 오픈소스에서는 종종 보인다
    // 5 ^ 0 = 5
    // 5 ^ 5 = 0;
    // 101 ^ 000 = 101
    
    // {5,2,4,1,2,4,5} 중에 중복되지 않는 원소를 리턴하는 함수 작성하기
    // -> 모든 원소들을 XOR연산 하면 남는 값이 중복되지 않는 원소다.
    private int solution(int[] nums) {
        int result = 0;
        for(int num: nums) result ^= num;
        return result;
    }
  • 논리 연산자
    • 논리 연산 and or 사용시 short circuit 방식으로 이후 연산이 필요없을 경우 스킵해서 연산량을 줄인다
    • &&, ||, !
  • 관계 연산자
    • <, ≤,>, ≥
    • 피연산자 중 하나가 boxing 되어 있다면 unboxing된다.
    • 피연산자 중 하나가 byte, short, char이면 int로 변환되어 연산됨
    • 더 작은 범위의 유형에서 더 큰 범위 유형으로 변환됨
  • instanceof
    • 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아야할 때 instanceof 연산자를 사용한다.
    • 조상타입의 참조변수로 자손타입의 인스턴스를 참조할수도 있기 때문에, 참조변수 타입과 인스턴스 타입은 무조건 일치하지 않는다. (BMW → Car → Object) <참조방향>
    • 어떤 타입에 대한 instanceof 연산이 true이면 그 타입으로 형변환이 가능하다
    ( Object reference variable ) instanceof  (class/interface type)
    
    void doWork(Car c) {
        if(c instanceof BMW) {
            BMW c = (BMW)c
            c.runSoftly();
        } else if(c instanceof Kickboard) {
            Kickboard c = (Kickboard) c;
            c.kickQuickly();
        }
    }
  • assignment(=) operator
    • =, +=, -=, *=, /=, %=, &=, |=, ^=, <≤, >≥, >>≥(부호 상관없이 오른쪽 시프트)
    • 대입 연산자에서 rvalue는 변수, 식, 상수 모두 가능하지만, lvalue는 반드시 변수처럼 값을 변경할 수 있는 것이어야 한다.
    x = y = 3; // 연산 순서 1. y = 3, 2. x = y;
  • 화살표(->) 연산자(JDK 8+)
    • 람다 표현식으로 익명함수, 또는 함수 인터페이스를 구현하는 익명 클래스의 인스턴스를 정의함
    • 메서드에서 이름과 반환타입을 제거하고 선언부와 블록 사이에 → 를 추가한다
    a -> a + 1              // a lambda that adds one to its argument
    a -> { return a + 1; }  // an equivalent lambda using a block.
    
    // lambda 안씀
    JButton btn = new JButton("My Button");
    btn.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("Button was pressed");
        }
    });
    
    // 람다씀
    JButton btn = new JButton("My Button");
    btn.addActionListener(e -> {
        System.out.println("Button was pressed");
    });
  • 삼항 연산자 expr ? a : b

2. switch operator, expression(JDK 13+)

기존 switch expression이 너무 장황하고, 에러 발생시 디버깅이 어렵고, break 체크하기가 힘든 점이 있었음

기존의 switch expression
- switch에 들어가는 표현식은 래퍼 타입이 허용되지만 case엔는 허용하지 않는다.
- 케이스 값은 리터럴 또는 상수여야 한다.
- 각 케이스는 고유해야 하고, 중복 케이스가 있으면 컴파일 오류가 발생
- 각 케이스에는 선택적으로 break문이 있음. 어떠한 케이스에도 break문이 없으면 break에 도달할 때까지 다음 case 실행이 됨.
- 모든 케이스에 해당되지 않으면 default가 실행됨(fall-through) 폴 쓰루라고 함. 기본 케이스에는 break가 필요없음

switch(this.type) {
	case MOTORCYCLE:
		a = 0.05;
		break;
	case MINIVAN:
		a = 0.1;
		break;
	default:
		assert(false): "Unrecongnized vehicle type: " + this.type;
		break;
}
// switch 문 작성시에 default case에 assert문으로 확인을 해주자
- case → 인 경우 branch fall-through가 아님(아래 case까지 내려가지 않고 거기서 끝남)
- break문 대신 yield를 사용함(switch operator에서 yield로 받은 산출값을 반환함)

switch(v){
    case a -> ret1;
    case b -> ret2;
    ...
    default -> throw new IllegalStateException("에러에러");
};

switch(expression){
    case expression:
        expression;
        yield expression;
    ...
    default:
        expression;
        yield expression;
};

int a = 1;
int b = switch(a) {
    case 1 -> {
        System.out.println("case1");
        yield 10;
    }
    case 2 -> {
        System.out.println("case2");
        yield 11;
    }
}

3. 연산자 우선순위

참고: https://www.cs.bilkent.edu.tr/~guvenir/courses/CS101/op_precedence.html

- 백기선님의 자바 온라인 스터디 https://github.com/whiteship/live-study 주제를 정리한 내용입니다.

참고
- 자바의 정석 2판
- https://www.cs.bilkent.edu.tr/~guvenir/courses/CS101/op_precedence.html
-https://www.baeldung.com/java-not-a-number