본문 바로가기
자바/스터디

[자바 스터디] 3주차 과제 : 연산자

by jeonghaemin 2020. 12. 1.
728x90

본 게시글은 백기선 님의 live-study 과제를 수행하면서 작성한 글입니다.

목표

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

학습할 것

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

연산자란?

연산자는 '연산을 수행하는 기호'를 말한다. 예를 들어 '+' 기호는 덧셈 연산을 수행하며, '덧셈 연산자'라고 한다. 자바에서는 사칙연산을 비롯해서 다양한 연산자들을 제공한다.

종류 연산자
산술 연산자 +, -, *, /, %, <<, >>
비교 연산자 >, <, >=, <=, ==, !=
논리 연산자 &&,
대입 연산자 =
기타 (type), ?:, instanceof

연산자가 연산을 수행하려면 연산 대상이 있어야하는데 이것을 '피연산자'라고 한다. 예를 들어 'x + 3' 식에서 연산자는 '+', 피연산자는 x와 3이다

종류 피연산자 개수
단항 연산자 1 -x, ++x, --x, x++, x--
이항 연산자 2 x+3, x-5, x*3
삼항 연산자 3 x==y ? x : y

피연산자의 개수에 따라 연산자를 분류하기도 한다.

산술 연산자

int n1 = 10;
int n2 = 4;

System.out.printf("%d + %d = %d\n", n1, n2, n1+n2); // 결과 : 14
System.out.printf("%d - %d = %d\n", n1, n2, n1-n2); // 결과 : 6
System.out.printf("%d * %d = %d\n", n1, n2, n1*n2); // 결과 : 40
System.out.printf("%d / %d = %d\n", n1, n2, n1/n2); // 결과 : 2
System.out.printf("%d / %f = %f\n", n1, n2, n1+n2); // 결과 : 2.500000
System.out.printf("%d % %d = %d\n", n1, n2, n1%n2); // 결과 : 2

산술 연산자에는 우리에게 익숙한 사칙연산자( +,-,*,/)와 나머지 연산자(%)가 있다.

long + int -> long + long -> long
float + int -> float + float -> float
double + float -> double + double -> double

산술 연산의 경우 피연산자의 타입이 다를 경우 값의 손실 가능성이 있기때문에, 두 피연산자의 타입이 일치해야 연산이 가능하다. 그래서 피연산자의 타입이 다르다면 형변환을 통해 타입을 일치시켜야한다. 자바에서는 대부분의 경우 두 피연산자의 타입 중에서 범위가 더 큰 타입으로 일치시키며 자동적으로 형변환이 되어 형변환 연산자를 생략할 수 있다.

float n1 = 5/2; // 결과 : 2.0f
float n1 = (float)5/2; // 결과 : 2.5f

int n2 = 1_000_000;
int n3 = 2_000_000;
long n4 = n1 * n2; // 결과 : -1454759936

위 코드의 5/2 연산은 실수형 변수에 담겨있더라도 연산의 결과가 int형이기 때문에 2.5가 아닌 소수점이 잘려져나간 int형 정수 2를 결과로 반환한다. 2.5를 결과값으로 얻고 싶다면 피연산자 중 하나를 실수형으로 형변환하면 된다.

1000000*200000 곱셉 연산의 결과도 long 형에 충분히 저장할 수 있는 범위지만, 피연산자가 int형이기 떄문에 오버플로우가 발생된 값이 변수에 저장된다.

byte + short -> int + int -> int
char + short -> int + int -> int

정수형의 기본 타입인 int형이 가장 효율적으로 처리할 수 있는 타입이고, char나 short는 값의 범위가 좁아서 연산 중에 오버플로우가 발생할 가능성이 있기 때문에 피연산자의 타입이 int보다 작은 타입이면 int로 형변환되어 연산을 수행한다.

char a = 'a';
char b = 'b';

System.out.printf("%d\n", (int)a); // 결과 : 97
System.out.printf("%c - %c = %d\n", b, a, b-a)// 결과 : 1

문자는 실제로 해당 문자의 유니코드 값으로 변환되어 저장되기 때문에 이 코드값을 이용하여 정수 연산과 똑같이 문자도 사칙연산을 수행할 수 있다. 이를 이용하여 문자형 숫자를 쉽게 정수형 숫자로 변환할 수도 있다.

char ctwo = '2';
int ntwo = ctwo - '0'; // 0

비트 연산자

비트 연산자는 피연산자를 비트 단위로 논리 연산한다.

  • |(OR 연산자) : 피연산자 중 하나라도 1이 있다면 1, 아니면 0
  • &(AND 연산자) : 피연산자 둘 모두 1이면 1, 아니면 0
  • ^(XOR 연산자) : 피연산자 값이 서로 다를 경우 1, 아니면 0
x y x|y x&y x^y
1 1 1 1 0
1 0 1 0 1
0 1 1 0 1
0 0 0 0 0
  • ~(비트 전환 연산자) : 피연산자를 2진수로 변환하였을때 1은 0으로, 0은 1로 바꾼다.
