[Live Study] #3 연산자

ohzzi·2021년 1월 14일
0

Live Study

목록 보기
3/13
post-thumbnail

목표

자바가 제공하는 다양한 연산자를 학습하세요.

학습할 것

  • 산술 연산자
  • 비트 연산자
  • 관계 연산자
  • 논리 연산자
  • instanceof
  • assignment(=) operator
  • 화살표(->) 연산자
  • 3항 연산자
  • 연산자 우선 순위
  • (optional) Java 13. switch 연산자

산술 연산자

일반적으로 우리가 산술 연산을 사칙연산(덧셈, 뺄셈, 곱셈, 나눗셈)이라고 하지만, 자바에는 (또한 자바를 비롯한 여러 프로그래밍 언어에는) 사칙연산에 추가적으로 하나의 산술 연산자가 더 있다.

자바의 산술 연산자는 +(덧셈), -(뺄셈), *(곱셈), /(나눗셈), %(나머지), 총 5개가 존재한다.

public class Operands {
    public static void main(String[] args) {
        int a = 10;
        int b = 3;

        System.out.println(a+b); // 10 + 3 = 13 출력
        System.out.println(a-b); // 10 - 3 = 7 출력
        System.out.println(a*b); // 10 * 3 = 30 출력
        System.out.println(a/b); // 10 / 3 = 3 출력
        System.out.println(a%b); // 10 = 3 * 3 + 1(나머지) 이므로 1 출력

        System.out.println((double)a/b); // 10 / 3 = 3.33333.... 출력
    }
}

여기서 10 / 3을 했을 때 처음에는 3이 나오고 뒤에는 3.3333...이 나오는 것은, 처음의 연산은 정수 연산이기 때문에 10을 3으로 나누어도 몫인 3만 나오기 때문이고, 10을 실수형으로 형변환 해주면 소수점까지 계산하기 때문이다.

즉, 정수형을 가지고 계산하면 정수형 범위 내에서만 값이 출력되고, 실수형을 가지고 계산하면 실수형 결과값을 출력할 수 있다. 또한 산술 연산은 이항 연산이기 때문에 반드시 연산자를 기준으로 좌우측에 모두 데이터가 존재해야 한다.

비트 연산자

자바에서 모든 숫자는 결국 이진수로 이루어진다. 그리고 이 이진수들은 비트 연산자를 통해 비트 단위로 연산할 수 있다. 비트 연산에는 비트 이동 연산자와 비트 논리 연산자가 존재한다.

종류연산자연산
비트 이동x >> yx의 비트를 오른쪽으로 y만큼 이동시킨다. 빈 공간은 x의 부호 비트로 채운다.
비트 이동x >>> yx의 비트를 오른쪽으로 y만큼 이동시킨다. 빈 공간은 0으로 채운다.
비트 이동x << yx의 비트를 왼쪽으로 y만큼 이동시킨다. 빈 공간은 x의 부호 비트로 채운다.
비트 이동x <<< yx의 비트를 왼쪽으로 y만큼 이동시킨다. 빈 공간은 0으로 채운다.
비트 논리& (AND)두 비트 모두가 1일 경우 1
비트 논리| (OR)두 비트 중 하나라도 1일 경우 1
비트 논리^ (XOR)두 비트가 서로 다를 경우 1
비트 논리~ (NOT)비트를 반전(1의 보수)

먼저 비트 이동 연산에 대해서 설명하자면, 연산자의 방향으로 연산자 뒤에 오는 숫자 만큼 비트를 이동시킨다. 이때 >>와 >>>의 차이는, 비트를 이동시킴으로써 생기는 빈 공간에 어떤 숫자를 채울 것인지다. 양수일때는 어차피 부호 비트가 0이기 때문에 차이가 없지만, 음수일때 차이가 있다.

예를 들어, byte형 정수 -2은 1111 1110 인데,
11111110 >> 1 => []1111111(0) => 11111111 = -1
11111110 >>> 1 => []1111111(0) => 01111111 = 127
이 된다.

