[Live Study 3주차] 연산자

이호석·2022년 7월 6일
0

자바에서 사용되는 다양한 연산자를 알아보고 활용해보자!

목표

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

학습할 것

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



시작하기에 앞서 간단한 연산에 관련된 용어를 정리해보자

  • 연산자: 연산을 진행할 때 사용되는 기호
  • 피연산자: 연산될 대상으로 변수, 상수, 리터럴 등을 의미
  • 연산이란?: 주어진 정보를 통해 일정한 규칙에 따라 어떤 값이나 결과를 구하는 과정

1. 산술 연산자

오라클 docs에서 정의하는 자바의 산술 연산자는 다음과 같다.

OperatorDescription
+덧셈 연산
-뺄셈 연산
*곱셈 연산
/나눗셈 연산
%나머지 연산

사칙연산이 포함되며 나머지 연산이 실세계의 연산과 조금 다르다.
나머지 연산은 피연산자를 특정 수로 나눈 나머지 값을 계산해준다.

public class Arithmetic {

    public static void main(String[] args) {
        int a = 10;
        int b = 3;
        double c = 10.0;
        double d = 3.0;


        System.out.println("정수 연산");
        System.out.println("a + b = " + (a + b));
        System.out.println("a - b = " + (a - b));
        System.out.println("a * b = " + (a * b));
        System.out.println("a / b = " + (a / b));
        System.out.println();

        System.out.println("실수 연산");
        System.out.println("c + d = " + (c + d));
        System.out.println("c - d = " + (c - d));
        System.out.println("c * d = " + (c * d));
        System.out.println("c / d = " + (c / d));
        System.out.println("c % d = " + (c % d));
        System.out.println();
        
        System.out.println("정수 및 실수 연산");
        System.out.println("a + d = " + (a + d));
        System.out.println("a - d = " + (a - d));
        System.out.println("a * d = " + (a * d));
        System.out.println("a / d = " + (a / d));
        System.out.println("a % d = " + (a % d));
        
        [실행 결과]
        정수 연산
        a + b = 13
        a - b = 7
        a * b = 30
        a / b = 3

        실수 연산
        c + d = 13.0
        c - d = 7.0
        c * d = 30.0
        c / d = 3.3333333333333335
        c % d = 1.0

        정수 및 실수 연산
        a + d = 13.0
        a - d = 7.0
        a * d = 30.0
        a / d = 3.3333333333333335
        a % d = 1.0
    }
}

각 자료형의 종류마다 연산되는 범위가 달라짐을 알 수 있다.
정수의 연산의 경우 10 / 3을 하게 되면 소수점 아래 자리는 버리게 되고, 실수의 연산에선 소수점 자리까지 연산하게 된다.

마지막 정수 및 실수의 연산은 이전 시간에 배웠던 캐스팅과 프로모션을 살펴보면 알 수 있다.
double 과 int의 계산에서 Newmeric Promotions이 적용되어 int 타입도 double로 변환되어 계산됐음을 알 수 있다.

지금까지의 연산은 모두 이항 연산자이다. 즉 두 개의 피연산자와 한 개의 연산자를 통해 계산한다. Java에서는 덧셈 및 뺄셈은 단항연산자로 나타낼수 도 있다.

int a = 0;

// ++ 연산자
a++;
System.out.println(a);
++a;
System.out.println(a);

System.out.println(a++);
System.out.println(++a);

// -- 연산자
System.out.println(a--);
System.out.println(--a);

[출력]
1
2
2
4
4
2

각 증감연산자들의(a++, a-- 및 ++a, --a) 차이가 보이는가?
a++, a--와 같이 변수 뒤에 증감 연산이 붙는 경우는 변수에 대한 다른 연산을 먼저 하고, 해당 변수의 값을 증가 또는 감소한다.

++a, --a와 같이 변수 앞에 증감 연산이 붙는 경우는 해당 변수의 증감을 먼저 시행하고, 해당 변수에 대해 연산을 실행한다.


