상세 컨텐츠

본문 제목

[JAVA] 자바 스터디 3주차 - 연산자

개발 공부 (etc)/JAVA

by letprogramming 2021. 1. 28. 02:37

본문

반응형

목표


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

학습할 것


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

 

연산자

연산자란 연산(계산)을 수행하기 위해 사용하는 기호이다. 자바에서는 다양한 연산자를 제공한다.


산술 연산자

산술 연산자사칙 연산과 관련된 연산자들이다.

우리가 덧셈, 뺄셈, 곱셈, 나눗셈을 하기 위해 흔히 사용하는

+, -, ×, ÷ 를 자바에서도 기본적으로 사용할 수 있다.

연산자 연산 설명 사용법
+ 더하기 a + b
- 빼기 a - b
* 곱하기 a * b
/ 나누기 (몫) a / b
% 나눈 나머지 a % b

예시)

int a = 1;
int b = 2;
int c = 0;

위와 같이 a와 b 변수가 존재할 때

c = a + b; //덧셈
c = a - b; //뺄셈
c = a * b; //곱셈
c = a / b; //나눗셈
c = a % b; //나머지

위의 코드를 실행하면 산술 연산을 수행한다.

a와 b에 저장되어 있는 값들을 연산하여 c에 대입한다.

 

마지막에 있는 %연산자는 나머지 연산을 수행한다.

나머지 연산이란 a를 b로 나눈 나머지를 구하는 연산이다.


비트 연산자

비트 연산자는 이진 비트 연산을 하는 연산자이다.

이진 비트 연산이란 피연산자들을 이진수로 변경한 후에 비트 단위로 연산을 하는 것이다.

 

비트 연산자는 4가지 종류가 있다.

연산자 설명 사용법
& AND (논리곱) a & b
| OR (논리합) a | b
^ XOR (배타적 논리합) a ^ b
~ NOT (부정) ~a

AND 는 a 와 b가 모두 1일 때 1이고, 다르거나 둘 다 0이면 0이다.

OR 는 a와 b 중에 하나만 1이어도 결과가 1이다. 둘 다 0일때만 결과가 0이다.

XOR는 a와 b가 달라야 1이고, a와 b가 다르면 0이다.

NOT은 부정으로 a가 1이면 0으로, 0이면 1로 바꾼다.

 

예시)

int a = 2

int b = 3

int a = 2;
int b = 3;
int c = 0;

위와 같이 정수 a와 b가 있다.

각각의 비트 연산자를 수행했을 때

c = a & b; //AND
c = a | b; //OR
c = a ^ b; //XOR
c = ~a; //NOT

먼저 a와 b를 이진수로 바꾸면

a = 0010(2)

b = 0011(3)

이다.

각각의 결과는

a & b : 0010(2)

a | b : 0011(3)

a ^ b : 0001(1)

~a : 1101(-3)

=> 2의 NOT 연산의 결과가 -3인 이유는

0010을 반전시켰을 때 1101이 되면서 가장 왼쪽 비트가 1이 된다.

컴퓨터는 음수를 표현하기 위해서 이 수의 2의 보수를 구한다.

1101의 1의 보수인 0010에 1을 더한 값인 0011음수 표시를 한

-3이 최종결과가 된다.


관계 연산자

관계 연산자는 연산을 하는 두 피연산자 사이의 관계를 결정하는 연산자이다.

관계 연산자의 결과는 항상 true 혹은 false 이다.

 

연산자 설명 사용법
> 보다 크다 a > b
>= 보다 크거나 작다 a >= b
< 보다 작다 a < b
<= 보다 작거나 같다 a && b
== 같다 a II b
!= 같지 않다 a != b

 

위의 관계 연산자의 결과가 참이면 true 거짓이면 false를 반환한다.

 

예시)

a = 1;
b = 2;
c = 2;

boolean isTrue = a > b; //is True : false
isTrue = a < b; //is True : true
isTrue = b <= c; //is True : true
isTrue = a == c;  //is True : false
isTrue = b == c; //is True : true
isTrue = a != b; //is True : true

관계 연산자는 결과가 둘 중 하나로 결정되기 때문에 조건문이나 반복문에서 자주 사용한다.


논리 연산자

논리 연산자는 피연산자의 값을 논리 연산하여 true 혹은 false를 결과로 반환한다.

연산자 설명 사용법
& AND a & b
| OR a | b
^ XOR (배타적 OR) a ^ b
&& AND a && b
|| OR a || b
~ NOT (부정) !a

위의 비트 연산자와의 차이점은

