순간을 성실히, 화려함보단 꾸준함을

3주차 과제: 연산자 본문

백기선님과 함께 하는 자바 스터디

3주차 과제: 연산자

폭발토끼 2021. 5. 16. 13:22

목표


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

학습할 것

1) 산술 연산자
2) 비트 연산자
3) 관계 연산자
4) 논리 연산자
5) instanceof
6) assignment(=) operator
7) 화살표(->) 연산자
8) 3항 연산자
9) 연산자 우선 순위
10) (optional) Java 13,switch 연산자


연산(operations) : 프로그램에서 데이터를 처리하여 결과를 산출 하는 것
연산자(operator) : 연산에 사용되는 기호
피연산자(operand) : 연산의 대상이 되는 데이터

연산자 종류연산자
산술+,-,*,/,%
부호+,-
문자열+문자열
대입=,+=,-=,*=,/=,%=,&=,
^=,|=,<<=,>>=,>>>=
 여러타입
증감++,--
비교(관계)==,~=,>,<,>=,<=,instanceofboolean
논리!,&,|,&&,||boolean
조건(조건식) ? A : B여러타입
비트-,&,|,^,boolean
쉬프트>>,<<,>>>

산술 연산자


산술 연산자는 수학적인 계산에 사용되는 연산자이다. 기본적인 연산을 뜻하며 이때 value값들은 숫자가 아닌 입니다

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

        //덧셈
        System.out.println(a+b); //13
        //뺄셈
        System.out.println(a-b); //7
        //곱셈
        System.out.println(a*b); //30
        //나눗셈
        System.out.println(a/b); //3
        //나머지
        System.out.println(a%b); //1
    }
}

주의할 점은 산술 연산에서는 타입 캐스팅과 프로모션이 의도치 않게 발생할 수 있기 때문에 이를 주의해야 합니다.
또한, 항상 알고리즘 문제 풀때도 강조하는 내용이지만 오버플로에 민감하게 반응 해야합니다.

public class Main {
    public static void main(String[] args){
        int s = 2_000_000_000;
        int e = 2_100_000_000;

        int mid = (s+e)/2;
        System.out.println(mid); //이상한 값 출력
    }
}

당연히 (x+y) 이부분에서 오버플로가 발생하게 되겠죠? 그럼 이를 어떻게 해결해야 될까요?

  1. int mid = s + (e-s)/2

중간값을 구하는 방법을 아에 다르게 바꾸어 버리는 방법입니다.
이 방법은 요긴하게 쓰일 것 같으니 기억해 둡시다.

public class Main {
    public static void main(String[] args){
        int s = 2_000_000_000;
        int e = 2_100_000_000;

        int mid = s + (e-s)/2;
        System.out.println(mid); //2050000000 출력
    }
}
  1. int mid = (s+e)>>>1
    >>> 연산자는 >> 연산자와 달리 최상위 비트(MSB)를 기존 비트로 채우는 것이 아닌 오로지 0으로 채우게 됩니다.
    그렇기 때문에 1칸만 오른쪽으로 비트들을 옮겨도 최상위 비트가 0으로 채워지기 때문에 올바른 중간값을 도출 할 수 있게 됩니다. 이 연산자는 자바에서만 존재합니다.
public class Main { 
    public static void main(String[] args){ 
       int s = 2_000_000_000; 
       int e = 2_100_000_000; 

       int mid = (s+e)>>>1; 
       System.out.println(mid); 
     } 
}

주의할 점
이 방법은 양수 에서밖에 사용할 수 있습니다. 그러니 리스크도 항상 기억하세요

비트 연산자


비트 연산자는 비트(bit) 단위로 논리 연산을 할 때 사용되는 연산자입니다

진리표를 살펴봅시다

~(not)
입력결과
01
10

~(not) : 비트를 반전시킵니다

|(OR)
입력입력결과
000
011
101
111

|(OR) : 하나라도 참(1) 이라면 1(참)을 리턴합니다

&(AND)
입력입력결과
000
010
100
111

&(AND) : 둘다 참(1) 일때만 참(1)을 리턴합니다

^(XOR)
입력입력결과
000
011
101
110

^(XOR) : 서로 다른 비트일때 참(1)을 리턴합니다