x ~x
1 0
1 1

부호있는 타입의 피연산자는 부호가 반대로 변경되어 1의 보수를 얻을 수 있다. 그래서 비트전환 연산자를 '1의 보수 연산자'라고도 한다.

  • < <, > >(쉬프트 연산자) : 피연산자를 2진수로 변환하였을때 비트의 자리수를 왼쪽 또는 오른쪽으로 이동시킨다.

<< 연산자는 비트들을 왼쪽으로 자리를 이동시키는데 부호와 상관없이 이동하면서 생긴 빈자리를 0으로 채운다.

| 1 << 2 | 0000 0001(2) -> 0000 0100(2) | 4 |

>> 연산자는 비트들을 오른쪽으로 자리를 이동시키는데 부호있는 정수의 부호를 유지하기위해 음수는 빈자리를 1로, 양수는 0으로 채운다.

-8>>2 1111 1000(2) -> 1111 1110(2) -2
8>>2 0000 1000(2) -> 0000 0010(2) 2

x << n은 x * 2n과 같으며 , x >> n은 x/2n 과 같다.

관계 연산자

관계 연산자는 두 피연산자를 비교하는 데 사용되는 연산자이며, 연산 결과는 오직 true 또는 false이다.

관계 연산자 역시 이항 연산자이기 때문에 비교하는 피연산자의 타입이 다를 경우 값의 범위가 큰 쪽으로 자동 형변환하여 타입을 일치시킨 후에 비교한다.

비교 연산자

boolean 형을 제외한 나머지 기본형에서 모두 사용가능하지만, 참조형에서는 사용할 수 없다.

연산자 연산결과
> 왼쪽 값이 더 크면 true, 아니면 false
< 오른쪽 값이 더 크면 true, 아니면 false
>= 왼쪽 값이 더 크거나 같으면 true, 아니면 false
<= 오른쪽 값이 더 크거나 같으면 true, 아니면 false

등가 연산자

기본형, 참조형에서 모두 사용 가능하다. 기본형은 변수의 값을 비교하고, 참조형은 변수에 담긴 객체의 주소 값을 비교하여 연산을 수행한다.

참조형의 경우 Object.equals() 메소드를 오버라이드하여 원하는 방식으로 비교연산을 수행할 수도 있으며, String 타입의 문자열도 equals() 메소드를 이용하여 등가 비교 연산을 수행할 수 있다.

연산자 연산결과
== 두 값이 같으면 true, 아니면 false
!= 두 값이 다르면 true, 아니면 false

논리 연산자

논리 연산자는 둘 이상의 조건을 '그리고'나 '또는'으로 연결하여 하나의 식으로 표현할 수 있게 해준다.

  • ||(OR 결합) : 피연산자 중 어느 한 쪽만 true이면 true로 결과를 얻는다.
  • &&(AND 결합) : 피연산자 양쪽 모두 true이어야 true를 결과로 얻는다.
x y x||y x&&y
true true true true
true false true false
false true true false
false false false false
// x가 10보다 크고 20보다 작으면 true
x > 10 && x < 20 

//x가 2의 배수이거나 3의 배수이면 true
x%2 == 0 || x%3== 0 

||(OR 결합) 연산의 경우 true일 확률이 더 높은 피연산자를 앞에 두면 연산 효율을 높일 수 있다.

OR 연산은 둘 중 하나라도 true이면 true이기 때문에 앞의 피연산자가 true이면 뒤의 피연산자는 값을 연산하지 않는다.

