연산자

백기선님의 유튜브로 진행하시는 스터디를 진행하며 올리는 정리 블로그입니다.



산술 연산자

두개의 피연산자를 갖는 이항 연산자로써, 기본적인 사칙연산을 다루는 연산자

◾ 더하기 (+)

왼쪽의 피연산자에 오른쪽 피연산자를 더하는 연산자로 숫자+숫자, 문자열+문자열이 가능하고 문자열+숫자를 할 시 숫자를 자동으로 문자열로 변환하여 덧셈이 가능하다.

문자+숫자를 할 경우에는 아스키 코드를 이용하여 문자로 결과가 출력 된다.
문자에 맞는 아스키 코드값과 숫자를 더한 결과값에 해당하는 아스키코드를 return하기 때문이다.

int left = 2020;
int right = 1;
int year = left + right;
char ch = A;

System.out.println(year); //2021
System.out.println("20"+"21"); //2021
System.out.println("Hello "+ year); //Hello 2021

System.out.println(ch + 2); //C
System.out.println( 9 + '0'); //9


◾ 뺄셈 (-)

왼쪽의 피연산자에서 오른쪽 피연산자를 빼는 연산자로 숫자-숫자와 같이 사용가능하고 문자열은 불가능하다.

◾ 곱셈 (*)

왼쪽의 피연산자에 오른쪽 피연산자를 곱하는 연산자로 숫자*숫자와 같이 사용가능하고 문자열은 불가능하다.

◾ 나눗셈 (/)

왼쪽의 피연산자를 오른쪽 피연산자로 나누는 연산자로 숫자/숫자와 같이 사용가능하고 문자열은 불가능하다.

정수/정수를 할 시 나머지는 버려진 몫만 구해지며 두 피연산자 중 하나라도 실수(부동 소수방식)가 존재한다면 결과는 실수가 나온다.

오른쪽 피연산자가 0이 될 수 없다. (0으로 나눌 수 없고 나눈다면 컴파일 에러가 난다.)

0이 아닌 정수를 0.0으로 나눈다면 Infinity, 0을 0.0으로 나눈다면 NaN이 반환된다.

int left = 5;
int right = 2;
System.out.println(left/right); // 2
System.out.println(left/2.0); // 2.5

System.out.println(left/0.0); //Infinity
System.out.println(0/0.0);      //NaN

◾ 나머지 (%)

왼쪽의 피연산자를 오른쪽 피연산자로 나눈 나머지를 구하는 연산자로 숫자%숫자와 같이 사용가능하고 문자열은 불가능하다.

왼쪽 피연산자를 0.0이나 0으로 나눈다면 NaN이 반환된다. (왼쪽 피연산자가 정수인데 0으로 나눈다면 컴파일 에러)

System.out.println(5%0); //컴파일 에러
System.out.println(5.0%0);  //NaN
System.out.println(0.0%0);  //NaN
System.out.println(0%0); //컴파일 에러

System.out.println(5%0.0);  //NaN
System.out.println(5.0%0.0); //NaN
System.out.println(0.0%0.0); //NaN
System.out.println(0%0.0);  //NaN



단항 연산자

피 연산자를 한개만 갖는 연산자

◾ 부호 연산 (+/-)

숫자를 입력시 +는 형식적으로 제공을 하기 때문에 생략이 가능하다.

-는 부호를 바꾸는 연산자로 2의 보수를 취하는 연산자이다.

System.out.println(2);//2
System.out.println(+2);//2
System.out.println(-2);// -2


◾ 증감 연산자 (++/–)

증감 연산자 독립적으로 사용하는 경우에는 위치에 상관없이 같은 결과를 갖는다.

int i=1;
int j=1;

i++; // i = 2
++j; // j = 2
i--; // i = 1
--j; // j = 1