2. 비트 연산자

비트 연산자는 피연산자를 비트 단위로 논리 연산한다. 모든 정수 유형에 적용할 수 있지만, 정수에서만 사용 가능한 연산자다.

OperatorsDescription
&AND 연산, 피연산자 중 양쪽의 값 모두 1이어야 결과로 1을 얻음, 그 외에는 0
|OR 연산, 피연산자 중 하나의 값이 1이면 결과로 1을 반환, 둘 다 0이라면 0
^XOR 연산, 피연산자의 값이 서로 다를때만 1을 반환, 그 외에는 0
~NOT 연산, 현재 피연산자의 모든 비트값들을 반전시킨다.
<<SHIFT 연산, 비트 값을 주어진 숫자만큼 왼쪽으로 이동시킨다.
>>SHIFT 연산, 비트값을 주어진 숫자만큼 오른쪽으로 이동시킨다.
>>>SHIFT 연산, 비트 값을 주어진 숫자 만큼 오른쪽으로 이동 시킨 후 빈공간을 모두 0으로 채운다.
static class BitwiseAndBitShiftOperators {
	public static void main(String[] args) {

        byte a = 9;
        byte b = 5;
		// a: 0000 1001
        // b: 0000 0101
        
		System.out.println("Bitwise and Bit Shift");
		System.out.println("a & b: " + (a & b));
        // 0000 1001 AND 0000 0101 = 0000 0001
		
        System.out.println("a | b: " + (a | b));
        // 0000 1001 OR 0000 0101 = 0000 1101

		System.out.println("a ^ b: " + (a ^ b));
        // 0000 1001 XOR 0000 0101 = 0000 1100

        
		System.out.println("a << b: " + (a << b));
        // 0000 1001 << 5 =00000000 00000000 00000001 00100000 
        // (Newmeric Promotions이 적용되어 결과가 int형으로 프로모션됨)
        
		System.out.println("a >> b: " + (a >> b));
        // 0000 1001 >> 5 = 00000000 00000000 00000000 00000000
        
		System.out.println("a >>> b: " + (a >>> b));
        // 0000 1001 >> 5 = 00000000 00000000 00000000 00000000
    }
}
[실행 결과]
Bitwise and Bit Shift
a & b: 1
a | b: 13
a ^ b: 12
a << b: 288
a >> b: 0
a >>> b: 0

3. 관계 연산자

자바와 관계연산자는 다음과 같다.

OperatorsDescription
==좌항과 우항이 같음
!=좌항과 우항이 같지 않음
>좌항이 우항을 초과함(혹은 우항이 좌항 미만)
>=좌항이 우항보다 크거나 같음
<좌항이 우항 미만
<=우항이 좌항보다 크거나 같음

관계연산자는 결과값을 논리값을 반환한다.

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

        System.out.println((a == b) + " " + (a == c));
        System.out.println((a != b) + " " + (a != c));
        System.out.println((a > b) + " " + (a > c));
        System.out.println((a >= b) + " " + (a >= c));
        System.out.println((a < b) + " " + (a < c));
        System.out.println((a <= b) + " " + (a <= c));
    }
}

[출력결과]
true false
false true
false false
true false
false true
true true

4. 논리 연산자(조건 연산자)

논리 연산자는 비트연산과 비슷하지만 피연산자가 비트가 아니라 boolean 타입의 논리 값이다.
두개의 부울 식에 대해 조건부 AND 혹은 조건부 OR 연산을 수행하고, !는 논리적인 부정을 뜻한다.

OperatorsDescription
&&AND, 양쪽 모두 true일때 true, 그 외엔 false
||둘 중 하나만 true여도 true, 둘 다 false면 false
!true면 false를 false면 true 반환(논리 부정)

