본문 바로가기
java-liveStudy

3주차 과제. 연산자

by 에드박 2020. 11. 26.

목표

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


학습할 것

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

< 용어 정의 >

연산(operation) : 프로그램에서 데이터를 처리하여 결과를 산출하는 것

연산자(operator) : 연산을 수행하는 기호 (+, -, *, / 등)

피연산자(operand) : 연산자의 작업 대상  (변수, 상수, 리터럴, 수식)

식(expression) : 연산자와 피연산자를 조합하여 계산하고자하는 바를 표현한 것

식을 평가한다 : 식을 계산하여 결과를 얻는 것

 

< 연산자의 종류 >

종류 연산자 설명
산술 연산자 +  ,  -  ,  *  ,  /  ,  % 사칙연산(+,-,/,%)과 나머지 연산(%)
관계(비교) 연산자 >  ,  <  ,  >=  ,  <=  ,  ==  ,  !=
instanceof
크고 작은과 같고 다름을 비교
논리 연산자 &&  ,  ||  ,  !  ,  &  ,  |  ,  ^ '그리고(AND)' 와 '또는(OR)'으로 조건을 연결
비트 연산자 &  ,  |  ,  ^  ,  ~  ,  <<  ,  >> 우변의 값을 좌변에 저장
대입 연산자 =  ,  op= 우변의 값을 좌변에 저장
('op='은  다른 연산자와 결합한 복합 대입 연산자)
형변환 연산자 (type) 타입 형변환(캐스팅)을 위한 연산자
조건 연산자 (조건식) ? 식1 : 식2 조건식이 true면 식1이, false면 식2가 연산결과가 된다.
(유일한 3항 연산자)

 

 <피연산자의 개수에 의한 분류>

단항 연산자 : 피연산자의 개수가 하나 (++, --, 부호연산자 +, -)

이항 연산자 : 피연산자의 개수가 두개 (+, -, *, 등등)

삼항 연산자 : 피연산자의 개수가 세개 ( 조건 연산자 (조건식) ? A : B)

 

<산술 변환>

연산 수행 직전에 발생하는 피연산자의 자동 형변환을 의미합니다.

이항 연산자는 두 피연산자의 타입이 일치해야 연산이 가능하므로 타입이 서로 다르다면 형변환 연산자로 타입을 일치시켜야합니다. 

 

산술 변환의 규칙은 다음과 같습니다.

 

  • 두 피연산자의 타입을 같게 일치시킨다.(보다 큰 타입으로 일치)
/*
long + int    ->    long + long    -> long
float + int   ->   float + float   -> float
double + float->  doutble + double -> double
*/

int a = 10;
long b = 20L;
float c = 1.0f;

long num = a + b;  // a는 int 타입이지만 산술 변환에 의해 보다 큰 타입인 long 타입으로 변환됩니다.

float f = a + c; // a는 int 타입이지만 산술 변환에 의해 보다 큰 타입인 float 타입으로 변환됩니다.
System.out.println(f); // 11.0

 

(a + b) -> a는 산술 변환에 의해 보다 큰 타입인 long 타입으로 변환됩니다.

(a + c) -> a는 i산술 변환에 의해 보다 큰 타입인 float 타입으로 변환됩니다.

 

보다 큰 타입으로 일치시키는 이유는 피연산자의 값손실을 최소화하기 위함입니다.

 

  • 피연산자의 타입이 int보다 작은 타입이면 int로 변환됩니다.

 

/*
byte + short   ->   int + int    -> int
char + short   ->   int + int    -> int
*/

byte a = 10;
byte b = 20;
short c = 30;

System.out.println(Integer.class.isInstance(a + b));
System.out.println(Integer.class.isInstance(a + c));
// 둘다 출력 결과는 true

 