이는 비트를 전체적으로 1칸 옮겼을때 생기는 빈 자리에 부호 비트를 채우는지 0을 채우는지가 다르기 때문이다.

비트 이동 연산은 또한 곱셈 및 나눗셈에도 쓸 수 있다. 한 칸 오른쪽으로 옮길때마다 2가 나누어지고, 한 칸 왼쪽으로 옮길 때마다 2가 곱해진다.

비트 논리 연산자들은 대상이 boolean 타입일 때는 그냥 논리 연산자로 쓰이지만, 대상이 정수형일 경우에는 비트 논리 연산을 한다. 예를 들어, byte형 정수 2와 6을 비트 연산 하면,

public class BitOperands {

    public static void main(String[] args) {
        byte a = 2; // 00000010
        byte b = 6; // 00000110
        System.out.println(a & b); // 00000010이므로 2
        System.out.println(a | b); // 00000110이므로 6
        System.out.println(a ^ b); // 00000100이므로 4
        System.out.println(~a); // 11111101이므로 -3
    }
}

위와 같이 계산된다.

생각해보면 비트 연산을 사용한 적은 손에 꼽을 수 있을 것 같다. 그만큼 실제 코드를 짤 때 많이 쓰지 않는 연산이다. 다만, 코딩 테스트 문제를 풀 때 비트 연산을 사용하면 은근히 간단하게 풀리는 문제들이 더러 있다.

관계 연산자

연산자연산
>왼쪽 항이 크면 true, 두 항이 같거나 오른쪽 항이 크면 false 반환
>=왼쪽 항이 오른쪽 항보다 크거나 같으면 true, 오른쪽 항이 크면 false 반환
<두 항이 같거나 왼쪽 항이 크면 false, 오른쪽 항이 크면 true 반환
<=왼쪽 항이 크면 false, 두 항이 같거나 오른쪽 항이 크면 true 반환
==두 항이 같으면 true, 다르면 false 반환
!=두 항이 같으면 false, 다르면 true 반환

관계 연산자는 우리가 수학시간에 배웠던 등호와 부등호를 생각하면 편하다. 대부분의 관계 연산자는 수학의 부등호 기호와 같은데, 다만 수학의 등호는 '=' 인데 반해 프로그래밍 언어에서의 '='은 대입 연산자라는 점이 다르다. 등식을 나타내기 위해서는 =를 두개 사용해서 '=='로 쓴다. 또한 같지 않다(not equal)을 나타내는 연산자는 '!='로 표시한다

논리 연산자

연산자연산
&& (AND)두 항이 모두 true일 때만 true반환, 하나라도 false면 false 반환
|| (OR)두 항 중 하나라도 true면 true 반환

논리 연산자는 연산 결과를 boolean 타입으로 반환하며, true 또는 false 값만 가진다. 앞서 설명한 비트 연산에서 비트 연산자는 boolean 타입을 연산할 때 논리 연산자로 작동한다고 설명했는데, 비트 논리 연산과 논리 연산에는 차이점이 있다.

논리 연산이 필요한 상황에서는 무조건 논리 연산자를 사용하는 것이 이득이다. 비트 논리 연산자를 사용한 연산에서는 무조건 피연산자의 앞 뒤를 모두 체크한다. 반면 논리 연산자를 사용한 경우 앞쪽 피연산자에서 true나 false가 결정된다면 뒤의 과정을 실행하지 않는다. 이를 단락 회로 평가라고 한다

public class LogicalOperands() {
    public static void main(String args[]) {
        boolean a = true;
        boolean b = false;
    
        System.out.println(a || b); // true
        System.out.println(a | b); // true
        // a가 true이기 때문에 윗 줄은 a만 체크하고 바로 true를 출력
    }
}

때문에, && 연산 시에는 false 값이 앞에 오는것이 더 빠르고(하나라도 false면 종료하므로), || 연산 시에는 true 값이 앞에 오는것이 더 빠르다(하나라도 true면 종료하므로).