다른 명령문과 같이 쓰인다면 결과가 달라진다.

  • 전위 증감 연산

    피연산자를 참조하기전에 값을 증감 시키고 난 후에 피연산자를 참조한다.

    int i=1;
    System.out.println(++i); //2출력
    System.out.println(--i); //1출력
    
  • 후위 증감 연산

    피연산자를 먼저 참조하고난 후 증감 시킨다.

    int i = 1;
    System.out.println(i++); //1 출력 후 i=2
    System.out.println(i--); //2 출력 후 i=1
    



비트 연산자

피연산자를 이진수로 생각하고 각 자리수(bit)단위로 계산하는 연산자

◾ 비트 NOT (~)

1의 보수를 구하는 연산자로 각 bit를 반전 시키는 연산자

byte num1 = 10;
  System.out.println(num);  //10  (00001010)
  System.out.println(~num); //-11 (11110101)

◾ AND (비트곱) (&)

두 피연산자의 각 bit들을 AND 연산

  • 1 & 1 = 1

  • 1 & 0 = 0

  • 0 & 1 = 0

  • 0 & 0 = 0

byte num1 = 10;  //bit로 00001010
byte num2 = 20;  //bit로 00010100

byte num3 = num1 & num2;
System.out.println(num3); //비트 곱하면 00000000로 0이 나온다.

◾ OR (비트합) (|)

두 피연산자의 각 bit를 OR 연산

  • 1 | 1 = 1

  • 1 | 0 = 1

  • 0 | 1 = 1

  • 0 | 0 = 0

byte num1 = 10;  //bit로 00001010
byte num2 = 20;  //bit로 00010100

byte num3 = num1 | num2;
System.out.println(num3); //비트 곱하면 00011110로 30이 나온다.

◾ XOR (^)

두 피연산자의 각 bit를 XOR 연산

  • 1 ^ 1 = 0

  • 1 ^ 0 = 1

  • 0 ^ 1 = 1

  • 0 ^ 0 = 0

byte num1 = 10;  //bit로 00001010
byte num2 = 20;  //bit로 00010100

byte num3 = num1 ^ num2;
System.out.println(num3); //비트 XOR하면 00011110로 30이 나온다.

◾ 오른쪽 Shift 연산자 (>>)

왼쪽의 피연산자의 bit를 오른쪽 피연산자의 수만큼 오른쪽으로 이동시키고 밀린 오른쪽 끝의 비트는 삭제되며 새로 오는 가장 왼쪽의 bit는 원래 부호에 따라 0(양수) or 1(음수)로 채워진다.

byte num1 = 10; //00001010
byte num2 = -10; //11110110

System.out.println(num1 >> 1); //00000101
System.out.println(num2 >> 1); //11111011

◾ unsigned 오른쪽 Shift 연산자 ( >>> )

왼쪽의 피연산자의 bit를 오른쪽 피연산자의 수만큼 오른쪽으로 이동시키고 밀린 오른쪽 끝의 비트는 삭제되며 새로 오는 가장 왼쪽의 bit는 부호에 상관없이 0으로 채워진다.

byte num1 = 10; //00001010
byte num2 = -10; //11110110

System.out.println(num1 >> 1); //00000101
System.out.println(num2 >> 1); //01111011

◾ 왼쪽 Shift 연산자 (<<)

왼쪽의 피연산자의 bit를 오른쪽 피연산자의 수만큼 왼쪽으로 이동시키고 밀린 왼쪽 끝의 비트는 삭제되며 새로 오는 가장 오른쪽의 bit는 0으로 채워진다.

byte num1 = 10; //00001010
byte num2 = -10; //11110110

System.out.println(num1 << 1); //00010100
System.out.println(num2 << 1); //11101100



관계 연산자

두 피연산자간에 값을 비교하기 위한 연산자

◾ 동치 연산자 ( == / != )

두 피연산자 값이 같은지 비교하는 연산자로 원시형 타입끼리 비교할때는 값을 비교하고 데이터 크기가 다른 데이터타입끼리의 비교라면 큰 데이터 타입으로 변환되어 비교한다.