정수형의 기본 타입인 int가 가장 효율적으로 처리할 수 있는 타입이기 때문에, 그리고 int 보다 작은 타입(short, char등)의 표현 범위가 좁아서 연산 중에 overflow가 발생할 가능성이 높기 때문에 만들어진 규칙이라고 합니다.

 

 


산술 연산자

산술연산자에는 사칙연산(덧셈, 뺄셈, 나눗셈, 곱셈) 과 나머지 연산자(%) 가 있습니다.

일반적인 수학에서의 계산 규칙과 같습니다.. (예를 들어 곱셈이 더하기 보다 우선순위)

 

int a = 10;
int b = 4;

System.out.println(a + b); // 14
System.out.println(a - b); // 6
System.out.println(a * b); // 40
System.out.println(a / b); // 3
System.out.println(a % b); // 2

System.out.println((float)a+(float)b); // 2.5

 

자바에서의 사칙연산중 +, -, *, 나머지연산 까지는  우리가 평소에 사용하는 수학의 개념과 같습니다.

하지만 나눗셈의 경우 정수형을 사용했을 때 몫을 반올림하여 10 / 4 의 값으로 3을 반환하는걸 볼 수 있습니다.

 

실수형을 사용했을 때는 소수점까지 출력해줍니다.

 

<주의사항>

  • 피연산자가 정수형인 경우, 나누는 수로 0을 사용할 수 없습니다.

 

int a = 10;
System.out.println(a / 0);  // 에러발생!!

 

위와같이 int 타입의 a를 0으로 나누는 경우 컴파일은 정상적으로 되지만 실행 시 오류 ArithmeticException이 발생합니다.

 

int b = 10;
System.out.println(b / 0.0f);  // 정상 실행 infinity 출력

float f = 10.0f;
System.out.println(f / 0); // 정상 실행 infinity 출력

 

위와같이 부동 소수점 0.0f 로 나눌 경우에는 정상 실행되며 infinity(무한대)를 출력합니다.

 

  • int 보다 작은 타입의 연산 결과는 int로 산술변환 되기 때문에 연산 결과를 대입할 때 주의해야합니다.

 

byte a = 10;
byte b = 200;

byte c = a + b; // 에러발생! a + b 는 int 타입값을 반환하기 때문에 byte타입의 변수인 c에 대입할 수 없습니다.

 

위에서 byte타입의 변수 c에 대입할 때 에러가 발생합니다. 그 이유는 a + b 를 연산할 때 산술 변환 규칙에 의해서 int 타입으로 변환하기 때문입니다. 따라서 형변환 연산자를 이용해서 

 

byte c = (byte)(a + b);

 

위와 같이 해야 정상적으로 결과를 byte타입 변수에 저장할 수 있습니다.

 

  • 연산 결과가 해당 타입의 표현범위를 넘어서면 올바른 결과를 얻을 수 없습니다.

 

int a = 1_000_000;
int b = 1_000_000;

System.out.println(a * b);   // -727379968 출력

 

a * b값을 출력해보면 -727379968이 나옵니다.

표현 범위를 넘어서서 오버플로우가 발생했기 때문입니다.

만약 long 타입으로 형변환하여 (long)(a * b) 와같이 표현하더라도 결과는 같습니다. 이미 (a * b)에서 오버플로우가 발생한 값을 반환하기 때문에 long 타입으로 반환해도 소용이 없습니다.

 

정상적인 값을 얻기 위해서는 피연산자를 표현범위에 맞는 타입으로 사용해야합니다.

 

long a = 1_000_000L;
long b = 1_000_000L;

System.out.println(a * b); // 1000000000000 출력

비트 연산자

비트 연산자는 피연산자를 비트단위로 논리 연산합니다. 피 연산자를 이진수로 표현했을 때 각 자리에 대해 연산을 수행합니다. 

피연산자로는 실수는 허용하지 않고 정수만 허용합니다.

| (OR연산자) : 피연산자 중 한 쪽이라도 값이 1이면 1을 결과로 얻습니다. (그 외는 0)

