자바/백기선 자바스터디

[백기선 자바스터디] 연산자 : instanceof, shift 연산자

nonosa 2022. 7. 2. 21:56

https://github.com/whiteship/live-study/issues?q=is%3Aissue+is%3Aclosed 

 

GitHub - whiteship/live-study: 온라인 스터디

온라인 스터디. Contribute to whiteship/live-study development by creating an account on GitHub.

github.com

목차

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. 연산자 우선 순위

연산자의 우선 순위

  • 괄호의 우선순위가 제일 높다.
  • 산술 > 비교 > 논리 > 대입
  • 단항 > 이항 > 삼항
  • 연산 진행방향은 왼쪽에서 오른쪽이다. 단 다항, 대입 연산자는 오른쪽에서 왼쪽이다.

주의할 점

  1. 쉬프트 연산자(>>)는 덧셈연산자보다 우선순위가 낮다. (즉 덧셈연산자 후 쉬프트)
int x = 2;
x << 2+1; // 값은 16
  1. or가 and보다 우선순위가 낮다. 괄호를 사용하면 편하다. (and 다음에 or)
  2. 비트 연산자가 비교연산자보다 우선순위가 낮다. (비교 연산자를 먼저)

Reference

1. 자바의 정석 3판

2. 자바 오라클 튜토리얼