public class Main {
    public static void main(String[] args){
        int a=10;   //00000000 00000000 00000000 00001010
        int b=12;   //00000000 00000000 00000000 00001100

        /*
            ~(not)을 취하면 모든 비트가 반전
            ~a = 1111111 11111111 1111111 11110101
            MSB가 1이니 음수라는 뜻이고 이때 우린 2의 보수법을 사용하게 되죠?
            1)MSB를 제외하고 비트를 반전시켜주고(1의 보수법)
            2)1을 더해주게 됩니다.(2의 보수법)

            10000000 00000000 00000000 00001010
           +00000000 00000000 00000000 00000001
           --------------------------------------
            10000000 00000000 00000000 00001011 = -11

            이렇게 -11이 나오게 되는 것입니다.
        */
        System.out.println(~a); //-11
        /*
            |(OR)을 취하면 둘 중 하나만 참(1)이면 참(1)을 리턴
            a | b =
                    00000000 00000000 00000000 00001010
                    00000000 00000000 00000000 00001100
                    ------------------------------------
                    00000000 00000000 00000000 00001110 = 14
        */
        System.out.println(a|b); // 14
        /*
            &(AND)를 취하면 둘 다 참(1)이어야지 참(1)을 리턴
            a & b =
                    00000000 00000000 00000000 00001010
                    00000000 00000000 00000000 00001100
                    ------------------------------------
                    00000000 00000000 00000000 00001000 = 8
        */
        System.out.println(a&b); // 8
        /*
            ^(XOR)를 취하면 둘이 비트값이 달라야지 참(1)을 리턴
            a ^ b =
                    00000000 00000000 00000000 00001010
                    00000000 00000000 00000000 00001100
                    ------------------------------------
                    00000000 00000000 00000000 00000110 = 6
        */
        System.out.println(a^b); // 6
    }
}

관계 연산자


관계연산자란 2개의 항을 비교하여 참인지 거짓인지 판별하는 연산자를 뜻합니다.

연산자설명
==같으면 참,다르면 거짓
!=같으면 거짓,다르면 참
왼쪽값이 크면 참, 같거나 작으면 거짓
>=왼쪽값이 크거나 같으면 참, 작으면 거짓
왼쪽값이 작으면 참, 같거나 크면 거짓
<=왼쪽값이 작거나 같으면 참, 크면 거짓
instanceof왼쪽 참조 변수 값이 오른쪽 참조 변수 타입이면 참, 아니면 거짓

논리 연산자


논리 연산자는 논리곱(&&,&), 논리합(||,|) , 논리 부정(!) 연산을 수행합니다. 논리 연산자의 피연산자는 불린 타입만 사용할 수 있습니다. 결과 또한 불린값 입니다.

연산자설명
&&논리식이 모두 참잉면 참을 반환(AND 연산)
||논리식 중에서 하나라도 참이면 참을 변환함(OR 연산)
!논리식의 결과가 참이면 거짓을, 거짓이면 참을 반환함(NOT 연산)

진리표

xyx || yx && y
TRUETRUETRUETRUE
TRUEFALSETRUEFALSE
FALSETRUETRUEFALSE
FALSEFALSEFALSEFALSE
public class Main {
    public static void main(String[] args){
        boolean b1 = true;
        boolean b2 = false;
        boolean b3 = true;

        //논리곱(&&,&)
        System.out.println(b1 && b2); // b2가 false 이므로 결과는 false
        System.out.println(b1 && b3); // b1과 b2 모두 true 이므로 결과는 true

        //논리합(||,|)
        System.out.println(b1 || b2); // b1이 true 이므로 결과는 true

        //논리 부정(!)
        System.out.println(!b1); //b1이 true 이므로 결과는 false
        System.out.println(!b2); //b2가 false 이므로 결과는 true
    }
}

& 와 &&의 그리고 | 와 ||의 차이점이 무엇인지 궁금할텐데 둘의 차이점은 바로
short-circuit evaluation 입니다.

short-circuit evalutation 이란??