&와 &&, |와 ||는 같은 결과를 반환하지만 차이점이 존재한다.
&는 앞의 조건식이 false여도 뒤의 조건식을 판별하고, &&는 앞이 false라면 뒤의 조건식을 판별하지 않는다.
|또한 앞의 조건식이 true여도 뒤의 조건식을 판별하고, ||는 앞이 true면 뒤를 판별하지 않는다.

boolean var1 = true;
boolean var2 = false;

System.out.println(var1 && var2);
System.out.println(var1 || var2);
System.out.println(var1 && !var2);

[출력]
false
true
true

5. instanceof

instancof 연산자는 객체를 지정된 유형과 비교하는 연산자다.
주로 객체가 클래스의 인스턴스인지, 하위 클래스의 인스턴스인지, 특정 인터페이스를 구현하는 클래스의 인스턴스인지 테스트하는데 사용된다. 아래의 예시를 보면 좀 더 쉽게 이해할 수 있다.

class InstanceofDemo {
    public static void main(String[] args) {

        Parent obj1 = new Parent();
        Parent obj2 = new Child();

        System.out.println("obj1 instanceof Parent: "
            + (obj1 instanceof Parent));
        System.out.println("obj1 instanceof Child: "
            + (obj1 instanceof Child));
        System.out.println("obj1 instanceof MyInterface: "
            + (obj1 instanceof MyInterface));
        System.out.println("obj2 instanceof Parent: "
            + (obj2 instanceof Parent));
        System.out.println("obj2 instanceof Child: "
            + (obj2 instanceof Child));
        System.out.println("obj2 instanceof MyInterface: "
            + (obj2 instanceof MyInterface));
    }
}

class Parent {}

// Child 클래스는 Parent 클래스와 MyInterface 인터페이스를 상속받고, 구현하고 있다.
class Child extends Parent implements MyInterface {}
interface MyInterface {}

[출력]
obj1 instanceof Parent: true
obj1 instanceof Child: false
obj1 instanceof MyInterface: false

// Child 클래스는 우항의 것들을 모두 구현하고 있으므로 instanceof가 참임
obj2 instanceof Parent: true
obj2 instanceof Child: true
obj2 instanceof MyInterface: true

instanceof를 사용할때는 null은 어떤 것의 인스턴스도 아님을 주의하고 null을 검사하지 않도록 주의해야 한다.

만약 instanceof의 결과가 참일경우 우항 타입의 참조변수에 좌항 타입의 객체를 할당할 수 있다.

interface AA {}
class BB implements AA {}
.
.
.
main 메소드
AA aa = new BB(); // 가능, BB instanceof AA가 참이므로

6. assignment(=) operator

assignment연산자는 대입 연산자(할당 연산자)로 특정 변수에 값을 초기화할때 이용되는 연산자이다.
수학에서의 등호와 같은 기호이지만, 자바에서는 같다라는 뜻이 아니다. 대입 연산자 기준, 우항의 값을 좌항의 변수에 대입하겠다는 의미이다.

실세계에선 A = 10는 A와 10이 같다라는 말이지만, 자바에선 10이라는 값을 A변수에 대입하겠다 라는 말이 된다.

int A = 10;
int B = A;
int C = A + B;

우항에 꼭 값이 있어야 하는게 아니라 이미 초기화 되어있는 변수가 들어올 수 있다.
기본형의 대입 연산일 경우 우항의 변수의 데이터가 좌항의 변수에도 초기화 되고, 두 변수는 같은 값을 가진다.(하지만 변수와 할당 값은 서로 독립적임)

위의 예시에선 A == 10, B == 10, C == 10 + 10 == 20이 된다.

참조형 변수에서 대입연산자를 이용하면 참조형 변수가 가지고 있는 실제 객체의 주소가 복사되는데 이는 결국 같은 주소값을 두 개의 변수가 참조하고 있는 상황이 된다.(얕은 복사)