비트 연산자가 값을 가지고 비트 단위로 계산을 한다면,

논리 연산자는 전체적인 값을 참 혹은 거짓으로 논리적으로 평가한다.

 

예시)

boolean a = true;
boolean b = false;
boolean c = false;

c = a & b; // c : false
c = a | b; // c : true
c = a ^ b; // c : true
c = a && b; // c : false
c = a || b; // c : true
c = !a; // c : false

AND, OR, XOR는 비트 연산자에서 설명한 것과 같다.

AND는 a와 b가 모두 true일 때만 true, 다르거나 둘 다 false이면 결과가 false이다.

OR는 a와 b가 모두 false일 때만 false이고, 둘 중 하나라도 true이거나 모두 true이면 결과가 true이다.

XOR는 a와 b가 달라야 true이고, 같으면 false이다. 위에서는 a = true이고 b = false로 다르기 때문에 결과 c 가 true이다.

NOT은 a를 반대로 바꾼다고 생각하면 된다. a = true이기 때문에 !a는 false이다.

 

&& 와 &, ||와 |의 차이점은 연산이 중간에 끝난다는 것이다.

&와 |은 a와 b를 끝까지 연산한다.

&의 경우 만약 a & b를 연산할 때 a 가 false라면 b를 볼 필요없이 결과는 false이다.

|의 경우도 a | b에서 a 가 true라면 b의 값과 상관없이 a | b는 true이다.

 

이렇게 뒤의 값을 볼 필요가 없이 결과가 정해지는 경우

&&와 ||은 뒤의 피연산자를 보지 않고 결과를 곧바로 반환한다.

 


instanceof

instanceof는 일종의 논리 연산자라고 볼 수 있다.

instanceof는 객체의 클래스를 확인하기 위해서 사용한다.

쉽게 말해 instanceofs는 객체의 정체성을 확인하는 것이다.

 

객체는 클래스를 인스턴스화(객체화)한 것이다.

int a, char ch, double d 등 앞서 살펴보았던 변수들이 자료형을 갖는 것처럼

객체는 클래스로부터 생성되기 때문에 클래스를 자신의 정체성으로 갖는다.

 

instanceof 연산자는 특정 객체의 실제 클래스를 확인해서 true 혹은 false를 결과로 반환한다.

 

예시)

String str = "Hello Wolrd";
if str instance of Object
  System.out.println("OBJECT");
else
  System.out.println("NOT OBJECT");
if str instance of String
  System.out.println("STRING");
else
  System.out.println("NOT STRING");

String도 하나의 클래스이다.

str객체는 String 이라는 클래스를 인스턴스 타입으로 갖기 때문에

instanceof의 결과는 true이고 "STRING"이라는 결과가 반환된다.

 

그러나 그 전에 "OBJECT"도 출력이 될 것이다.

그 이유는 Object클래스는 모든 클래스의 최상위 클래스이기 때문이다.

클래스에 대해서는 나중에 자세하게 공부하기 때문에 자세하게 언급하지 않지만

이렇게 부모 - 자식간 상속으로 이루어진 클래스들을 구분하기 위해서

instanceof를 사용하는 경우가 많다.

 

결론은

instanceof 연산자는 객체가 가리키고 있는 인스턴스 타입, 클래스를 확인할 수 있는 연산자이다.


assignment(=) operator

대입 연산자는 변수에 값을 할당하는 대입을 하는 연산자이다.

a = b;

를 수행하면 a와 b가 같다는 뜻이 아니라 a라는 변수에 b 변수에 저장되어 있는 값을 대입한다는 의미이다.

 

만약

int a, b, c;

a = b = c = 10;

이렇게 코드를 작성하면

a : 10

b : 10

c : 10

a, b, c 모두에 10이 할당된다.

 

대입 연산자를 사용할 때는 대입을 하려는 값과 변수의 자료형이 동일해야 한다.


위에서 알아보았던 산술연산자와 대입연산자를 한 번에 수행하는 연산자도 존재한다.

연산자 설명 사용법
+= 덧셈 대입 a += b
-= 뺄셈 대입 a -= b
*= 곱셈 대입 a *= b
/= 나눗셈 대입 a /= b
%= 나머지 대입 a %= b

실제 의미를 살펴보면,

a += b 가 실제로 수행되는 과정은 a = a + b 이다.

a의 값에 b를 더한 결과를 다시 a에 대입하는 것이다.


화살표(->) 연산자

화살표 연산자는 java 8부터 사용이 가능한 람다(Lambda)식을 나타내는 연산자이다.