참조 타입 (String, Array, Class...)끼리의 비교라면 동일한 원본 데이터(객체)인지 비교한다.

  • == : 두 피연산자 값이 같으면 false, 아니면 false 반환

  • != : 두 피연산자 값이 같지 않으면 false, 같으면 false 반환

◾ < , <=

왼쪽 피연산자가 오른쪽 피연산자보다 작은지(작거나 같은지) 판단하여 맞다면 true, 아니라면 false반환

◾ >, >=

왼쪽 피연산자가 오른쪽 피연산자보다 큰지(크거나 같은지) 판단하여 맞다면 true, 아니라면 false 반환



논리 연산자

boolean 형(true/false)를 비교하는 연산자로 두 피연산자가 boolean형일때만 사용가능 하다.

◾ 논리 NOT (!)

한개의 피연산자의 값을 바꾸는 연산자

boolean t = true;
System.out.println(!t); //false;

◾ 논리 AND (&&)

두 피연산자가 모두 true이면 true 아니면 false를 반환한다.

boolean t = true;
boolean f = false;

System.out.println(t && t); //true
System.out.println(t && f); //false
System.out.println(f && t); //false
System.out.println(f && f); //false

◾ 논리 OR (||)

두 피연산자중 하나라도 true이면 true 아니면 false를 반환한다.

boolean t = true;
boolean f = false;

System.out.println(t || t); //true
System.out.println(t || f); //true
System.out.println(f || t); //true
System.out.println(f || f); //false

◾ 단락 회로 평가 (Short Circuit Evaluation)

논리 연산자를 수행 중 true나 false조건이 만족된다면 오른쪽 피연산자는 참조안하고 바로 true 리턴하는 것

System.out.println(f && t); //왼쪽이 피연산자가 이미 false이므로 오른쪽 피연산자인 t 는 참조하지 않고 바로 false return
System.out.println(t && f); //왼쪽이 true이기 때문에 오른쪽피연산자 값에 따라 return값이 다르므로 오른쪽 피연산자 참조후 false return

System.out.println(t || f); //왼쪽이 피연산자가 이미 true이므로 오른쪽 피연산자인 t 는 참조하지 않고 바로 truereturn
System.out.println(f || f); //왼쪽이 false이기 때문에 오른쪽피연산자 값에 따라 return값이 다르므로 오른쪽 피연산자 참조후 false return

◾ & , | , ^

비트 연산자를 논리형(boolean)에도 사용할 수 있으며 결과값은 동일하지만 단락 회로 평가가 되지 않는다.
(양쪽 피연산자를 모두 참조후 결과 값 반환)

boolean t = true;
boolean f = false;

System.out.println(t & t); //true
System.out.println(t & f); //false
System.out.println(f & t); //false
System.out.println(f & f); //false

System.out.println(t | t); //true
System.out.println(t | f); //true
System.out.println(f | t); //true
System.out.println(f | f); //false

System.out.println(t ^ t); //false
System.out.println(t ^ f); //true
System.out.println(f ^ t); //true
System.out.println(f ^ f); //false



instanceof

참조 타입의 변수가 특정 타입인지 검사하는 연산자로 같다면 true, 아니라면 false가 반환되고 null을 검사하려고한다면 false가 반환된다.

참조 타입 변수가 초기화가 되지 않았다면 검사할 변수의 참조 값이 없으므로 컴파일 에러가 뜬다.

자바의 모든 객체는 암시적으로 Object상속받고 있기 때문에 Object로 비교해도 true가 나오며 왼쪽 피연산자로 원시타입은 올 수 없다.

int[] i;
System.out.println(i instanceof int[]); //컴파일 에러

int[] i = new int[5];
System.out.println(i instanceof Object); //true
System.out.println(i instanceof int[]); //true

Object j = new int[5];
System.out.println(i instanceof int[]); //true
System.out.println(j instanceof Object);//true