단축평가라고 불리며, AND 혹은 OR 연산에 있어서 결과가 확실하게 예측이 되었을때 나머지 연산을 실행하지 않고 답을 내버리는 경우를 뜻합니다.

  • AND 연산인 경우
    AND 연산의 경우에 false가 우선적으로 나와버리면 AND 뒤에 나오는 연산은 생략이 됩니다.
  • OR 연산인 경우
    OR 연산의 경우에 true가 우선 나와버리면 OR 뒤에 나오는 연산은 생략이 됩니다.
public class Main {
    public static void main(String[] args){
        int a = 10;
        int b = 5;
        //&&
        if(a<0 && (++b)<10) System.out.println(b); //출력 x
        if(a>0 &&(++b)<10) System.out.println(b); // 6 출력

        //||
        if(a<0 || (--b)<10) System.out.println(b); //5 출력
        if(a>0 || (--b)<10) System.out.println(b); //5 출력
    }
}

a=10 이고 b=5로 값들이 할당되어 있습니다.
먼저 첫번째 if문을 실행했을 경우 a 는 0보다 큰 값이니 왼쪽평가만 해버리고 조건에 안맞으니 뒤의 연산은 생략이 되버립니다. 따라서 출력을 하지 않습니다.

두번째 if문을 실행한 경우 a가 0보다 큰 값이니 왼쪽평가를 마친 후 뒤의 연산을 실행하게 됩니다.
따라서 6을 출력하게 됩니다.

세번째 if문을 실행한 경우 a가 0보다 큰 값이니 왼쪽평가를 마친 후 뒤으 연산을 실행하게 됩니다.
따라서 5가 출력이 됩니다.

네번째 if문을 실행한 경우 a가 0보다 큰 값이니 왼쪽평가만으로도 조건에 충족하니 뒤의 연산을 실행하지 않습니다.
따라서 5가 출력이 됩니다.

instanceof


instanceof란 객체타입을 확인하는데 사용됩니다.
속성은 연산자이며 형변환이 가능한지 해당 여부를 true 혹은 false로 가르쳐줍니다.
주로 레퍼런스 타입 변수가 레퍼런스 데이터 타입으로 변환이 가능한지 확인하기 위해 사용됩니다.(부모 객체인지 자식 객체인지 확인하는데 쓴다고 생각하면 됩니다)

사용형식 : 객체 + instanceof + 클래스 입니다.

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

        System.out.println("parent instanceof Parnet : " + (parent instanceof Parent)); //true
        System.out.println("child instanceof Parent : " + (child instanceof Parent)); //true
        System.out.println("parent instanceof Child : " + (parent instanceof Child)); //false
        System.out.println("child instanceof Child : " + (child instanceof Child)); //true
    }
}
class Parent{}
class Child extends Parent{}

3번째만 false 가 나왔습니다. 이유는 parnet 본인이 부모객체인데도 불구하고 자식객체로 사용되려고 했기 때문입니다.
나머지는 전부 만족을 하는 조건입니다.

assignment(=) operator


할당 연산자 또는 대입 연산자라고 불립니다. 오른쪽의 피연산자를 왼쪽의 피연산자의 값으로 할당합니다.
흔히 값을 초기화 한다고 하기도 합니다.(초기화와 할당은 사실 개념은 다르지만 그냥 통상해서 부르는 것 같습니다)

대입연산자에는 여러가지 연산자들이 있지만 좀 헷갈릴 수 있는 연산자 쉬프트 연산자를 봐봅시다.
>><< 연산자를 사용할때 범위를 벗어나게 된다면 그 자리는 어떠한 비트(0,1)로 채워지게 되는 걸까요??

public class Main {
    public static void main(String[] args){
        int x = 17;
        int y = 3;

        //x = 0000000 00000000 0000000 00000000 00010001
        //x<<(y) = 0000000 00000000 0000000 00000000 10001000
        System.out.println(x<<(y)); //136 출력
        //=> 0 으로 채워지는 걸 확인

        //x = 0000000 00000000 0000000 00000000 00010001
        //x>>(y) = 0000000 00000000 0000000 00000000 00000010
        System.out.println(x>>(y)); //2 출력
        //=> MSB(0) 으로 채워지는 걸 확인

        x=-17;
        //x = 1111111 11111111 11111111 11101111
        //x>>(y) = 11111111 11111111 11111111 11111101
        System.out.println(x>>(y));// -3 출력
        //=> MSB(1) 으로 채워지는 걸 확인
    }
}