람다식이란 별도로 작성한 후에 호출하는 기존의 함수와 달리

함수의 이름이 존재하지 않는 익명의 함수를 의미한다.

 

먼저 사용법은 다음과 같다.

(매개변수, ...) -> { 실행문 }

 

- 함수의 이름이 필요없다.

- 매개 변수의 타입을 명시하지 않아도 된다.

- 함수의 실행문이 단일 실행문, 하나의 문장만 수행한다면 중괄호 생략이 가능하다. 단, return문일 경우 생략 불가능

- 매개 변수가 하나일 경우, 소괄호 생략이 가능하다.

 

예시)

(x, y) -> { return 1; }

위와 같이 작성하면 매개변수 x와 y를 받아서 중괄호 안에 있는 실행문 return 1 을 수행한다는 의미이다.

 

예시)

기존 자바 코드

new Thread(new Runnable(){
  @Override
  public void run(){
    System.out.println("Hello World!!");
  }
}).start();
new Thread(()->{
  System.out.println("Hello World!!");
}).start();

아직 객체, 무명 객체, 스레드, 오버라이드 등과 같은 개념은 공부하지 않았지만

람다식과 기존 코드를 비교해보면 차이가 크다는 것을 알 수 있다.

 

기존의 자바 코드에서는 Thread라는 객체를 생성하면서 Runnable 내에 있는 run() 함수를 반드시

오버라이드해서 구현해야 했다.

상단의 코드처럼 run 이라는 함수 이름을 반드시 명시하면서 그 안의 함수 내용을 채웠었다.

 

그러나 아래의 람다식을 보면 run이라는 함수명과 new Runnable() 인터페이스가 사라진 것을 볼 수 있다.

단순하게 소괄호 -> 중괄호 라는 구조를 통해 Thread 객체를 생성할 때 필요한 run 함수를 파라미터로 전달했다.


3항 연산자

3항 연산자는 3개의 연산항, 피연산자를 가진 연산자이다.

3항 연산자는 논리적인 결과에 따라 수행하는 코드를 결정할 수 있다.

 

일반적으로 if - else를 사용하는 부분을 짧게 축약해서 사용하는 것이다.

 

3항 연산자는 ?(물음표)와 :(콜론)을 사용한다.

 

조건 ? 수식1(조건이 true) : 수식 2(조건이 false)

 

위와 같이 작성했을 때 만약 조건의 결과가 참이면 앞의 수식1이 결과가 되고,

false일 경우 수식2가 최종 결과가 된다.

 

예시)

int a = 1;
int b = 2;

int max = a > b ? a : b; //max : 2

위의 코드에서

조건은 a > b

수식1은 a

수식2는 b 라고 할 수 있다.

조건을 보았을 때 a : 1, b : 2로 조건의 결과는 false인 것을 알 수 있다.

그러므로 콜론(:)뒤에 있는 수식2의 결과, b가 최종 결과로 max 변수에 대입되는 것이다.

 

3항 연산자는 간단한 조건과 수식으로 이루어지면 코드가 간결해지고 짧아지는 장점이 있지만,

복잡할 경우 오히려 코드가 이해하기 어려워질 수 있다는 단점이 있다.


연산자 우선 순위

우리가 수학에서 계산을 할 때, 계산의 순서가 정해져 있다.

덧셈과 뺄셈보다 곱셈과 나눗셈을 먼저 하고, 만약 괄호가 있다면 괄호를 먼저 계산한다.

 

컴퓨터에서도 코드를 작성하면 여러 연산자를 혼합해서 사용하는 경우가 존재할 수 있다.

이러한 경우 어떤 것을 먼저 수행하느냐에 따라 결과가 달라질 수 있기 때문에

우선 순위를 정하는 것은 매우 중요하다.

 

위에서 이야기한 괄호와 사칙 연산의 경우는 일반적인 수학과 유사하지만,

수학에서는 사용하지 않는 다른 연산자가 많기 때문에 헷갈릴 수 있다.

 

자바에서 연산자의 우선 순위는 다음과 같다.

우선 순위 연산자
1 () [] .
2 ++ -- ~ !
3 * / %
4 + -
5 >> >>> <<
6 > >= < <=
7 == !=
8 &
9 ^
10 I
11 &&
12 II
13 ?:
14 = op=

연산자의 우선 순위는 암기할 필요는 없다.

++, --, &&, || 등과 같이 자주 사용하는 연산자들은 자연스럽게 익히게 된다.

 

