https://github.com/whiteship/live-study/issues?q=is%3Aissue+is%3Aclosed
목차
1. 산술 연산자
2. 비트 연산자
3. 관계 연산자
4. 논리 연산자
5. instanceof
6. assignment(=) operator
7. 화살표 연산자(->)
8. 3항 연산자
9. 연산자 우선 순위
(optional) Java 13. switch 연산자
1. 산술 연산자
산술 변환
이항 연산자는 연산을 수행하기 전에 피연산자의 타입을 일치시킨다.
- int 보다 크기가 작은 타입을 int로 변환한다.
- byte + short -> int + int -> int
- 피연산자 중 표현 범위가 큰 타입으로 형변환한다.
- byte + int -> int + int -> int
- 더 큰 범위를 표현하기 위해
byte a = 10;
byte b = 20;
byte c = a + b; // Error a+b가 int로 바뀌었기 때문
byte c = (byte)a + b; // Error a가 byte로 바뀌고 다시 int로 변환된다.
byte c = (byte)(a+b); // 올바른 코드
int i = 'B' - 'A'; //결과는 1. 둘다 int로 변환됐기 때문
int i = '2' - '0'; //결과는 2. 숫자 문자열을 int로 바꾸는데 활용할 수 있다.
형변환 및 오버플로우
int a = 1000000;
int b = 2000000;
long c = a * b; //오버플로우. a*b가 int기 때문에
long c = (long)a * b//올바른 코드 long *int -> long*long
형변환 및 오버플로우2
long a = 1000000 * 1000000;// int끼리의 연산이기 때문에 오버플로우 발생.
long b = 1000000 * 1000000L;// 기대한 값이 나온다.
int c = 1000000 * 1000000 / 100000; // int형끼리 연산이기 때문에 오버플로우 발생.
int d = 1000000 / 1000000 * 100000; // 오버플로우 X
c와 d는 산술적으로는 같은 값이지만, 곱셈과 나눗셈은 연산 우선순위가 같아서 c의 경우 앞의 두 항을 곱하고 오버플로우가 발생하지만 d는 일어나지 않는다.
나눗셈
int a = 10;
int b = 4;
int c = a / b // 연산 결과가 int기 때문에 몫인 2가 나온다.
int d = a/(float)b // a가 float으로 자동형변환이되어 결과는 2.5f이다.
문자형 변수에서 산술연산자
char c1 = 'a'; //실제로는 유니코드 97이 저장된다.
char c2 = c1 + 1; //char 타입읜 c1이 int로 변경된다. int를 char에 저장하려하니 오류가 일어난다.
char c3 = (char)(c1 + 1); // 유니코드 98이 저장된다.
char c4 = ++c1; // 단항연산자를 사용하는 것은 허용된다.
char c5 = 'a' + 1 // 2번째 줄처럼 변수를 사용하는 것이 아니라 리터럴끼리 연산은 오류가 일어나지 않는다.
c5의 경우 리터럴의 연산은 c2와 달리 컴파일 시에 컴파일러가 계산하기 때문에 오류가 발생하지 않는다.
//바이트코드 살펴볼것
2. 비트 연산자
비트연산자
피연산자를 비트단위로 연산한다.
실수형을 제외한 기본형에 사용할 수 있다.
- OR연산자 ( | ) : 피연산자 중 어느 한쪽이 1이면 1이다.
- 특정 비트의 값을 변경하기 위해 사용한다.
- AND연산자 ( & ) : 피연산자 양 쪽 모두 1이면 1이다.
- 특정 비트의 값을 뽑아내기 위해 사용한다;
- XOR연산자 ( ^ ) : 피연산자가 서로 다를 때 1이다.
x | y | x|y | x&y | x^y |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
0 | 1 | 1 | 0 | 1 |
0 | 0 | 0 | 0 | 0 |
비트 전환 연산자
p = 10;
~p = - 11;
~p + 1 - -10;
2의 보수로 부호를 바꿀 수 있다.
3. 관계 연산자
비교 연산자
- 기본형 중에서 boolean 형을 제외한 나머지 자료형에서 사용할 수 있다.
- 참조형에서는 사용이 불가능하다.
- 피연산자를 같은 타입으로 변환한 후에 비교한다.
- 대소비교 연산자
int a = 1, b = 0
a > b //true
a < b //false
a >= b //true
a <= b //false
int a = 1, b = 1
a == b // true
a !=b // false
10.0d == 10.0f // true
0.1d == 0.1f // false
소수점 비교시 float을 double로 변환하는 과정에서 오차가 발생하므로 double을 float으로 형변환해서 비교하자
문자열 비교
String은 primitive type이 아닌 객체이다.
따라서 == 는 값 자체를 비교하는 것이 아닌 객체의 주소를 비교한다.
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
"abc" == "abc" //true
str1 == str2 // true
str1 == "abc" //true
str1 == str3 //false
str3 == "abc" //false
str1.eqauls(str3) //true
str3.equals("abc") //true
String은 리터럴은 한 곳에 저장하고 롭게 참조할 때 새로운 인스턴스를 사용하는 것이 아닌 미리 만들어 놓은 인스턴스를 사용한다. 따라서 str1과 str2는 같은 인스턴스를 가리키고 있고 따라서 str1 == str2, str1 == "abc"는 true이다.
equals 메서드는 String에서 문자열 자체가 같은지 비교한다.
4. 논리 연산자
피연산자가 반드시 boolean이고 연산결과도 boolean이다.
비트연산자의 결과가 1, 0이 아닌 것을 제외하고 같다.
- OR연산자 ( || ) : 피연산자 중 어느 한 쪽이 true이면 true이다.
- AND연산자 ( && ) : 피연산자 양 쪽 모두 true이면 true이다
x | y | x||y | x&&y |
true | true | true | true |
true | false | true | false |
false | true | true | false |
false | false | false | false |
논리연산자에서 실행속도
int i = 0;
int j = 0;
if (i++ == 0 || j ++ 0){
System.out.println("Hello")
}
System.out.println(i);
System.out.println(j);
실행 결과
1
0
|| 연산자는 앞의 조건이 만족되면 뒤의 조건을 실행하지 않기 때문에 j는 증가하지 않았다.
int i = 0;
int j = 0;
if (i++ == 0 | j ++ 0){
System.out.println("Hello")
}
System.out.println(i);
System.out.println(j);
실행 결과
1
1
하지만 논리연산자 ||가 아닌 비트연산자 |를 사용하면 앞의 조건과 상관 없이 뒤의 코드가 실행된다.
5. instanceof
참조형 변수의 타입을 체크할 수 있다. 인스턴스의 타입의 형변환을 체크하는데 사용할 수 있다.
class Parent {}
class Child extends Parent implements MyInterface {}
interface MyInterface {}
인스턴스 instanceof 클래스(class, interface, enum ..)
결과는 인스턴스가 피연산자 클래스와 같거나 혹은 그 자식 클래스이거나 혹은 인터페이스를 구현했으면 true이다.
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));
}
}
실행 결과
obj1 instanceof Parent: true
obj1 instanceof Child: false
obj1 instanceof MyInterface: false
obj2 instanceof Parent: true
obj2 instanceof Child: true
obj2 instanceof MyInterface: true
obj1은 Parent의 인스턴스로 Child의 자식이 아니고 MyInterface를 구현하지 않았기 때문에 false이다.
obj2는 Child의 인스턴스로 Parent의 자식이고 MyInterface를 구현했으므로 true이다.
6. assignment operator
변수와 같은 저장공간에 값 또는 수식의 결과를 저장한다.
연산자들 간의 가장 낮은 우선순위를 갖는다.
진행방향이 왼쪽이다.
왼쪽 피연산자 lvalue : 리터럴 혹은 상수 불가능
오른쪽 피연산자 rvalue : 모두 가능
복합대입연산자
op | = |
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 ^ 3 |
7. 화살표 연산자
비트 단위로 자리를 이동시키는 연산자
int x = 8 << 2 // 8 * 2^2 왼쪽으로 이동하는 것이기 때문에 2의 2승을 곱해준다.
int x = 8 >> 2 // 8 / 2^2 오른쪽으로 이동하는 것이기 때문에 2의 2승을 나눠준다.
쉬프트 연산자 >>와 >>>의 차이
쉬프트 연산자는 비트 단위로 값을 이동시킨다. 예를 들어 3 << 2 의 경우
3을 이진수로 표현하면 0000 0011인데 3 >>2는 왼쪽으로 두칸 이동하라는 의미이므로
0000 1100으로 이동해서 결과로 12가 나온다. 자리수를 넘어가면 버려지고 빈칸은 0으로 채운다.
반대로 >> 의 경우는 오른쪽으로 이동하는데 오른쪽으로 이동할 때는 >>와 >>> 두가지를 지원한다.
>>는 오른쪽으로 이동하고 맨 왼쪽의 빈칸을 부호를 유지하면서 값을 채운다.
그리고 >>>는 오른쪽으로 이동하고 왼쪽의 빈칸을 부호와 상관없이 0으로 채운다.
>>>을 이용해서 중간값을 구하는 예제를 통해 >>과 >>>의 차이를 이해할 수 있었다.
(물론 코드는 모두가 공유하는 것이니까 아래 방법을 다 같이 쓰는 코드로 사용할 수는 없을 것 같다.)
public class Operator {
public static void main(String[] args) {
int start = 2_000_000_000;
int end = 2_100_000_000;
//int mid = (start + end) >> 1
int mid = (start + end) >>> 1
System.out.println(mid);
}
}
주석의 경우에는 (start + end)에서 오버플로우가 일어나서 음수로 값이 바뀌었다. 그 상태에서 >> 1을 사용하면 빈칸을 1로 채워서 의도치 않은 값이 출력된다. 그래서 밑의 줄처럼 >>>을 사용하면 (start+end)가 음수로 바뀌어도 부호와 상관없이 0으로 채우기 때문에 의도한 대로 중간값을 얻을 수 있다.
20억 21억은 아니지만 비트 단위로 그려봤다.
8. 3항 연산자
삼항연산자
조건식의 연산결과가 true이면 ‘식1’dml 결과를 반환하고 false면 ‘식2’의 결과를 반환한다.
(조건식) ? 식1 : 식2
ex1) 절대값 구하기
int x = -10;
int absX = x>=0 ? x: -x;
ex2) 셋 중에 하나를 고르고 싶을때 중첩
int score = 50;
char grade = score >= 90 ? 'A' : (score >= 80? 'B' : 'C');
9. 연산자 우선 순위
연산자의 우선 순위
- 괄호의 우선순위가 제일 높다.
- 산술 > 비교 > 논리 > 대입
- 단항 > 이항 > 삼항
- 연산 진행방향은 왼쪽에서 오른쪽이다. 단 다항, 대입 연산자는 오른쪽에서 왼쪽이다.
주의할 점
- 쉬프트 연산자(>>)는 덧셈연산자보다 우선순위가 낮다. (즉 덧셈연산자 후 쉬프트)
int x = 2;
x << 2+1; // 값은 16
- or가 and보다 우선순위가 낮다. 괄호를 사용하면 편하다. (and 다음에 or)
- 비트 연산자가 비교연산자보다 우선순위가 낮다. (비교 연산자를 먼저)
Reference
1. 자바의 정석 3판
2. 자바 오라클 튜토리얼
'자바 > 백기선 자바스터디' 카테고리의 다른 글
[백기선 자바스터디] 인터페이스 (0) | 2022.08.10 |
---|---|
[백기선 자바스터디] 패키지 (0) | 2022.07.25 |
[백기선 자바스터디] 상속 (0) | 2022.07.14 |
[백기선 자바스터디] 클래스 (0) | 2022.07.06 |
[백기선 자바스터디] 자바 데이터 타입, 변수 : 형변환, 타입추론 (0) | 2022.06.25 |