단, 논리 연산 안에 ++와 같은 다른 연산이 들어갈 경우, 논리 연산자를 사용했을 때 단락 회로 평가에 의해 연산이 이루어지지 않고 종료될 수 있으므로 주의가 필요하다.

instanceof

instanceof는 객체의 타입을 확인하는 연산자다. 형변환 가능하면 true, 불가능하면 false를 반환한다. 주로 사용되는 용도는 상속관계에서 부모 자식을 판단하는데 이용한다.

class Parent {}
class Child extends Parent {}

public class Test {
    public static void main(String[] args) {
        Parent parent = new Parent();
        Child child = new Child();

        System.out.println(parent instanceof Parent); // true
        System.out.println(parent instanceof Child); // false
        System.out.println(child instanceof Parent); // true
        System.out.println(child instanceof Child); // true
    }
}

Child는 Parent를 상속하기 때문에 Child가 Parent의 자식 클래스가 된다. parent instanceof Parent와 child instanceof Child는 각각이 해당 클래스의 인스턴스이기 때문에 true가 출력된다. 여기서 두번째 줄 parent instanceof Child만 false가 나오는 이유는, Child가 Parent를 상속받은 관계이므로 Parent의 인스턴스인 parent가 Child로 형변환 될 수 없기 때문이다.

만약 instanceof의 피연산자로 레퍼런스 타입이 아닌 프리미티브 타입이 올 경우에는 true / false가 아니라 에러가 발생한다.

assignment(=) operator

assignment operator는 우리 말로 하면 대입 연산자다. 수학의 등호 기호 ('=')로 나타내며, 오른쪽 항의 값을 왼쪽 항의 변수에 대입함을 의미한다. 수학에서는 왼쪽 항과 오른쪽 항이 같다는 것을 의미하지만 앞서 말했듯이 프로그래밍 언어에서는 '=='에 해당한다.

이때 오른쪽 항에 또다른 연산자가 존재하면 해당 연산을 하고 대입 연산을 하며, 대입 연산자의 앞에 다른 연산자가 붙으면, 왼쪽 항의 변수에 오른쪽 항의 값을 대입 연산자 앞에 붙은 연산자로 연산해서 대입한다.

public class Test {
    public static void main(String[] args) {
        int a = 10;
        int b = 2;
        System.out.println(a); // a에 정수 리터럴 10이 대입되었으므로 10 출력
        System.out.println(a += b); // a = a + b를 의미하므로 12 출력
        a = a >> 1; // a에 a를 오른쪽으로 1만큼 비트 연산한 값을 대입
        System.out.println(a); // 6 출력
        a = 20;
        System.out.println(a); // a에 20을 새로 대입했으므로 20 출력
    }
}

화살표(->) 연산자

화살표 연산자는 자바 8부터 도입된 람다식에서 사용된다. 람다에 대한 자세한 설명은 이후의 스터디에 람다식이 있으므로 생략한다. 화살표 연산자의 왼쪽에는 매개변수가 오고, 오른쪽에 매개변수를 받아서 처리하는 로직이 오게 된다.

public class Test {
    public static void main(String[] args) {
        // 기존 자바 문법
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("기존 자바 문법을 이용한 쓰레드");
            }
        }).start();

        // 람다식을 사용한 문법
        new Thread(() -> {
            System.out.println("람다를 사용한 쓰레드");
       }).start();
    }
}

3항 연산자

3항 연산자는 말 그대로 항이 3개여서 3항 연산자인데, if문으로 인해 길어지는 코드를 짧게 줄이는데 사용된다. 기본적인 형식은

(조건) ? (조건이 참일때의 로직) : (조건이 거짓일때의 로직)의 형태다.

다음은 같은 로직을 각각 if문과 3항 연산자로 쓴 예시다.

public class Test {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;

        // if else 문 사용
        if (a > 0) {
            System.out.println(a);
        }
        else {
            System.out.println(b);
        }

        // 3항 연산자 사용
        System.out.println(a > 0 ? a : b);
    }
}