// ch가 영어 대문자 또는 소문자이면 true
(ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') 

그렇기 때문에 위의 식에서 ch가 영어 소문자라면 대문자인지 판별하는 연산을 수행하지 않고 결과를 true를 반환한다.

  • !(논리 부정) : 피연산자가 true이면 false, false이면 true를 결과로 반환한다.
x x!
true false
false true

instanceof

instanceof 연산자는 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보는데 사용한다

class Parent {}
class Child extends Parent {}

public class Main {
  public static void main(String[] args) {
    Child c = new Child();
    System.out.println(c instanceof Child);
    System.out.println(c instanceof Parent); 
    System.out.println(c instanceof Oject);  

    //결과 : true true true
  }
}

위 코드의 결과 값은 모두 true이다. instanceof 연산자는 실제 자신의 타입 뿐만 아니라 조상 타입의 타입에도 true를 반환한다.

Parent 클래스는 Child 클래스의 조상 클래스이기 때문에 true, Object 클래스는 자바에서 모든 클래스의 최고 조상이기 때문에 true이다.

이를 통해 '어떤 타입에 대한 instanceof 연산의 결과가 true이면 해당 타입으로 형변환이 가능하다'라는 것을 알 수 있다.

대입(=) 연산자

대입 연산자는 변수와 같은 저장 공간에 결과를 저장하는데 사용된다.

int n = 3; // 변수 n에 3을 저장한다.

대입 연산자는 잠시 후에 알아볼 연산자 우선 순위에서 가장 낮은 우선 순위를 가지고 있어 식에서 가장 나중에 수행되고, 연산 진행 방향은 왼쪽에서 오른쪽으로 진행되는 사칙연산과 달리 오른쪽에서 왼쪽으로 진행된다.

int x = y = 3;
  1. 변수 y에 3을 저장한다.
  2. 변수 x에 y의 값을 저장한다.

또한 대입 연산자는 다음 표와 같이 다른 연산자와 결합하여 사용될 수도 있다.

op= =
i += 5; i = i + 5;
i -= 5; i = i - 5
i *= 5; i = i * 5;
i /=5; i = i / 5;
i %= 5; i = i % 5;
i << = 5; i = i << 5;
i >>= 5; i = i >> 5
i &= 5; i = i & 5;
i ^= 5; i = i ^ 5;
i = 5;

화살표(->) 연산자

화살표(->) 연산자는 자바8의 람다식의 도입으로 생긴 것으로 알고 있다.

람다식은 메서드를 하나의 식으로 표현함으로써 함수를 간략하고 명확한 식으로 표현할 수 있게 해준다.

int max(int a, int b) {
  return a > b ? a : b;
}

위의 메서드를 람다식으로 바꾸려면 다음과 같이 메서드의 반환 타입과 이름을 지워주면 된다.

(int a, int b) -> { return a > b ? a : b;}

반환 값이 있는 메서드의 경우 return 키워드를 생략할 수 있으며, 메서드의 내용이 한 줄인 경우 {} 괄호를 생략할 수 있다.

(int a, int b) ->  a > b ? a : b //;도 빼줘야한다.

람다식에 선언된 매개변수의 타입은 추론이 가능한 경우 생략할 수 있다. 대부분의 경우 생략 가능하고, 람다식에 반환 타입이 없는 이유도 항상 추론이 가능하기 때문이다.

(a,b) ->  a > b ? a : b

//매개 변수가 하나인 경우 매개 변수의 () 괄호도 생략 가능하다.
a -> System.out.println(a)

3항 연산자

3항 연산자는 조건식, 2개의 식 모두 3개의 피연산자를 필요로한다. if문 대신 3항 연산자를 사용하면 코드를 간단하게 변경할 수 있다.

if(x > y) {
  result = x;
} else {
  result = y;
}

위의 if문을 3항 연산자를 이용하여 간단하게 바꿀 수 있다.

result = x > y ? x : y;

x>y 조건식의 결과에 따라 조건식의 결과가 true이면 x가, false이면 y가 연산 결과가 된다.

//x가 0보다 크면 1, 0이면 0, 0보다 작으면 -1
result = x > 0 ? 1 : (x==0 ? 0 : -1);

다음과 같이 중첩하여 사용할 수도 있다.

연산자 우선 순위

식에 사용된 연산자가 둘 이상인 경우, 연산자의 우선순위에 의해서 연산순서가 결정된다.

종류 결합 규칙 연산자 우선 순위
단항 연산자 <- x++, x--, +x, -x, ~x, !x, (type)x 1
산술 연산자 -> *, /, % 2
산술 연산자 -> +, - 3
산술 연산자 -> <<, >> 4
비교 연산자 -> <, >, <=, >=, instanceof 5
비교 연산자 -> ==, != 6
논리 연산자 -> & 7
논리 연산자 -> ^ 8
논리 연산자 ->    
논리 연산자 -> && 10
논리 연산자 ->    
3항 연산자 -> 조건식 ? 식1 : 식2 12
대입 연산자 <- =, +=, -=, *=, /=, %=, <<=, >>=, &=, ^=, =
  1. 산술 > 비교 > 논리 > 대입 순으로 우선 순위가 높다.
  2. 단항 > 이항 > 삼항 순으로 우선 순위가 높다.
  3. 단항 연산자와 대입 연산자를 제외한 모든 연산의 진행방향은 왼쪽에서 오른쪽이다.

Java 13. switch 연산자

자바12부터 Preview로 추가되었다.

기존의 switch문에서의 :를 ->로 변경하여 사용할 수 있으며, break문을 생략할 수 있다.

switch(n) {
    case 1 -> System.out.println("one");
    case 2 -> System.out.println("two");
    default -> System.out.println("many");
}

expression으로 사용될 수 있어 변수로 할당 할 수 있다.

String result = switch(n) {
    case 1 -> System.out.println("one");
    case 2 -> System.out.println("two");
    default -> System.out.println("many");
}

yield를 이용하여 값을 리턴할 수 있다.

String result = switch(n) {
    case 1 -> {
      System.out.println("one");
        yield 1;
    }
    case 2 -> {
      System.out.println("two");
      yield 2;
    }
    default -> {
      System.out.println("many");
      yield 100;
    }
}

참고 자료

댓글