보시는 거와 같이 >> 연산자를 이용할 땐 최상위비트는 원래 MSB 였던 값으로 채우는 걸 확인 할 수 있습니다.
그리고 << 연산자를 이용할 땐 0으로 채워지는 걸 확인 할 수 있습니다.

화살표(->) 연산자


화살표 연산자는 자바에 람다 가 도입되고 같이 들어온 개념이라고 한다.
(아직 클래스와 인터페이스에 관한 내용을 다루지 않았으니 간단하게 훑고 가자)

화살표 연산자를 이해하기 위해선 람다 부터 이해해야 한다.
람다라는 개념이 존재하지 않았을때 인터페이스클래스를 사용하려고 할때 귀찮음이 있었다.

//Foo interface
@FunctionalInterface
public interface Foo{
    void printInt(int x);
}
//Main class
public class Main implements Foo{
    @Override
    public void printInt(int x){
        System.out.println(x);
    }
    public static void main(String[] args){
        Main v = new Main();
        int y = 3;
        v.printInt(y); //3 출력
    }
}

interface에서는 오로지 추상 메소드만 선언할 수 있고 이를 클래스내에서 구현체를 만들어서 사용할 수 있었다.
이를 내부 익명 클래스를 사용하여 구현할 수 있다.

public class Main{
    public static void main(String[] args){
        int y=3;
        Foo foo = new Foo(){
            @Override
            public void printInt(int x){
                System.out.println(x); //3 출력
            }
        };
        foo.printInt(y);
    }
} //이때는 implements 키워들 빼야한다. 적어주면 충돌발생

그러나 단순히 출력하기 위한 기능을 구현하기 위해 상당히 많은 코드들을 짜야되고 이는 불편함을 야기하게 되었다.
람다는 내부익명 클래스를 통해서 만든 구현체의 단점을 보완할 수 있게 되었다.

public class Main{
    public static void main(String[] args){
        int y=3;
        Foo foo =(x)-> System.out.println(x);
        foo.printInt(y); //3 출력
    }
}

더 자세한 이야기는 후에 다루자. 나도 아직 잘 모른다 ㅎㅎㅎ
[출처 : https://catch-me-java.tistory.com/30]

3항 연산자


3항연산자는 알고리즘 문제풀때 되게 유용하게 사용 될 수 있는 개념인데 실무에서는 사용되련지 모르겄다!

사용 방법 : (조건) ? (참일때 실행) : (거짓일때 실행)

public class Main{
    public static void main(String[] args){
        int x=10;

        if(x>0) System.out.println("TRUE"); //TRUE 출력
        else System.out.println("FALSE");
    }
}

이러한 조건문을 3항연산자를 통해 한번 바꾸어보자

public class Main{
    public static void main(String[] args){
        int x=10;
        System.out.println(x>0 ? "True":"False");
    }
}

한 줄로 깔끔하게 나타낼 수 있다.

연산자 우선순위


우선순위연산자
1(),[]
2!,~,++,--
3*,/,%
4+,-
5<<,>>,>>>
6<,<=,>,>=
7==,!=
8&
9^
10|
11&&
12||
13?:
14=,+=,-=,*=,/=,<<=,>>=,&=,^=,~=

(optional) Java 13, switch operator


되게 흥미로운 내용인데 , Java 12 에서 Java 13으로 넘어올때 switch 문법이 변경이 되었다.
statement 가 아니고 operator 로 변경이 된 것이다.
이 말은 무슨 뜻이냐면 switch 문을 통해 결과값을 산출 할 수 있다는 것을 뜻한다.

public class Main{
    public static void main(String[] args){
        int x=5;
        System.out.println(switchRet(x)); //5출력
    }
    public static int switchRet(int x) {
        int ret;
        switch (x) {
            case 1:
                ret = 1;
                break;
            case 2:
                ret = 2;
                break;
            case 3:
                ret = 3;
                break;
            case 4:
                ret = 4;
                break;
            case 5:
                ret = 5;
                break;
            default:
                ret = -1;
                break;
        }
        return ret;
    }
}