두 식 모두 결과값은 10이 출력되지만, 코드의 길이가 달라진다. 다만 3항 연산자를 사용해서 코드의 길이가 줄어들더라도, 실행 시간은 달라지지 않는다는 것에 주의해야 한다.

3항 연산자를 적절히 사용하면 가독성을 높일 수 있지만, 복잡한 코드에 사용하면 오히려 가독성을 해칠 수 있기 때문에, 3항 연산자를 기피하는 사람들이 많다. 나 역시도 오히려 if else문에 비해서 가독성이 떨어진다고 느낄때가 많아서 잘 사용하지 않는 편이다.

연산자 우선 순위

수학에서 곱셈, 나눗셈이 덧셈, 뺄셈보다 먼저 계산되듯이, 자바의 연산자들에도 우선순위가 있다. 이 우선순위를 고려하지 않고 코드를 짜게 되면 결과가 매우 달라지게 된다. 연산자들의 우선 순위는 다음 표와 같다.

우선순위연산자연산자 내용
1(), []괄호, 대괄호
2!, ~, ++, --부정, 증감 연산자
3*, /, %곱셈 / 나눗셈 연산자
4+, -덧셈, 뺄셈 연산자
5<<, <<<, >>, >>>비트 이동 연산자
6<, <=, >, >=관계 연산자
7==, !=관계 연산자
8&비트 AND
9^비트 XOR
10|비트 OR
11&&논리 연산자 AND
12||논리 연산자 OR
13?:조건 연산자
14=, +=, -=, /=, <<=, >>=, &=, ^=, ~=대입 연산자

(optional) Java 13. switch 연산자

자바에는 다른 프로그래밍 언어와 마찬가지로 여러 조건에 따라 다른 로직을 처리하는 switch ~ case 구문이 존재한다. 조건 분기가 많을 때 switch문을 사용하면 코드의 길이를 줄이고 가독성을 높일 수 있다. 전통적인 방법으로는 다음과 같이 사용한다.

public class Test {
    public static void main(String[] args) {
        int a = 1;
        switch (a) {
            case 0:
                System.out.println("zero");
                break;
            case 1:
                System.out.println("one");
                break;
            case 2:
            case 3:
                System.out.println("two or three");
                break;
            default:
                System.out.println("others");
                break;
        }
    }
}

switch문에는 조건을 판단할 변수를 넣고, 그 아래에 case문과 break를 사용해서 조건에 맞는 로직을 작성한다. (break를 안써주면 다음 case로 넘어간다.)

자바 12에 들어와서는 각각의 case 안에 화살표를 사용해서 간단하게 표현할 수 있다.

public class Test {
    public static void main(String[] args) {
        int a = 1;
        switch (a) {
            case 0 -> System.out.println("zero");
            case 1 -> System.out.println("one");
            case 2, 3 -> System.out.println("two");
            default -> System.out.println("others");
        }
    }
}

또한 switch문에서 결과값을 반환할 수도 있는데, 전통적인 방법을 사용할때는 오류가 발생할 수 있다. 때문에 자바 12에서는 break로 값을 반환 가능하다.

public class Test {
    public static void main(String[] args) {
        int a = 1;
        String result = switch (a) {
            case 0:
                break "zero";
            case 1:
                break "one";
            case 2:
            case 3:
                break "two or three";
            default:
                break "others";
        }
        System.out.println(result); // one 출력
    }
}

그리고 자바 13에 들어와서는, 이 break로 값을 반환하는 구문이 yield로 바뀌었다.

public class Test {
    public static void main(String[] args) {
        int a = 1;
        String result = switch (a) {
            case 0:
                yield "zero";
            case 1:
                yield "one";
            case 2:
            case 3:
                yield "two or three";
            default:
                yield "others";
        }
        System.out.println(result); // one 출력
    }
}

참고 자료
https://blog.naver.com/kgw1988/221478447511
https://blog.naver.com/PostView.nhn?blogId=kgw1988&logNo=221678438564&parentCategoryNo=&categoryNo=50&viewDate=&isShowPopularPosts=true&from=search

profile
배울 것이 많은 초보 개발자 입니다!

0개의 댓글