System.out.println(null instanceof Object);//false

System.out.println("hello" instanceof Object);//true
System.out.println("hello" instanceof String);//true

ArrayList arrayList= new ArrayList();
System.out.println(arrayList instanceof ArrayList); //true
System.out.println(arrayList instanceof Object);  //true



assignment(=) 연산자

우리가 일반적으로 변수에 값을 할당하거나 참조할 데이터 주소를 할당할때 쓰는 연산자이다.

◾ 복합 대입 연산자

연산을 실행 후 대입하는 연산자로 산술 연산자 or 비트 연산자 or 시프트 연산자대입연산자를 합친 형태이다.

int i = 10;

i += 1; // i = i + 1
i -= 2; // i = i - 2
i *= 3; // i = i * 3
i /= 4; // i = i / 4
i %= 5; // i = i % 5

i &= 1; // i = i & 1
i |= 2; // i = i | 2
i ^= 3; // i = i ^ 3

i <<= 1; // i = i << 1
i >>= 2; // i = i >> 2
i >>>= 3; // i = i >>> 3



화살표(->) 연산자

람다를 사용하기 위해 도입된 람다 표현식으로 익명 컬렉션이다.

핵심은 간단하게 표현이다.

컴파일러의 추론을 통해 지울수 있는 것은 모두 지우거나(생략하여) 표현하는 방법이다.

interface User{
    void printUserName(String name);
}

User user = new User() {
    @Override
    public void printUserName(String name) {
        System.out.println("User name is " + name);
    }
};

위의 코드와 같이 setUserName메서드 하나만을 갖고있는 User 인터페이스에서 이 메서드를 override하려고 한다고 한다면 아래와 같이 생략이 가능할 것이다.

User user = (name) -> {
    System.out.println("User name is " + name);
}

컴파일러는 User user를 통해 데이터 타입을 명시 했기 때문에 new User부분은 생략이가능할 것이고 메서드는 하나만 갖고있기때문에 메서드를 명시하지 않아도 추론이 가능하기 때문에 이렇게 표현이 가능하다.

파라미터 인자가 한개라면 ()는 생략이 가능하고 {}안의 코드가 한줄이라면 {}또한 생략이 가능하다.



3항 연산자 (?:)

말그대로 3개의 피연산자를 사용하는 연산자이다.

조건문 (if/else)처럼 사용이 가능하다.

조건문 ? 값1 : 값2와 같은 형태로 조건문이 참이라면 값1을 조건문이 거짓이라면 값2를 반환한다.

boolean isTrue1 = (10 > 1) ? true : false;  //10 > 1은 true이기 때문에 true를 isTrue에 반환한다.

(10 > 1) ? true : false; //3항 연산자는 식으로써 값을 만들어내기 때문에 참조나 대입이 없다면 `Not a Statment`라는 컴파일 에러를 띄운다.

◾ if/else와 3항 연산자 차이

  • if/else는 statement로써 값을 만들어내지 못한다.

  • 3항 연산자는 expression으로써 값을 만들어 낸다.


때문에 ?뒤에 오는 2개의 값에는 값만 넣고 코드를 넣지 말자!!

가독성도 안좋아지고 엄연히 목적이 다르다.

Statement와 Expression란? (if/else와 삼항연산자의 차이) 참고 글

연산자 우선 순위