public class Assignment {
    public static void main(String[] args) {
        int a = 10;
        int b = a;
        int c = a + b;
        a = 20;
        System.out.println(a + " " + b + " " + c);
		
        System.out.println();
        
        int[] arr = new int[]{1, 2, 3};
        int[] arr2 = arr;
        arr2[0] = 5;
        arr[2] = 10;
        System.out.println(arr[0] + " " + arr[1] + " " + arr[2]);
        System.out.println(arr2[0] + " " + arr2[1] + " " + arr2[2]);
        
    }
}
[출력]
20 10 20

5 2 10
5 2 10

기본형의 대입 연산의 경우 a의 값이 20으로 변경되어도 b의 값은 초기 a의 값인 10이 유지된다.
하지만, 참조형의 경우 arr, arr2 변수가 모두 같은 주소의 배열을 참조하고 있으므로, 배열의 값을 변경하게 되면 같은 배열이 수정됨을 알 수 있다.


7. 화살표(->) 연산자

Java 8 버젼에서 람다식이 등장하면서 화살표 연산자가 도입됐습니다.
화살표 연산자는 람다식에서 인수와 본문을 구분하는 역할을 합니다.

이를 통해 자바의 익명 클래스를 좀 더 쉽게 사용할 수 있게 해줍니다.

익명 클래스는 이름이 없는 클래스로 클래스의 정의와 동시에 객체를 생성할 수 있다. Interface, Class 모두 익명 클래스로 객체를 만들 수 있다.
믹명 클래스는 프로그램 내에서 한 번만 사용되는 클래스를 굳이 정의 할 필요가 없기 때문이다.

public class Practice {
    public static void main(String[] args) {

        // 익명 클래스
        Runnable r1 = new Runnable() {
        
            @Override
            public void run() {
                System.out.println("run1");
            }
        };

        r1.run();

        // 익명 클래스를 화살표 연산자를 이용한 람다 
        Runnable r2 = () -> System.out.println("run2");

        r2.run();
    }
}

// 추상 메서드가 오직 하나인 인터페이스
// default, static method는 여러개 있을 수 있음
@FunctionalInterface
interface Runnable {
    public void run();
}

[출력]
run1
run2

위와같이 익명 클래스를 (인수) -> {본문}; 와 같이 간단하게 사용할 수 있다. 다만 위와같이 사용하기 위해서는 반드시 @FunctionnalInterface의 조건을 만족해야 한다.

여기서 화살표 연산자는 인수와 본문을 구분해주는 역할을 한다.


8. 3항 연산자

자바에서 3항 연산자는 다음과 같이 구성된다.
조건식 ? 값1 or 연산식1 : 값2 or 연산식2

만약 true false를 판단할 수 있는 조건식이 참이라면 값1 or 연산식1을 선택하고, false라면 값2 or 연산식2를 선택한다.

여기서 값 or 연산식으로 말하는 이유는 특정 값이 들어가는 경우가 있고, 또 연산식, 수식, 함수 호출 등 여러가지 형태의 명령문이 올 수 있다.

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

        c = (a > 10) ? b : a;
        System.out.println(c);

        if (a > 10) {
            c = b;
        } else {
            c = a;
        }
        System.out.println(c);
    }
}

위에서 5줄의 if - else문이 삼항 연산자를 이용하면 단 한줄로 표현되는것을 알 수 있다.

이렇게 삼항 연산자는 간단한 판별식을 이용할때 사용하면 효과가 좋지만, 반대로 복잡한 로직을 삼항 연산자를 이용하게 되면 가독성이 저하되므로 적절하게 판단하여 사용하는것이 바람직하다.


9. 연산자 우선 순위


위의 사진은 Oracle Docs에서 정의한 연산자 우선 순위의 표이다.
단항 연산자, 부정의 연산이 가장 높은 우선순위를 가지고, 실제 수학에서의 연산과 동일하게 곱셈, 나눗셈이 덧셈 뺄셈보다 높은 우선순위를 가진다.

모든 연산자 우선순위를 외우는것도 좋지만, 가장 많이 사용되는 사칙 연산들의 순서는 외우고 있는것이 좋다고 생각한다.