& (AND연산자) : 피연산자 양 쪽 모두 1일때만 1을 결과로 얻습니다. (그 외는 0)

^ (XOR 연산자) : 피연산자의 값이 서로 다를때만 1을 결과로 얻습니다. (같을 때는 0)

~(비트 전환 연산자) : 단항 연상자로 피연산자의 값이 0이면 1로, 1이면 0으로 바꿉니다. (1의 보수 연산자)

<<, >> (쉬프트 연산자) : 각 자리를 쉬프트 연산자의 방향으로 이동합니다. (<< 왼쪽, >> 오른쪽)

 

| (OR연산자)
x y x | y
1 1 1
1 0 1
0 0 0
1 1 1

둘 중 한쪽이 1이면 1을 반환

 

& (AND연산자)
x y x & y
1 1 1
1 0 0
0 0 0
1 1 1

둘다 1이어야 1을 반환

 

^ (XOR연산자)
x y x ^ y
1 1 0
1 0 1
0 0 0
1 1 1

값이 서로 다를때만 1을 반환 (같으면 0을 반환)

 

~ (비트 전환 연산자)
x ~x
1 0
1 0
0 1
1 0

피연산자의 값이 1이면 0으로, 0이면 1로 바꿔줍니다.

 

 

<< , >> : 쉬프트 연산자

 

왼쪽 피연산자를 2진수로 표현했을 때 각 자리를 쉬프트 연산자의 방향으로 오른쪽 피연산자 만큼 이동시킵니다.

예를 들어 8 << 2 라면

0 0 0 0 1 0 0 0

8의 이진수를

0 0 1 0 0 0    

위 처럼 왼쪽으로 두칸씩 이동시킵니다. 가장 왼쪽의 두자리는 버려지며 오른쪽의 빈자리는 0으로 채워집니다.

0 0 1 0 0 0 0 0

<< 연산자의 경우 피연산자의 부호(+, -)에 상관없이 빈자리를 0으로 채우지만 >> 연산자는 오른쪽으로 이동시키기 때문에 음의 정수 일경우 부호를 유지하기 위해 빈자리를 1로 채워넣습니다.

 

쉬프트 연산자에서는 int 보다 작은 타입이 사용됐을 때 좌측 피연산자는 int 타입으로 자동 형변환 되며

우측 피연산자는 타입을 일치시킬 필요가 없어서 산술변환이 적용되지 않습니다.

 

 

X << n 은 x * 2ⁿ 과 같고

X >> n 은 x / 2ⁿ 과 같습니다.


관계 연산자

두 피연산자가 서로 크고 작음과 같음을 비교하는 데 사용되는 연산자입니다. 

연산결과는 참이면 true 거짓이면 false 둘 중 하나를 반환합니다.

 

비교연산자 연산결과
< 좌변 값이 작으면 true 아니라면 false
> 좌변 값이 크면 true 아니라면 false
<= 좌변 값이 작거나 같으면 true 아니라면 false
>= 좌변 값이 크거나 같으면 true 아니라면 false
== 좌변과 우변이 같다면 true 다르면 false
!= 좌변과 우변이 다르면 true 같으면 false

 

System.out.println(10 < 20);   // true
System.out.println(20 < 10);   // false

System.out.println(20 > 10);   // true
System.out.println(10 > 20);   // false

System.out.println(10 <= 10);   // true
System.out.println(20 <= 10);   // false

System.out.println(10 >= 10);   // true
System.out.println(10 >= 20);   // false

System.out.println(10 == 10);   // true
System.out.println(10 == 20);   // false

System.out.println(10 != 20);   // true
System.out.println(20 != 20);   // false

 


논리 연산자

둘 이상의 조건을 연결하여 하나의 식으로 표현할 수 있게 해줍니다.

피연산자로 boolean형 또는 boolean형 값을 결과로 반환하는 조건식만이 가능합니다.

 

