[JAVA] 연산자

SungBum Park·2021년 3월 7일
0

JAVA

목록 보기
3/9
post-thumbnail

용어 정리
연산(operations): 프로그램에서 데이터를 처리하여 결과를 산출하는 것
연산자(operator): 연산에 사용되는 표시나 기호
피연산자(operand): 연산의 대상이 되는 데이터
연산식(expressions): 연산자와 피연산자를 사용하여 연산의 과정을 기술한 것

연산자 정리

산술 연산자

산술 연산자는 피연산자가 숫자인 연산에서 그 결과 또한 숫자인 것을 말한다. 산술 연산자에서 주의할 점은 다음과 같다.

  • 타입이 서로 다른 피연산자끼리의 연산
  • 연산 결과가 이를 담을 타입의 범위를 넘어설 때
  • 0으로 나누기 또는 나머지 연산

타입이 서로 다른 피연산자끼리의 연산

int a = 10;
long b = 20;
int intResult = a + b;    // 컴파일 에러
long longResult = a + b   // O

타입이 서로 다른 피연산자의 결과로 피연산자 중 크기가 작은 타입에 담을려고 하면 다음과 같은 컴파일 에러가 발생한다.

error: incompatible types: possible lossy conversion from long to int

물론 강제 타입 형변환을 하면 컴파일 에러없이 정상 동작한다.

int intResult = a + (int)b;

연산 결과가 이를 담을 타입의 범위를 넘어설 때

int result = Integer.MAX_VALUE + 1;   //Integer.MAX_VALUE = 2147483647

위 연산의 결과는 int 자료형의 범위를 넘어선 값이며, 이를 int 변수에 담을려고 한다. 이 결과는 다음과 같다.

-2147483648

연산식에서 의도한 값은 2147483648이지만, 2147483647보다 큰 수는 int 범위를 넘어서기 때문에 이는 가장 작은 범위로 넘어간 후 계산이 된다. 이는 프로그램이 동작하는데 있어서, 컴파일 오류보다 더 큰 위험을 가질 수도 있다. 오류가 나지 않는 정상적인 상황에서 전혀 예상하지 못하는 결과를 초래하기 때문이다.

연산 결과가 이를 담을 변수 범위보다 크면 가장 작은 범위로 넘어가는 결과가 나오며, 반대로 변수 범위보다 작으면 가장 큰 범위로 넘어간다.

따라서 연산 결과가 이를 담을 변수의 크기에 충분히 담을 수 있는지 반드시 고려해야 한다.

0으로 나누기 또는 나머지 연산

0으로 나누기 또는 0으로 나머지 연산은 전혀 계산할 수 없는 연산이므로 컴파일 오류가 발생한다.

int result = 10 / 0;
int result = 10 % 0;
java.lang.ArithmeticException: / by zero

위 자바 코드와 같이 0으로 나누거나 나머지 연산을 수행시 위와 같은 오류가 발생한다.

비트 연산자

사실 어떤 연산자를 사용하든, 결국 컴퓨터는 모두 비트로 계산을 수행한다. 비트 연산을 직접 수행할 수 있도록 해주는 것이 비트 연산자이다.

& 연산자

& 연산자는 AND 연산이다.

AND

int result = 5 & 1;
/*
	0000 0000 0000 0000 0000 0000 0000 0101
&	0000 0000 0000 0000 0000 0000 0000 0001
------------------------------------------
	0000 0000 0000 0000 0000 0000 0000 0001
*/
assertThat(result).isEqualTo(1);  // pass!

위는 int 자료형으로 & 연산자를 사용한 모습이다. int 자료형의 크기는 4byte이며, 1byte는 8bit다. 따라서 4byte는 32bit이므로, 위처럼 32개의 0과 1로 이루어져있다.

| 연산자

| 연산자는 OR 연산이다.

OR

int result = 5 | 1;
/*
	0000 0000 0000 0000 0000 0000 0000 0101
|	0000 0000 0000 0000 0000 0000 0000 0001
------------------------------------------
	0000 0000 0000 0000 0000 0000 0000 0101
*/
assertThat(result).isEqualTo(5);  // pass!

^ 연산자

^ 연산자는 XOR 연산이다.

XOR

int result = 5 ^ 1;
/*
	0000 0000 0000 0000 0000 0000 0000 0101
^	0000 0000 0000 0000 0000 0000 0000 0001
------------------------------------------
	0000 0000 0000 0000 0000 0000 0000 0100
*/
assertThat(result).isEqualTo(4);  // pass!

~ 연산자

~연산자는 NOT 연산이다.

NOT

int result = ~5;
/*
~	0000 0000 0000 0000 0000 0000 0000 0101
------------------------------------------
	1111 1111 1111 1111 1111 1111 1111 1010
*/
assertThat(result).isEqualTo(-6);  // pass!

>> 연산자

연산자는 right shift 연산으로, 모든 비트를 오른쪽으로 지정한 수만큼 밀어내는 연산이다.

int result = 5 >> 1;
/*
	0000 0000 0000 0000 0000 0000 0000 0101 >> 1
------------------------------------------
	0000 0000 0000 0000 0000 0000 0000 0010
*/
assertThat(result).isEqualTo(2);

