(백기선 스터디)
📌 목표
자바가 제공하는 다양한 연산자를 학습하세요.
💡 학습할 것
- 산술 연산자
- 비트 연산자
- 관계 연산자
- 논리 연산자
- instanceof
- assignment(=) operator
- 화살표(->) 연산자
- 3항 연산자
- 연산자 우선순위
- (optional) Java 13. switch 연산자
코드 올릴 때 여기를 통해서 올려볼 예정이다.
colorscripter.com/
💡 바이트코드 빠르게 보는 법(인텔리제이 : view-> show bytecode)을 이용한다. (터미널이랑은 조금 다른 것 같다)
1. 산술 연산자
산술 연산자를 배우기 전에 연산자와 피연산자의 개념에 대해서 알아보자.
- 연산자 : 연산을 수행하는 기호를 의미
- 피연산자 : 연산자를 이용해 연산되는 변수를 의미한다.
x + y라는 식에서 x와 y는 피연산자, +는 연산자를 의미한다.
산술 연산자에는 총 5가지가 있다.
우리가 익히 알고 있는 가감승제(+,-, *, /) 외에도 나머지를 구하는 연산자인 %를 포함해서 5가지이다.
가감승제는 우리가 기본적으로 알고 있으나 %는 익숙하지 않아 알아보도록 하자.
%는 어떤 수를 나누었을 때의 나머지를 계산해주는 연산자이다. 예) 5 % 2 = 1이다.
📢 산술 연산 시의 주의할 점이 몇 가지 있다.
1. 오버플로우 : 연산 후의 결과를 담을 수 있는 타입의 변수를 선언해야 된다. 그렇지 않으면 오버플로우가 발생할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Main {
public static void main(String[] args) {
int a = 3000000;
int b = 3000000;
int c = a * b;
System.out.println(c); // overflow 발생
}
}
|
cs |
결과 값은 변환이 되나 쓰레기 값이 나온다.
Overflow를 탐지하는 ArithmeticException 예외처리를 하면 안전하게 사용할 수 있다.
int로 선언했을 때 바이트코드는 아래와 같다.
long으로 선언한다면
1
2
3
4
5
6
7
8
9
10
11
12
|
public class Main {
public static void main(String[] args) {
long a = 300000;
long b = 300000;
long c = a * b;
System.out.println(c);
}
}
|
cs |
바이트코드는 아래와 같다.
둘 차이를 보면
타입이 주석으로 보이는 거랑 long으로 표현된 바이트코드가 더 길어졌다(?) 정도밖에 없는 거 같다.(?)
2. 정확한 계산은 실수 타입(부동소수점)을 사용하지 않는 것이 좋다.
- 라이브 스터디 2회에서도 말했지만 float이나 double보다는 BigDecimal을 사용하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import java.math.BigDecimal;
import java.math.BigInteger;
public class Main {
public static void main(String[] args) {
float number = 0f;
for (int i = 0; i < 10 ; i++) {
number += 0.1f;
}
System.out.println(number); // 1.0000001
BigDecimal number2 = BigDecimal.ZERO;
for (int i = 0; i < 10 ; i++) {
number2 = number2.add(BigDecimal.valueOf(0.1));
}
System.out.println(number2); // 1.0
}
}
|
cs |
float이나 double이 값이 정확하지 않는 이유는 이진 포맷의 가수를 사용하는 부동소수점 타입은 0.1을 정확히 표현할 수 없어 근사치로 처리하기 때문이다. ( 이것이 자바다.(신용권) )
3. 형변환
- 이 부분은 저번 시간에 파악했으므로 생략한다.
4. NaN과 Infinity
일반적으로 0으로 나누면 컴파일은 정상적으로 동작하지만 ArithmeticException이 발생한다.
5 / 0.0 -> Infinity
5 % 0.0 -> NaN
연산 결과가 Infinity인지 NaN인지 확인하려면 Double.isInfinite()와 Double.isNaN()을 이용하면 된다.
5. 문자열 연결 연산자 (+)
문자열을 연결할 때 +연산자를 사용한다.
String str = "Hello" + "study";
2. 비트 연산자
비트 연산자는 데이터를 비트(bit) 단위로 연산한다. 즉 0과 1로 이루어진 피연산자가 대상이 된다.
비트 연산자는 비트 논리 연산자와 비트 이동 연산자로 이루어져 있다.
비트 연산 시 주의할 점은 그 연산 괄가가 int타입이 된다는 것이다.
비트 논리연산자
- AND(논리곱, &) : 두 비트 모두 1일 경우에만 1 반환
- OR(논리합, |) : 두 비트 중 하나만 1이면 1 반환
- XOR(배타적 논리합, ^) : 두 비트 중 하나는 1, 다른 하나가 0일 때 1 반환
- NOT(논리 부정, ~) : 보수
비트 이동연산자
연산식 | 설명 | ||
a | << | b | 정수 a의 각 비트를 b만큼 왼쪽으로 이동(빈자리는 0으로 채워진다.) |
a | >> | b | 정수 a의 각 비트를 b만큼 오른쪽으로 이동(빈자리는 정수 a의 최상위 부호 비트와 같은 값으로 채워진다.) |
a | >>> | b | 정수 a의 각 비트를 b만큼 오른쪽 으로 이동(빈자리는 0으로 채워진다.) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public class Main {
public static void main(String[] args) {
byte a = 8;
byte b = 1;
int result; // 연산결과는 int타입으로 반환된다.
// 비트 논리연산
result = a & b;
System.out.println(result);
result = a | b;
System.out.println(result);
result = a ^ b;
System.out.println(result);
result = ~a;
System.out.println(result);
// 비트 이동연산
result = a >> b;
System.out.println(result);
result = a << b;
System.out.println(result);
result = a >>> b;
System.out.println(result);
}
}
|
cs |
3. 관계 연산자
관계 연산자는 비교 연산자라고도 하며 대소 비교 or 동등 비교를 해서 true/false를 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class Main {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = 1;
/*대소 비교*/
System.out.println( a > b); // false
System.out.println( a < b); // true
System.out.println( a >= b); // false
System.out.println( a <= b); // true
/*동등 비교*/
System.out.println(a == c); // true
System.out.println(a != c); // false
}
}
|
cs |
4. 논리 연산자
비트 논리 연산자와 거의 동일하다. 기호만 조금 다른 부분이 있으므로 주의하자.
피연산자들의 타입이 boolean이다.
- AND(논리곱, & 또는 &&)
- OR(논리합, | 또는 || )
- XOR(배타적 논리합, ^)
- NOT(논리 부정, !)
5. instanceof
참조 변수가 참조하고 있는 인스턴스 타입이 맞는지 아닌지 boolean으로 반환해 준다.
1
2
3
4
5
6
7
8
9
10
|
public class Rand {
public static void main(String[] args) {
String c = "5";
if(c instanceof String) { // true
System.out.println("String 타입입니다.");
}
}
}
|
Reference
improver.tistory.com/140
6. assignment(=) operator
대입 연산자, 할당 연산자라고 한다.
이 연산자를 통해 메모리에 값을 저장할 수 있다.
구분 | 연산자 | 설명 |
단순 대입 연산자 | = | 우측의 피연산자를 좌측 피연산자에 대입 |
복합 대입 연산자 | += | 우측 피연산자를 더한 후 좌측 피연산자에 대입 |
-= | 우측 피연산자를 뺀 후 좌측 피연산자에 대입 | |
*= | 우측 피연산자를 곱한 후 좌측 피연산자에 대입 | |
/= | 우측 피연산자를 나눈 후 좌측 피연산자에 대입 | |
%= | 우측 피연산자에 대한 나머지값을 좌측 피연산자대입 | |
&= | &연산 후 좌측 피연산자에 대입 | |
|= | |연산 후 좌측 피연산자에 대입 | |
^= | ^연산 후 좌측 피연산자에 대입 | |
<<= | <<연산 후 좌측 피연산자에 대입 | |
>>= | >>연산 후 좌측 피연산자에 대입 | |
>>>= | >>>연산 후 좌측 피연산자에 대입 |
7. 화살표(->) 연산자
화살표 연산자는 자바 8 이후에 도입된 람다식에서 도입된 연산자이다.
람다식은 "(매개변수) -> {실행코드}" 형태로 작성한다.
Runnable runnable = new Runnable() {
public void run() {...}
};
Runnable runnable = () -> {...};
예) 이름이 6자리가 넘으면 예외 발생한다를 ->연산자를 이용해 코딩을 한다면 이렇게 짤 수 있을 거 같다.
public List<String> Names() {
Scanner scanner = new Scanner(System.in);
String str = scanner.nextLine();
String[] name = str.replace(" ", "")
.split(",");
Arrays.stream(name).forEach(s -> {
if (s.length() > 6) {
throw new IllegalArgumentException("예외 발생");
}
});
return Arrays.asList(name);
}
8. 3항 연산자
삼항 연산자는 세 개의 피연산자가 필요하는 연산자이다.
형태
조건식 ? 값1or 연산식1 : 값1 or 연산식1
- 조건식이 true : 값1 or 연산식 1을 반환한다.
- 조건식이 false : 값2 or 연산식 2를 반환한다.
9. 연산자 우선 순위
아래의 순서로 연산자 우선순위를 가진다.
1. 최우선연산자 ( ., [], () )
2. 단항연산자 ( ++,--,!,~,+/- : 부정, bit변환>부호>증감)
3. 산술연산자 ( *,/,%,+,-,shift) < 시프트연산자 ( >>,<<,>>> ) >
4. 비교연산자 ( >,<,>=,<=,==,!= )
5. 비트연산자 ( &,|,,~ )
6. 논리연산자 (&& , || , !)
7. 삼항연산자 (조건식) ? :
8. 대입연산자 =,*=,/=,%=,+=,-=
Reference
programmers.co.kr/learn/courses/5/lessons/116
10. (optional) Java 13. switch 연산자
oracle13 doc참고
docs.oracle.com/en/java/javase/13/language/switch-expressions.html
여기도 좋은듯하다.
아래와 같은 코드가 있다고 하자.
public enum Day { SUNDAY, MONDAY, TUESDAY,
WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; }
// ...
int numLetters = 0;
Day day = Day.WEDNESDAY;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
default:
throw new IllegalStateException("Invalid day: " + day);
}
System.out.println(numLetters);
이 코드는 Day의 Letter 수를 return 한다.
복잡한 조건을 분기 처리를 필터링해 variable numLetters에 값을 저장해서 반환하는 것보다.
Day의 길이를 바로 return 하는 것이 좀 더 좋아 보인다. 이것은 switch expression으로 해결 가능하다.
switch expression은 또한 break문을 사용할 필요 없이 밑으로 떨어지는 걸 방지할 수 있다.
switch expression을 통한 코드 재구현
Day day = Day.WEDNESDAY;
System.out.println(
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> throw new IllegalStateException("Invalid day: " + day);
}
);
자바 런타임 시에 화살표 왼쪽에 매칭 되는 걸 찾아, 화살표 오른쪽 코드를 실행하며 밑으로 떨어지지 않는다.
자바 13에서 yield라는 표현도 생겼지만 시간 관계상 나중에 알아봐야 할 것 같다.
'Java' 카테고리의 다른 글
4주차 과제: 제어문 (0) | 2020.12.12 |
---|---|
3주차 과제: 연산자(feedback, 피드백) (0) | 2020.11.29 |
2주차 과제: 자바 데이터 타입, 변수 그리고 배열(피드백, feedback) (0) | 2020.11.22 |
2주차 과제: 자바 데이터 타입, 변수 그리고 배열 (0) | 2020.11.18 |
1주차 과제: JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가.(피드백 feedback) (0) | 2020.11.15 |