&& : AND 에 해당하며 두 피연산자가 모두 true 일 때만 true를 반환합니다. (이외에는 false)

||    : OR에 해당하며 두 피연산자중 어느 한쪽이라도 true라면 true를 반환합니다. (둘다 false라면 false를 반환)

!    : 논리 부정 연산자로 단항 연산자입니다. true면 false를 반환하고 false면 true를 반환합니다.

 

논리연산자는 효율적인 연산을 하는데 OR의 경우 어느 한쪽만 true라면 전체 결과가 true이므로 좌측 피연산자가 true라면 우측 피연산자는 평가하지 않습니다.

AND의 경우 좌측 피연산자가 false라면 전체 결과가 false이므로 우측 연산자를 평가하지않고 false를 반환합니다.

 

x y x || y  x && y
true true true true
true false true false
false true true false
false false false false

 

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

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

System.out.println(!true);   // false
System.out.println(!false);  // true

 


instanceof

 

레퍼런스 타입의 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용하는 연산자입니다.

다음과 같이 사용할 수 있습니다.

(레퍼런스 타입의 참조변수) instanceof (레퍼런스 타입)

참조변수의 인스턴스 타입이 레퍼런스 타입과 같다면 true, 다르다면 false를 반환합니다.

instanceof 의 반환값이 true라는것은 검사한 타입으로 형변환이 가능하다는 것입니다.

이를 이용해서 형변환이 가능한지 체크하기 위해 주로 사용됩니다.

 

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

        if (child instanceof Child) {
            System.out.println("child 참조변수는 Child 타입이다.");
        }

        if (child instanceof Parent1) {
            System.out.println("Parent1 p = new Child(); 가능");
        }

        if (child instanceof Grandparent) {
            System.out.println("Grandparent gp = new Child(); 가능");
        }

    }
}

class Grandparent { }
class Parent1 extends Grandparent{ }
class Child extends Parent1{ }

 


assignment(=) operator

<'=' 대입연산자>

'=' 연산자는 우리가 평소 수학에서 같다 라는 의미지만 프로그래밍에서는 오른쪽 피연산자를 왼쪽 피연산자에 저장한다고 하여 대입 연산자라고 합니다. 이것을 우리는 2주차에 배웠던 '값을 초기화한다' 라고 합니다.

 

 

오른쪽 피연산자는 변수나 상수, 식, 리터럴 모두 가능하지만

왼쪽 피연산자는 변수처럼 값을 변경할 수 있는 것이어야합니다.

 

int a = 30; // int타입의 변수 a에 30을 대입합니다.(int타입의 변수 a를 30으로 초기화합니다.)

// 3 + 1 = 4; // !에러. 왼쪽 피연산자에 리터럴이 올 수 없습니다.
// a + 1 = 30; // !에러. a + 1은 31로 리터럴이 되므로 불가능합니다.

 

<복합 대입 연산자>

'=' 연산자는 다른 연산자와 결합하여 식을 간결하게 표현할 수 있습니다.

표현 방법은 '(다른 연산자)=' 과 같이 표현하며 '+=', '-=', '*=', '/=', '%=' 처럼 사용할 수 있습니다.

'a = a + 1'을 'a += 1' 로 표현할 수 있습니다.

 

자기 자신에 대한 식을 연산하여 값을 저장할 때 사용합니다.

 

대입 연산자 복합 대입 연산자
a = a + 5 a += 5;
a = a - 5; a -= 5;
a = a * 5; a *= 5;
a = a / 5; a /= 5;
a = a % 5;  a %= 5;
a = a << 5; a <<= 5;
a = a >> 5; a >>= 5;
a = a & 5; a &= 5;
a = a | 5; a |= 5;
a = a ^ 5; a ^= 5;
a = a + (5 + b) a += (5 + b)

 


화살표(->) 연산자

 

 '->' 연산자는 Java8(JDK1.8)부터 추가된 람다식에서 사용되는 연산자입니다.