암기할 필요가 없는 이유는 괄호가 가장 최우선 순위이기 때문이다.

만약 여러 개의 연산자를 이용해 코드를 작성했을 때는 괄호로 묶는 것이 안전하고

내가 원하는 대로 연산의 결과를 얻을 수 있다.

 

위의 연산자 우선 순위에 맞게 나의 코드를 작성했다고 하더라도

내가 생각한 연산 순서와 컴퓨터의 실제 연산 순서는 다를 수 있고 내가 실수할 수 있다.

위에서 이야기했던 것처럼 사소한 연산 순서의 차이가 전혀 다른 결과를 반환할 수도 있고,

에러를 일으킬 수도 있다.

 

또한 다른 사람이 나의 코드를 읽고 이해할 때, 그 사람은 연산자의 우선 순위를 잘 모를 수도 있다.

괄호가 있다면 누구든지 연산자의 우선 순위를 신경쓰지 않고 연산의 결과를 예상할 수 있으므로

헷갈릴 만한 연산을 수행할 때는 괄호를 쓰는 것이 안전하다고 생각한다.


Java 13, switch 연산자

switch는 반복되는 if - else if - else 구문을 가독성 좋고 읽기 쉽게 바꾸는 조건문이다.

switch 구문은 switch, case, break, default로 이루어져 있다.

 

기존의 switch 구문 사용법은 다음과 같다.

int score = 90;

switch(score){
  case score >= 90:
    System.out.println("A");
    break;
  case score >= 80:
    System.out.println("B");
    break;
  case score >= 70:
    System.out.println("C");
    break;
  default:
    System.out.println("NO SCORE");
    break;
}

간단하게 설명하면 switch() 문의 소괄호안에 있는 값을 보고

중괄호 안에 해당 값을 가진 case를 찾아 간다.

만약 값과 같은 case가 없을 경우  default로 구현되어 있는 구문을 수행한다.

 

switch문의 특징은 break가 없을 경우 멈추지 않는다는 것이다.

int score = 90;

switch(score){
  case score >= 90:
    System.out.println("A");
  case score >= 80:
    System.out.println("B");
  case score >= 70:
    System.out.println("C");
  default:
    System.out.println("NO SCORE");
    break;
}

만약 break없이 코드를 작성한 위와 같은 경우에

score는 90이기 때문에 첫 번째 case에 도착한다.

그러나 "A"를 출력하고 나서 switch문은 멈추지 않고 아래의 구문들을 모두 살핀다.

score는 90이기 때문에 80과 70보다도 크다.

그러므로 "B"와 "C" 모두 출력한다.


java 12에서는 switch문이 개선되었다.

먼저 기존 switch문의 문제는

- 불필요하게 길다. => if - else보다 보기에는 좋지만 딱히 더 간결해지고 짧아진 것 같진 않다.

- 에러 디버깅이 어렵다.

- break가 없으면 멈추지 않는다.

 

java 12에서는 switch문에서 람다식을 이용할 수 있다.

위의 예시를 람다식으로 바꾸면 아래와 같다.

int score = 90;

switch(score){
  case score >= 90 -> System.out.println("A");
  case score >= 80 -> System.out.println("B");
  case score >= 70 -> System.out.println("C");
  default -> System.out.println("NO SCORE");
}

훨씬 간결하고 코드가 짧아진 것을 확인할 수 있다.

더불어 ->(화살표)를 사용하면 break를 명시하지 않아도 다음 case로 넘어가지 않는다.

 

또한 java12에서는 switch 문에서 값을 리턴할 수 있다.

기존의 switch문 내에서는 값을 리턴할 수 없었다.

 

그렇기 때문에 switch문 내에서 선언한 변수를 사용할 수 없었고, 

외부에서 선언한 변수를 이용하다가 에러가 발생할 가능성이 존재했다.

 

그러나 java 12부터는 다음과 같이 사용이 가능하다.

int score = 90;

String grade = switch(score){
  case score >= 90 -> "A";
  case score >= 80 -> "B";
  case score >= 70 -> "C";
  default -> "NO SCORE";
}

grade 변수에 score에 따른 평점을 저장할 수 있게 되었다.

 

만약 단일 실행문이 아닌 경우에는 break value; 이런 식으로 switch문의 리턴값을 넘겨줄 수 있다.


java13 이후에는 yield라는 키워드를 이용해 산출값을 return할 수 있다.

java 12 에서는 break value; 를 이용해 산출값을 넘겨주었지만

java13 부터 yield value; 로 리턴값을 반환할 수 있다.

반응형

관련글 더보기