int result2 = 5 >> 2;
/*
	0000 0000 0000 0000 0000 0000 0000 0101 >> 2
------------------------------------------
	0000 0000 0000 0000 0000 0000 0000 0001
*/
assertThat(result2).isEqualTo(1);

위는 각각 오른쪽으로 1칸, 2칸을 옮긴 결과를 보여준다. 오른쪽으로 이동결과 범위를 넘어가는 비트는 사라지며, 왼쪽은 0 비트로 채워진다. 참고로 >> 1 를 수행할 때마다 2로 나눠지는 결과를 얻는다.

<< 연산자

<< 연산자는 left shift 연산으로, 모든 비트를 왼쪽으로 지정한 수만큼 밀어내는 연산이다.

int result = 5 << 1;
/*
	0000 0000 0000 0000 0000 0000 0000 0101 << 1
------------------------------------------
	0000 0000 0000 0000 0000 0000 0000 1010
*/
assertThat(result).isEqualTo(10);

int result2 = 5 << 2;
/*
	0000 0000 0000 0000 0000 0000 0000 0101 << 2
------------------------------------------
	0000 0000 0000 0000 0000 0000 0001 0100
*/
assertThat(result2).isEqualTo(20);

위는 각각 왼쪽으로 1칸, 2칸을 옮긴 결과이다. << 1 를 수행할 때마다 2를 곱한 결과를 얻을 수 있다.

>>> 연산자

연산자는 unsigned right shift 연산으로, 부호없는 비트이동 연산자이다. 모든 비트를 오른쪽으로 지정한 수 만큼 밀어낸 후, 남은 공간을 모두 0으로 채운다.

관계 연산자

관계 연산자는 두 피연산자를 비교하는 연산자이다. 비교한 결과는 boolean 값으로 반환한다.

  • == : 두 피연산자가 서로 같으면 true, 아니면 false
  • != : 두 피연산자가 서로 다르면 true, 아니면 false
  • < : 왼쪽 피연산자보다 오른쪽 피연산자가 크면 true, 아니면 false
  • <= : 왼쪽 피연산자보다 오른쪽 피연산자가 크거나 같으면 true, 아니면 false
  • > : 왼쪽 피연산자보다 오른쪽 피연산자가 작으면 true, 아니면 false
  • >= : 왼쪽 피연산자보다 오른쪽 피연산자가 작거나 같으면 true, 아니면 false

논리 연산자

논리 연산자는 NOT, AND, OR 연산을 수행하며, 결과로 boolean 값을 반환한다.

  • ! : NOT
  • && : AND
  • || : OR

위에서 비트 연산자에도 같은 NOT, AND, OR 연산을 수행하는 연산자가 있었다. 논리 연산자가 비트 연산자와 다른 점은 앞의 조건식부터 순서대로 비교하면서, 앞의 조건식 결과로 이미 전체의 true 또는 false를 판단할 수 있다면 더이상 뒤의 조건식을 보지 않는다.

assertThat(true || false).isTrue();
assertThat(!false || false).isTrue();

assertThat(false && true).isFalse();
assertThat(!true && true).isFalse();

|| 연산자는 앞에서 이미 true인 경우는 뒤의 조건식의 결과에 상관없이 true를 반환한다. 따라서 || 연산은 앞에서 true가 있다면 더이상 뒤 조건식을 볼 필요가 없다. 따라서 좀 더 효율적인 연산이 가능하다. (&& 연산자도 동일하다.)

instanceof

instanceof 는 자바에서 제공하는 실행 시간에 객체의 타입을 검사하는 연산자이다.(type introspection) 이는 상속관계 또는 구현관계를 모두 포함한다.

class Dog extends Animal implements Moving {
	// ...
}

...

Dog dog = new Dog();
assertThat(dog instanceof Dog).isTrue();
assertThat(dog instanceof Animal).isTrue();
assertThat(dog instanceof Moving).isTrue();
assertThat(dog instanceof Object).isTrue();

Dog 클래스는 Animal 클래스를 상속받고, Moving 인터페이스를 구현하고 있다. 따라서 dog 객체는 앞선 3개의 클래스 타입을 모두 instanceof 로 검사할 지 true를 반환다. 그리고 모든 객체의 최상위 객체인 Object 객체 역시 true를 반환한다.

이는 Dog 클래스 정보에 상속받거나 구현한 모든 정보가 저장되어 있기 떄문이다. 따라서 반대로 Animal 클래스나 Moving 인터페이스를 객체로 만든 후 instanceofDog 클래스 타입을 검사하면 false를 반환한다. Animal 클래스는 Dog 클래스가 자신을 상속하고 있는지 모르고 알 필요도 없기 때문이다.

assignment(=) operator

Assignment operator는 대입 또는 할당 연산자로 부르며, = 를 기준으로 왼쪽에는 대입 또는 할당할 변수가 위치하고 오른쪽은 대입 또는 할당할 리터럴 또는 객체의 주소값이 위치한다.