우선순위 연산자 연산 방향 동작 내용
1 . 왼쪽 -> 오른쪽 객체 멤버 접근
[, ] 왼쪽 -> 오른쪽 배열 요소 접근
(args) 왼쪽 -> 오른쪽 메소드 호출
i++, i-- 왼쪽 -> 오른쪽 후위 증감
2 ++i, --i 왼쪽 <- 오른쪽 전위 증감
+, - 왼쪽 <- 오른쪽 단항 증감
~, ! 왼쪽 <- 오른쪽 비트 보수, 부정 연산
3 new 왼쪽 <- 오른쪽 객체 생성
(datatype) 왼쪽 <- 오른쪽 캐스팅(형 변환)
4 *, /, % 왼쪽 -> 오른쪽 곱하기, 나누기, 나머지
5 +,- 왼쪽 -> 오른쪽 더하기, 빼기
6 <<, >>, >>> 왼쪽 -> 오른쪽 왼쪽 시프트, 오른쪽 시프트, 부호없는 오른쪽 시프트
7 <, <=, >, >= 왼쪽 -> 오른쪽 작음, 작거나 같음, 큼, 크거나 같음
instanceof 왼쪽 -> 오른쪽 타입 비교
8 ==, != 왼쪽 -> 오른쪽 같음, 같지 않음
9 & 왼쪽 -> 오른쪽 AND
10 ^ 왼쪽 -> 오른쪽 XOR
11 | 왼쪽 -> 오른쪽 OR
12 && 왼쪽 -> 오른쪽 논리 AND
13 || 왼쪽 -> 오른쪽 논리 OR
14 ?: 왼쪽 <- 오른쪽 3항 연산자
15 =, *=, /=, %=, +=, -=, <<=, >>=, >>>=, &=, ^=, |= 왼쪽 <- 오른쪽 대입 연산자
16 -> 왼쪽 -> 오른쪽 람다 표현식



switch 연산자

  //Java 12 이전
        int num = 1;
        int result = 0;
        switch(num){
            case 1:
                result = 1;
                System.out.println("1");
                break;
            case 2:
                result = 2;
                System.out.println("2");
                break;
            case 3:
                result = 3;
                System.out.println("3");
                break;
        }

        //Java 12
        var result2 = switch(num){
            case 1 -> 1;
            case 2 -> System.out.println("2"); //compile error
            case 3 -> 3;
            case 4 :            //compile error
                System.out.println("4");
                break;
            default -> throw new IllegalStateException("default");
        };


        //Java13
        var result3 = switch(num){
            case 1 : {
                System.out.println("1");
                yield 1;
            }
            case 2 : yield 2;
            case 3 : yield 3;
            default :
                throw new IllegalStateException("default");

        };

        var result4 = switch(num){
            case 1 -> {
                System.out.println("1");
                yield 1;
            }
            case 2 -> 2;
            case 3 -> {
                yield 3;
                System.out.println("2");  //compile error
            }
            default ->
                throw new IllegalStateException("default");

        };

기존의 switch문에서 외부 변수에 값을 대입하기 위해서는 위의 코드와 같이 result = 1과 같은 식으로 매 case마다 추가해주어야 했었다.

Java 8이후에 람다식이 생기고 나서 Java에 많은 변화가 있었고 Java 12에는 case마다 결과값을 반환하는 switch문에 경우 람다 표현식(->)을 사용 할 수 있게 되었다.

Java 12의 람다 표현식의 경우 바로 대입값을 우항에 표현해주어야 했기 때문에 추가 작업을 못한다는 단점이 있어

Java 13yield가 추가 되었다.

yield를 사용하면 해당 값을 return하고 case문을 종료하기 때문에 yield다음 줄에 다른 구문이 온다면 compile error가 난다.

◾ 특징

  • 각 case마다 반환타입을 컴파일시에 추론이 가능하기 때문에 추론형 데이터 타입인 var이 가능하다.

  • 모든 case에 대해 반환 값이 있어야 error가 안나기 때문에 default문도 반드시 있어야 한다.

  • 람다 표현식(->)기존 표현식 (:)은 혼용해서 사용이 불가능하다.

  • break;구문 없이도 다음 case로 넘어가지 않는다.





Refernece

https://blog.baesangwoo.dev/posts/java-livestudy-3week/#%EB%B9%84%ED%8A%B8-%EC%97%B0%EC%82%B0%EC%9E%90

https://multifrontgarden.tistory.com/124