람다식은 메서드를 하나의 '식'으로 표현한 것으로 메서드를 간략하게 표현할 수 있게 해줍니다.

 

메서드를 람다식으로 표현하게 되면 메서드의 반환타입과 이름이 없어지므로 '익명 함수' 라고도 부릅니다.

 

-일반적인 메서드
[반환 타입] [메서드이름] (매개변수) {
    실행할 문장들
}

- 람다식을 적용한 메서드
[반환 타입] [메서드이름] (매개변수) -> {
    실행할 문장들
}

'->' 연산자의 왼쪽에는 소괄호'( )' 안에 매개변수를 선언하고 오른쪽에는 중괄호'{ }' 안에 매개변수를 받아서 실행할 문장들을 선언할 수 있습니다.

 

int a = 10;
int b = 20;

public void printSumResult(int a, int b) {
    System.out.println(a + b);
} // 일반적인 메서드

(int a, int b) -> { System.out.println(a + b); }
// 람다식을 사용한 메서드

 

람다식 작성에는 많은 규칙들이 있는데 자세한것을 다루려면 너무 길어지기 때문에 15주차 과제에서 람다식에 대해 정리할 때 다루도록 하겠습니다.

 

 


3항 연산자

자바에는 단 하나의 3항 연산자가 존재하는데 그것이 조건연산자 " ? : " 입니다.

조건 연산자는 (조건식, 식1, 식2) 3개의 피연산자가 필요한 식으로 다음과 같이 표현합니다.

 

조건식 ? 식1 : 식2

조건식이 true 라면 식1을 반환하고, false 라면 식2를 반환합니다.

 

조건식을 사용하면 if else 문을 간단하게 표현할 수 있는데 문장이 길지 않은 if else 문이라면 사용해도 괜찮지만 문장이 길어지면 알아보기 힘든 단점이 있습니다.

아래 예제의 if else 문과 3항 연산자는 같은 결과를 반환합니다.

int a = 5;
int b = -10;

String resultA;
String resultB;

if (a > 0) {
    resultA = "+" + a;  // "+5"
} else {
    resultA = "-" + a;  // "-5"
}

resultA = a > 0 ? "+" + a : "-" + a;

 

조건 연산자를 중첩해서 사용할 수 있는데

다음과 같이 사용할 수 있습니다.

resultA  = a > 0 ? "+" + a : (a == 0 ? "0" : "-" + a);

a > 0 이 false일 때 조건 연산자를 다시 사용

여러 번 중첩하면 if else문을 중첩하는것 보다 코드는 간단하지만 가독성이 떨어지므로 꼭 필요할 때만 사용하거나 if else 문을 사용하는것을 추천합니다.

 


연산자 우선 순위

 

수학의 사칙연산에서 하나의 식에 '+' 와 '*' 가 함께 사용됐을 때 곱셈의 우선 순위가 더 높듯이 프로그래밍의 연산에도 우선 순위가 있습니다.

 

연산자의 우선 순위는 다음과 같은 규칙이 있습니다.

1. 산술 > 관계(비교) > 논리 > 대입.
2. 단항(1) > 2항 > 3항
3. 단항 연산자와 대입 연산자는 연산의 진행 방향이 오른쪽에서 왼쪽(<--) 이고 나머지는 왼쪽에서 오른쪽(-->)

3번 항목은 연산자의 '결합 규칙' 이라고 하는데 연산을 진행하는 방향을 의미합니다.

 

예를 들어 '3 + 2 - 5' 가 있다면 '2 - 5' 부터 계산하는것이 아닌 '3 + 2' 부터 계산을 진행합니다.

 

대입 연산자는 식의 결과를 저장하므로 가장 마지막에 연산됩니다.

 

종류 결합 규칙 연산자 높음


|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|