대입 연산자는 = 하나가 존재하지만, 연산식을 효율적으로 줄이기 위해 다른 산술 연산자와 합쳐 사용할 수도 있다. +=, -= , *=, /= 등이 있다.

int num = 5;
assertThat(num += 10).isEqualTo(15);    // num = num + 10
assertThat(num -= 10).isEqualTo(5);     // num = num - 10
assertThat(num *= 10).isEqualTo(50);    // num = num * 10
assertThat(num /= 10).isEqualTo(5);     // num = num / 10

화살표(->) 연산자

Java 8 버전에 나온 기능으로, 람다 표현식(Lambda Expressions)을 표현하기 위한 연산자이다. 람다 표현식은 자바에서 함수형 프로그래밍을 도입하기 위한 방법 중 하나로, 기존에 사용하던 익명 클래스를 간소화한 표현식이다.

익명 클래스는 메서드 파라미터로 클래스를 전달할 수 있게 함으로써, 좀 더 유연한 코드를 만들 수 있다. 하지만 익명 클래스를 사용하면, 반복되는 키워드를 작성해주어야 해서 불편하고 코드가 지저분해질 수 있다. 이를 해결한 것이 람다 표현식이다.

람다 표현식을 위한 화살표 연산자의 사용법은 다음과 같다.

(argument, ...) -> {expression}

화살표 연산자 또는 람다 표현식을 이해하기 위해 이전에는 어떻게 사용했으며, 이를 어떻게 람다 표현식으로 표현가능한지 살펴보자. 예를 들어 사과 객체의 일정 이상 무게와 초록색 사과를 선택하는 로직을 구현한다고 하자.

public interface ApplePredicate {
	boolean test(Apple apple);
}

// 사과를 필터링하는 메소드
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
	// ...
}

ApplePredicate 인터페이스는 어떤 사과를 거를지에 대한 인터페이스이며, 이를 사용하여 실제로 필터링하는 메소드는 filterApples 메소드이다.

익명 클래스가 생기기 전에는 인터페이스를 생성한 후, 필요한 구현체 클래스를 모두 직접 만들어서 메소드 파라미터로 전달해야 했다.

List<Apple> heavyApples = FilteringApples.filterApples(inventory, new AppleHeavyWeightPredicate());
List<Apple> greenApples = FilteringApples.filterApples(inventory, new AppleGreenColorPredicate());

익명 클래스가 생기면서, 인터페이스의 구현체를 생성해줄 필요가 없어졌다. 메소드 파라미터에 바로 필요한 구현체를 아래와 같이 익명 클래스로서 선언할 수 있다.

List<Apple> heavyApples = FilteringApples.filterApples(inventory, new ApplePredicate() {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
});
List<Apple> greenApples = FilteringApples.filterApples(inventory, new ApplePredicate() {
    @Override
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
});

화살표 연산자를 사용한 람다 표현식이 나오면서, 위를 좀 더 편리하게 선언하여 사용할 수 있다.

List<Apple> heavyApples = FilteringApples.filterApples(inventory, 
    (Apple apple) -> apple.getWeight() > 150);
List<Apple> greenApples = FilteringApples.filterApples(inventory,
    (Apple apple) -> "green".equals(apple.getColor()));

화살표 연산자의 사용법에서 {expression} 부분은 한 줄로 표현할 수 있다면 중괄호({})를 생략할 수 있다.

예제 출처: Java8 in Action(현재는 모던 자바 인 액션)

3항 연산자

3항 연산자는 피연산자가 3개인 연산자를 말한다. 자바에서는 ?: 연산자가 존재한다.

조건식 ? 조건식이 true인 경우 : 조건식이 false인 경우;

조건식은 true 또는 false를 반환하며, true인 경우 : 앞의 값을, false인 경우 뒤의 값을 사용한다.

String result = A > B ? "B보다 A가 크다" : "A보다 B가 크다";

3항 연산자는 단순한 if-else 문을 한 줄로 줄일 수 있는 장점이 있지만, 항상 가독성을 생각해서 어떤 것을 사용하는게 더 좋을지 선택해서 사용하자.

연산자 우선 순위

제목 없음

연산자의 우선순위를 모두 외우기는 매우 힘들다. 자신이 100% 다 외운다고 해도 코드는 자기 자신만 읽는 것이 아니므로, 코드를 읽는 사람도 생각해야 한다. 따라서 매우 당연한 우선순위(덧셈보다 곱셈의 우선순위가 높다.)를 제외하고는 괄호를 통해 우선순위를 명시적으로 나타내는 것이 좋다.

(optional) Java 13. switch 연산자

Java 12버전과 13버전에서 swtich 구문에 변화가 생겼다.

  • Java 12
    • case : 대신 case -> 로 화살표 연산자를 사용할 수 있다.
    • 화살표 연산자를 사용하면 break 문을 생략할 수 있다.
  • Java 13
    • case 문 내에 여러 개의 값을 사용할 수 있다.
    • 중괄호 블록을 사용할 수 있다.
    • break 문 대신 yield 문을 사용할 수 있다.

참고자료


profile
https://parker1609.github.io/ 블로그 이전

0개의 댓글