연산자 우선순위가 헷갈린다면 가장 확실한 방법은 우선 수행되어야 하는 연산식을 (, )로 감싼다. 이렇게 하면 감싸진 연산이 가장 먼저 수행되므로 원하는 순서대로 연산을 수행할 수 있다.

연산자의 결합 규칙
만약 동일 우선순위의 연산자들이 나열되어 있다면 좌측부터 순서대로 계산된다.

int a = 10 + 20 - 5 // 10 + 20 먼저 계산 -> 30 - 5 계산
System.out.println(a);

[출력]
25

10. Java 13, switch 연산자

Java 12, 13에서 새로운 switch 연산이 등장했다 먼저 기존의 switch연산을 살펴보자

[기존의 switch]
 switch (number) {
 	case 1:
    case 2:
    	result = "one or two";
        break;
    case 3:
    	result = "three";
        break;
    case 4:
    case 5:
    case 6:
    	result = "four or five or six";
        break;
    default:
    	result = "unknown";
};

Java 12에선 여러개의 case: 라벨의 조건들을 쉼표를 이용해 나열할 수 있고, break를 통해 값을 반환할 수 있다.

또한 case: 대신 case -> 와 같이 화살표 연산자를 이용하면 break없이도 값 반환이 가능하다.

// [Java 12]쉼표를 이용한 case 조건 나열, break를 통해 값 반환
String result = switch (number) {
	case 1, 2:
		break "one or two";
	case 3:
		break "three";
	case 4, 5, 6:
		break "four or five or six";
	default:
		break "unknown";
};

// [Java 12]화살표 연산자를 사용해 값 반환
String result = switch (number) {
	case 1, 2 -> "one or two";
	case 3 -> "three";
	case 4, 5, 6 -> "four or five or six";
	default -> "unknown";
};

다만 Java 12에서 이용하기 위해서는 활성화 작업을 한 후 사용할 수 있다. 참고링크

Java 13에선 새 키워드를 추가하여 스위치 표현식에서 값을 반환 함으로써 이전 Java 12 스위치 표현식 을 확장한다.
break 키워드 대신 사용할 수 있는 yield 키워드가 등장했다.

또한 -> 연산자를 그대로 사용할 수 있다.

// [Java 13]쉼표를 이용한 case 조건 나열, yield 키워드를 통해 값 반환
String result = switch (number) {
	case 1, 2:
    	yield "one or two";
    case 3:
    	yield "three";
    case 4, 5, 6:
    	yiedl "four or five or six";
    default:
    	yield "unknown";
};

// [Java 13] 화살표 연산자를 이용한 값 반환
String result = switch (number) {
 	case 1, 2 -> "one or two";
    case 3 ->"three";
    case 4, 5, 6 ->"four or five or six";
    default -> {
    	System.out.println("This is Default Value);
        yield "unknown";
    }
};

Java 13 또한 이용하기 위해서는 활성화 작업을 한 후 사용할 수 있다. 참고링크

새로운 스위치 연산의 사용법이 Java 12에서 설정을 활성화 시키면 사용할 수 있었고, Java13에서 해당 식을 확장시켰으며 Java 14에서 표준이 되었다.

기존의 switch문은 많이 장황하다는 생각이 컸는데 화살표 연산자 혹은 yield 키워드를 통해 값을 반환하니 가독성과 코드 길이가 확 개선된 부분이 큰 장점인것 같다.


References

Oracle docs
https://lob-dev.tistory.com/entry/Live-StudyWeek-03-%EC%97%B0%EC%82%B0%EC%9E%90
https://mkyong.com/java/java-12-switch-expressions/
https://mkyong.com/java/java-13-switch-expressions/

profile
꾸준함이 주는 변화를 믿습니다.

2개의 댓글

comment-user-thumbnail
2022년 7월 8일

멋있습니다!!

1개의 답글