낮음
단항 연산자 <-------- ++  --  +(양수 부호)  -(음수 부호)   ~  !  (type)
산술 연산자 --------> *  /  %
--------> +  -
--------> <<  >>
관계(비교) 연산자 --------> <  >  <=  >=  instanceof
--------> ==  !=
논리 연산자 --------> &
--------> ^
--------> |
--------> &&
--------> ||
삼항 연산자 --------> ? :
대입 연산자 <-------- =  +=  -=  *=  /=  %=

표 출처 : 자바의 정석(도우 출판)

 

"++" "--" 는 증감 연산자로 boolean타입을 제외한 기본 자료형 변수를 피연산자로 받습니다.
피연산자의 앞이나 뒤에 붙여서 값을 1 증가 시키거나 감소시킵니다.
증감 연산자는 위치에 따라 연산되는 결과가 다른데
피연산자의 앞(++a)에 위치하면 1을 증가 시킨 후 연산을 수행합니다.
피연산자의 뒤(a++)에 위치하면 연산을 수행한 후에 a의 값을 1을 증가 시킵니다.

(optional) Java 13. switch operator

 

switch 문은 단 하나의 조건식으로 많은 경우의 수를 처리할 수 있고 표현도 간결하여 알아보기 쉽다는 장점이 있습니다.

switch문이 java12에서 한번 바뀌고 java13에서 유저들의 피드백을 받아 다시한번 바뀌었다고 합니다.

 

java12이전에 사용하던 switch문은 다음과 같습니다.

switch (조건식) {
    case 값1 :
        값1과 조건식이 일치할 때 실행할 문장
        ...
        break;
    case 값2 :
        값1과 조건식이 일치할 때 실행할 문장
        ...
        break;
    default :
        조건식들과 일치하는 값이 없을 때 실행할 문장
        ...
}        

 

java 12부터는 switch 문에 '->' 연산자를 사용할 수 있게 됐습니다. 

 

switch (조건식) {

    case 값1 -> 조건식과 값1이 일치할 때 실행할 문장이나 반환할 값;
    case 값2, 값3 -> {
        조건식과 값2, 값3이 일치할 때 실행할 문장이나 반환할 값;
    }
    default -> 조건식과 일치하는 값이 없을때 실행할 문장이나 반환할 값;
}        

 

java 12에서는 break with value문을 사용해서 break에 값을 반환할 수 있게 했습니다.

switch (조건식) {
    case 값1 :
        값1과 조건식이 일치할 때 실행할 문장
        ...
        break 반환값;
    case 값2 :
        값1과 조건식이 일치할 때 실행할 문장
        ...
        break 반환값;
    default :
        조건식들과 일치하는 값이 없을 때 실행할 문장
        ...
        break 반환값;
}        

java 13에서는 break 대신 yield로 바뀌게 됐습니다.

switch (조건식) {
    case 값1 :
        값1과 조건식이 일치할 때 실행할 문장
        ...
        yield 반환값;
    case 값2 :
        값1과 조건식이 일치할 때 실행할 문장
        ...
        yield 반환값;
    default :
        조건식들과 일치하는 값이 없을 때 실행할 문장
        ...
        yield 반환값;
}        

 


참조

- 자바의 정석(도우 출판)

- colossus-java-practice.tistory.com/22

- openjdk.java.net/jeps/354(JEP 354: Switch Expressions)

- blog.naver.com/PostView.nhn?blogId=kgw1988&logNo=221678438564&parentCategoryNo=&categoryNo=50&viewDate=&isShowPopularPosts=true&from=search

'java-liveStudy' 카테고리의 다른 글

6주차 과제. 상속  (0) 2020.12.25
5주차. 과제  (0) 2020.12.17
5주차. 클래스  (0) 2020.12.16
4주차 과제. 제어문 + 과제  (0) 2020.12.04
2주차 과제. 자바의 프리미티브 타입, 변수 그리고 배열  (0) 2020.11.21

댓글