https://github.com/whiteship/live-study
백기선님 자바 기초 스터디 3주차
목표
자바가 제공하는 다양한 연산자를 학습하세요.
학습할 것
- 산술 연산자
- 비트 연산자
- 관계 연산자
- 논리 연산자
- instanceof
- assignment(=) operator
- 화살표(->) 연산자
- 3항 연산자
- 연산자 우선 순위
- (optional) Java 13. switch 연산자
산술 연산자
산술 연산자에는 사칙 연산자(+,-,*,/)와 나머지 연산자(%)가 있다. 사칙연산은 일상생활에서 자주 사용해 익숙하기 때문에, 자바에서 사칙연산시 주의해야할 사항 위주로 알아보자.
0. 산술 변환
산술 변환: 연산 수행 직전에 발생하는 피연산자의 자동 형변환
- 두 피연산자의 타입을 같게 일치시킨다(보다 큰 타입으로 일치)
- 피연산자의 타입이 int보다 작은 타입이면 int로 변환된다.
-> 산술 변환은 산술 연산자 뿐만 아니라 앞으로 나올 모든 연산자의 적용될 규칙이다.
1. int / int = int
1) 나누기 연산자(/)의 두 피연산자가 모두 int타입인 경우, 연산결과 역시 int 타입이다.
ex) 10 / 4 -> 2
실제 연산결과는 2.5일지라도 소수점 이하는 버리고 int타입의 값인 2를 결과로 얻는다.
2) 올바른 연산결과를 얻기 위해서는 두 피연산자 중 어느 한쪽을 실수형으로 형변환해야 한다.
ex) 10 / 4.0f -> 10.0f / 4.0f -> 2.5f
3) 나누기 연산자는 0으로 나누는 경우, ArithmeticException, 0.0으로 나누는 경우, Infinity를 결과로 갖는다.
2. char = char + ...
1)
char c1 = 'a';
c1 = (char)(c1 + 1);
c1 = c1 + 1; //에러
c1 + 1에서 피연산자 c1의 타입(char)은 int보다 작기 때문에 산술 변환에 의해서 int로 변환된다. ('a' -> 97)
c1 + 1 -> 97 + 1 -> 98
연산 결과 int타입을 다시 char타입에 대입할 수 없기 때문에 char타입으로 강제 형변환을 해주어야 한다.
2)
char c1 = 'a' + 1; //에러X
다만 이렇게 리터럴 간의 연산은 컴파일러가 알아서 계산해주기 때문에 에러가 발생하지 않는다.
3. 나머지 연산자 %
나머지 연산자는 왼쪽의 피연산자를 오른쪽 피연산자로 나누고 난 나머지 값을 결과로 반환한다.
System.out.println(10%8);
System.out.println(-10%8);
System.out.println(10%-8);
System.out.println(-10%-8);
실행결과
2
-2
2
-2
피연산자의 부호를 모두 무시하고, 나머지 연산을 한 결과에 왼쪽 피연산자의 부호를 붙이면 된다.
비트 연산자
- 비트 연산자는 피연산자를 이진수로 표현했을 때, 각 비트 단위로 연산한다. (피연산자는 정수(문자 포함)만 허용된다)
1. | (OR 연산), & (AND 연산), ^ (XOR 연산)
1) OR 연산
OR 연산은 주로 특정 비트의 값을 변경할 때 사용한다.
아래의 식은 피연산자 0xAB의 마지막 4bit를 'F'로 변경하는 방법을 보여준다.
식 | 2진수 | 16진수 |
0xAB | 0xF = 0xAF | 1 0 1 0 1 0 1 1 0 0 0 0 1 1 1 1 |
0xAB |
0xF | ||
1 0 1 0 1 1 1 1 | 0xAF |
2) AND 연산
AND 연산은 주로 특정 비트의 값을 Qhqdksof 때 사용한다.
아래의 식은 피연산자 0xAB의 마지막 4bit가 어떤 값인지 알아내는데 사용되었다.
식 | 2진수 | 16진수 |
0xAB & 0xF = 0xB | 1 0 1 0 1 0 1 1 0 0 0 0 1 1 1 1 |
0xAB |
0xF | ||
1 0 1 0 1 0 1 1 | 0xB |
2) XOR 연산
XOR 연산은 두 피연산자의 비트가 다를 때만 1이 된다.
그리고 같은 값으로 다시 XOR 연산을 수행하면 원래의 값으로 돌아오는 특징이 있어서 간단한 암호화에 사용된다.
식 | 2진수 | 16진수 |
0xAB ^ 0xF = 0xA4 | 1 0 1 0 1 0 1 1 0 0 0 0 1 1 1 1 |
0xAB 0xF |
0xA4 ^ 0xF = 0xAB | 1 0 1 0 0 1 0 0 0 0 0 0 1 1 1 1 1 0 1 0 1 0 1 1 |
0xA4 0xF 0xAB |
2. 비트 전환 연산자 ~
~ 연산자는 피연산자를 2진수로 표현했을 때, 0은 1로, 1은 0으로 바꾼다.
식 | 2진수 |
0xA | 0 0 0 0 1 0 1 0 |
~ 0xA | 1 1 1 1 0 1 0 1 |
3. 쉬프트 연산자 << >>
쉬프트 연산자는 피연산자를 2진수로 표현했을 때, 각 자리를 왼쪽(<<) 또는 오른쪽(>>)으로 이동한다.
1) << 연산자
식 | 2진수 |
0x8 | 0 0 0 0 1 0 0 0 |
0x8 << 2 | 0 0 1 0 0 0 0 0 |
자리이동으로 인해 저장 범위를 벗어난 값은 버려지고, 빈자리는 0으로 채워진다.
2. >> 연산자
식 | 2진수 |
0x8 | 0 0 0 0 1 0 0 0 |
0x8 >> 2 | 0 0 0 0 0 0 1 0 |
식 | 2진수 |
0xF8 | 1 1 1 1 1 0 0 0 |
0xF8 >> 2 | 1 1 1 1 1 1 1 0 |
부호있는 정수는 부호를 유지하기 위해 왼쪽 피연산자가 음수인 경우 빈자리를 1로 채운다. 물론 양수일 때는 0으로 채운다.
+) 자바에서는 추가로 >>> 연산자를 제공한다. 이는 음수, 양수 상관 없이 무조건 빈자리를 0으로 채우는 연산자이다.
>>> 연산자를 적용한 예시를 보자. (중앙값 계산)
int start = 2_100_000_000;
int end = 2_100_000_002;
int mid1 = (start + end) / 2; //오버플로우
int mid2 = start + (end - start) / 2;
int mid3 = (start + end) >>> 1;
- 보통 가장 흔하게 사용하는 방법이 1번 방식인데, 이 방법은 int의 크기를 벗어나면 오버플로우가 발생할 수 있다.
- 그래서 2번 방식을 사용하는 것이 직관적이고 명확하다. 가장 권장하는 방식이다.
- 3번 방식은 면접 때 활용하거나 하면 좋은 멋내기(?)용 방법이다. >> 연산자를 사용하지 않는 이유는 (start + end)에서 오버플로우가 발생하여 음수가 되었을 때, 빈자리에 1을 채워 또 다시 음수가 되기 때문에, 빈자리에 0을 채우는 >>> 연산자를 사용한다.
관계 연산자
- 관계 연산자는 비교 연산자라고도 하며, 두 피연산자를 비교하는 데 사용되는 연산자다.
- 연산 결과는 오직 true와 false 둘 중의 하나이다.
프리미티브 타입에는 boolean 형을 제외한 나머지 자료형에 다 사용할 수 있지만 레퍼런스 타입에서는 사용할 수 없다.
비교연산자 | 연산결과 |
> | 좌변 값이 크면, true 아니면 false |
< | 좌변 값이 작으면, true 아니면 false |
>= | 좌변 값이 크거나 같으면, true 아니면 false |
<= | 좌변 값이 작거나 같으면, true 아니면 false |
== | 두 값이 같으면 true 아니면 false |
!= | 두 값이 다르면 true 아니면 false |
문자열의 비교
두 문자열을 비교할 때는, 비교 연산자 '==' 대신 equals()라는 메서드를 사용해야 한다.
String str = new String("abc");
boolean result = str.equals("abc"); //내용이 같으므로 true
boolean result2 = (str == "abc"); //내용은 같지만 서로 다른 객체라서 false
논리 연산자
- 논리 연산자는 둘 이상의 조건을 연결하여 하나의 식으로 표현할 수 있게 해준다.
- 논리 연산자는 피연산자로 boolean형 또는 boolean형 값을 결과로 하는 조건식만을 허용한다.
ex) x > 10 && x < 20
x | y | x || y | x && y | !x |
true | true | true | true | false |
true | false | true | false | false |
false | true | true | false | true |
false | false | false | false | true |
효율적인 연산
논리 연산자는 효율적인 연산을 한다는 특징이 있다.
OR 연산 '||'의 경우, 두 피연산자 중 어느 한 쪽만 '참'이어도 전체 연산결과가 '참'이므로 좌측 피연산자가 '참'이면, 우측 피연산자의 값은 평가하지 않는다.
따라서, 연산결과가 '참'일 확률이 높은 피연산자를 연산자의 왼쪽에 놓아 더 빠른 연산결과를 얻을 수 있다.
ex)
('a' < ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')
위 예시는 문자 ch가 소문자 또는 대문자인지 확인하는 것인데, 사용자로부터 문자 ch를 입력받을 때, 대문자보다 소문자를 입력할 확률이 높다고 판단했기 때문에 ch가 소문자인 조건을 대문자인 조건보다 왼쪽에 놓았다.
AND 연산 '&&'도 마찬가지로 어느 한쪽만 '거짓'이어도 전체 연산결과가 '거짓'이므로 좌측 피연산자가 '거짓'이면, 우측 피연산자의 값은 평가하지 않는다.
instanceof
- 레퍼런스 타입, 즉 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof연산자를 사용한다.
- 주로 조건문에 사용되며, instanceof의 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치한다.
- 연산의 결과로 boolean값을 반환한다.
다음 예시를 보자.
void doWork(Car car) {
if (c instanceof FireEngine) {
FireEngine fe = (FireEngine) c;
fe.water;
} else if (c instanceof Ambulance) {
Ambulance a = (Ambulance) car;
a.siren();
}
}
Car타입의 참조변수 c를 매개변수로 하는 메서드이다. 만약 Car클래스의 자손타입으로 FireEngine과 Ambulance클래스가 있다고 할 때, c는 실제로 어떤 타입의 인스턴스인지 메서드 내에서는 알 길이 없다.
그래서 intanceof 연산자를 이용해 참조변수 c가 가리키고 있는 인스턴스의 타입을 체크하고, 적절히 형변환한 다음에 작업을 해야 한다.
예를 들어, fe.water메서드는 Car가 아닌 FireEngine클래스에서만 호출이 가능하기 때문에
instanceof로 인스턴스 타입 확인 -> FireEngine으로 형변환 -> water 메서드 호출의 과정이 필요하다.
-> 어떤 타입에 대한 instanceof연산의 결과과 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.
assignment(=) operator
1) 대입 연산자는 연산 진행 방향이 오른쪽에서 왼쪽이기 때문에 'x=y=3'에서 'y=3'이 먼저 수행되고 그 다음에 'x=y'가 수행된다.
2) Ivalue와 rvalue
대입 연산자의 왼쪽 피연산자를 Ivalue(left value), 오른쪽 피연산자를 rvalue(right value)라고 한다.
rvalue는 변수뿐만 아니라 식이나 상수 등이 모두 가능한 반면, lvalue는 반드시 변수처럼 값을 변경할 수 있는 것이어야 한다. 그래서 리터럴이나 상수같이 값을 저장할 수 없는 것들은 lvalue가 될 수 없다.
int i = 0;
3 = i + 3; //에러. lvalue가 값을 저장할 수 있는 공간이 아니다.
i + 3 = i; //에러. lvalue의 연산결과는 리터럴이다.(i+3->0+3->3)
복합 대입 연산자
op= | = |
i += 3; | i = i + 3; |
i -= 3; | i = i - 3; |
i *= 3; | i = i * 3; |
i /= 3; | i = i / 3; |
i %= 3; | i = i % 3; |
i <<= 3; | i = i << 3; |
i >>= 3; | i = i >> 3; |
i &= 3; | i = i & 3; |
i |= 3; | i = i | 3; |
i ^= 3; | i = i ^ 3; |
i *= 10 + j; | i = i * (10 + j); |
화살표(->) 연산자
화살표 연산자는 자바8부터 추가된 람다식에서 사용하는 연산자이다.
람다식은 메서드를 하나의 '식'으로 표현한 것이다.
메서드를 람다식으로 표현하면 메서드의 이름과 반환값을 제거하고 매개변수 선언부와 몸통{ } 사이에 화살표 연산자(->)를 추가한다.
//메서드
반환타입 메서드이름(매개변수 선언) {
문장들
}
//람다식
(매개변수 선언) -> {
문장들
}
ex)
//메서드
int sum(int a, int b) {
return a + b;
}
//람다식
(int a, int b) -> {
return a + b;
}
3항 연산자
자바에서 삼항 연산자는 조건 연산자 하나뿐이다.
조건 연산자는 조건식, 식1, 식2 모두 세 개의 피연산자를 필요로 한다.
조건식 ? 식1 : 식2 -> 조건식의 결과가 true이면 식1이, false이면 식2가 연산결과가 된다.
ex)
result = (x > y) ? x : y;
식 'x > y'의 결과가 true이면, 변수 result에는 x의 값이 저장되고, false이면 y의 값이 저장된다.
조건 연산자를 중첩해서 사용하면 셋 이상 중의 하나를 결과로 얻을 수 있다. 아래의 식은 x의 값이 양수면 1, 0이면 0, 음수면 -1, 즉 셋 중 하나를 결과로 반환한다.
result = x > 0 ? 1 : (x == 0 ? 0 : -1);
조건 연산자를 여러 번 중첩하면 코드가 간략해지긴 하지만, 가독성이 떨어지므로 꼭 필요한 경우에 한번 정도만 중첩하는 것이 좋다.
연산자 우선 순위
- 산술 > 비교 > 논리 > 대입. 대입은 제일 마지막에 수행된다.
- 단항(1) > 이항(2) > 삼항(3). 단항 연산자의 우선순위가 이항 연산자보다 높다.
- 단항 연산자와 대입 연산자를 제외한 모든 연산의 진행방향은 왼쪽에서 오른쪽이다.
Java 13. switch 연산자
switch 연산자는 아니지만 자바는 switch ~ case 문법을 가지고 있다.
int result = 0;
String day = "WEDNESDAY";
switch (day) {
case "MONDAY":
case "FRIDAY":
case "SUNDAY":
result = 6;
break;
case "TUESDAY":
result = 7;
break;
case "THURSDAY":
case "SATURDAY":
result = 8;
break;
case "WEDNESDAY":
result = 9;
break;
default:
throw new IllegalStateException("Invalid day: " + day);
}
System.out.println(result);
이 방식의 문제점은 다음과 같다.
- 수많은 case, break... 너무 장황하다.
- break를 빼먹을 경우 다음 분기로 넘어가게 된다.
- return 값이 존재할 수 없다.
그래서 자바 12버전부터 switch 연산자를 제공한다.
String day = "WEDNESDAY";
int result = switch (day) {
case "MONDAY", "FRIDAY", "SUNDAY" -> 6;
case "TUESDAY" -> 7;
case "THURSDAY", "SATURDAY" -> 8;
case "WEDNESDAY" -> 9;
default -> throw new IllegalStateException("Invalid day: " + day);
};
System.out.println(result);
- 화살표(->) 연산자 표현으로 간결한 코드를 작성할 수 있다.
- break를 적지 않아도 된다.
- data만 존재할 경우 return이 가능하다.
자바 13 버전은 자바 12 버전에서 yield 명령이 추가되었다. yield x 하게 되면 x가 리턴된다.
String day = "WEDNESDAY";
int result = switch (day) {
case "MONDAY":
case "FRIDAY":
case "SUNDAY":
yield 6;
case "TUESDAY":
yield 7;
case "THURSDAY":
case "SATURDAY":
yield 8;
case "WEDNESDAY":
yield 9;
default:
throw new IllegalStateException("Invalid day: " + day);
}
System.out.println(result);
'java > java' 카테고리의 다른 글
[Java] 제어문 (3) | 2021.06.29 |
---|---|
[Java] Java 환경 변수 설정 - MacOS (0) | 2021.06.29 |
[Java] 배열의 복사 (0) | 2021.06.23 |
[Java] 배열의 출력 (0) | 2021.06.23 |
[Java] 자바 데이터 타입, 변수 그리고 배열 (1) | 2021.06.22 |