3장. 연산자
목표
자바가 제공하는 다양한 연산자를 학습하세요.
학습할 것
산술 연산자
우리가 기본적으로 우리 세계에서 자주 만나고 사용했던 수학 연산자라고 생각하면 편할 것 같다.
기본적으로 +
, -
, *
, /
, %
연산자를 말할 수 있다.
+
public int add(int a, int b) {
return a + b;
}
println(add(1, 2)); // 3
기본적인 수학이기에 따로 설명하지 않겠습니다.
-
public int subtract(int a, int b) {
return a - b;
}
기본적인 수학이기에 따로 설명하지 않겠습니다.
println(subtract(1, 2)); // -1
*
public int multiply(int a, int b) {
return a * b;
}
println(multiply(1, 2)); // 2
기본적인 수학이기에 따로 설명하지 않겠습니다.
/
public int divide(int a, int b) {
return a / b;
}
println(divide(1, 2)); // 0
여기서 중요한 것은 타입이 int
이기 때문에 1 / 2
는 0
이 된다. int
는 기본적으로 소수점을 표현하지 변수 타입이기 때문에 이 부분에서 값이 사라진다.
저번 스터디 강의에서 들은 내용으로 만약 소수점을 저장하기 위해서 어떤 변수 타입을 사용해야 되냐는 답을 해주셨다.
일단 double
과 float
는 기본적으로 부동소수점 정확도 문제가 발생하기 때문에 값의 변형이 일어날 수 있는 위험이 있다.
그래서 BigDecimal
이라는 변수 타입을 사용하는 것을 권장한다고 합니다.
BigDecimal
을 객체로 선언해서 만들어야 되면 +
, -
, *
, /
이런 식으로 연산자를 사용하는 것이 불가능하다.
BigDecimal a = new BigDecimal(100);
BigDecimal b = new BigDecimal(3);
a.add(b);
a.subtract(b);
a.multiply(b);
a.devide(b);
위와 같은 연산으로 사용할 수 있다. 추가적으로 밑에 같은 경우가 있다.
BigDecimal a = new BigDecimal(100);
BigDecimal b = new BigDecimal(3);
a.devide(b); // => 무한대로 가는 경우 exception 발생
a.devide(b, RoundingMode.HALF_EVEN); // => 값을 어떤 식으로 처리할 것인지에 대해서 지정할 수 있다.
%
public int modular(int a, int b) {
return a % b;
}
println(modular(1, 2)); // 1
이거는 기본 수학에서 만나지 못하는 연산자일텐데요. 대학에서 정수론에서 배운 모듈러 연산
과 같은 방식으로 동작합니다.
a % b
를 했을 경우 a
를 b
로 나눴을 때 a가 b보다 작아지기 위해서 b의 배수를 빼는 과정이 진행됩니다.
5 % 2
5 - 2 * ??? // 5와 가장 가까운 4가 되기 위해 ???는 2가 됩니다.
5 - 4
=> 1
---
10 % 7 = 3
144 % 2 = 0
145 % 2 = 1
...
이런 과정으로 연산이 진행된다고 생각하면 될 것 같습니다.
비트 연산자 & 논리 연산자
기본적으로 bit
라는 개념을 알아야 합니다. bit
는 데이터를 저장하는 단위라고 생각하면 될 것 같으며 0
과 1
을 기억합니다.
여기서 이 bit
라는 것을 8개 모으게 되면 byte라는 개념을 만들 수 있습니다.
한마디로 8bit는 1byte라는 단위가 되는 것입니다. (1000m = 1km, 8bit = 1byte)
0 0 0 0 0 0 0 0 => 1byte
| |
여기가 07번째 비트|
여기가 0번째 비트
그런데 이 1byte, 8bit를 우리가 아는 일반적인 숫자로 표현이 가능합니다.
0번째 bit는 2^0, 1번째 bit는 2^1, 2번째 bit는 2^2, …, 7번째 bit는 2^7의 값을 가지고 있습니다.
만약 bit가 켜져있다면(값이 1이라면) 해당 비트에 해당하는 곱합니다.
1 0 0 0 0 0 0 1 => 1 * 2^7 + 0 * 2^6 + ... + 1 * 2^0 = 128 + 1 = 129
위와 같은 식으로 계산이 가능합니다.
이젠 연산에 대해서 설명 드리겠습니다.
&
AND
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
해당 값들은 단순히 1을 의미하는 것이 아닌 bit의 값을 표현하고 있습니다. 한마디로 서로 해당 비트의 값이 저런 형식으로 표현할 수 있다는 것입니다.
AND
연산자는 두 개 값 모두가 1이어야지만 1이 나오는 연산을 진행합니다.
5 & 3
---
5 => 1 0 1
3 => 0 1 1
---
5 & 3 = 0 0 1 = 1
위에 예제처럼 값은 1로 되어버립니다.
|
OR
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
OR
연산자는 모두 중에 하나만 1이어도 1을 반환하게 됩니다.
5 | 3
---
5 => 1 0 1
3 => 0 1 1
---
5 | 3 = 1 1 1 = 7
위에 예저에서 보면 값은 7이 됩니다.
<<
, >>
SHIFT
shift 연산자는 해당 비트를 움직이게 만듭니다. 간단하게 예를 들면
5 => 1 0 1
5<<1 => 1 0 1 0 => 10
5<<2 => 1 0 1 0 0 => 20
5>>1 => 0 1 0 => 2
5>>2 => 0 0 1 => 1
5라는 값을 한번 왼쪽으로 shift를 진행하고 오른쪽 끝에 0번째 비트에 0인 값으로 생성된다.
단순히 보게되면 왼쪽으로 shift를 진행할 때 마다 2배씩 커지게 됩니다.
이것과 반대로 오른쪽으로 쉬프트를 하게 되면 2배씩 작아지게 됩니다.
제가 설명한 연산자들 말고도 ^
(XOR), ~
(toggle) 연산자와 같이 여러 다른 연산자가 존재합니다.
관계 연산자
관계 연산자는 기본적으로 2개의 수를 가지고 어떤 관계를 가지고 있는지에 대한 결과를 만들어냅니다.
우리가 보통 알고 있는 <
, >
, ==
가 여기에 해당된다.
primitive type
5 < 4 // true
5 > 4 // false
5 == 4 // false
5 != 4 // true
객체
자바에는 primitive type을 제외하고는 모두 객체라고 말해도 된다. 이런 부분을 생각했을 때 ==
는 어떻게 동작하는 지 보자.
객체는 내부적으로 여러 값을 가질 수 있습니다. 그리고 그것이 모두 같은 경우 같다고 볼 수 있습니다. (상황에 따라 다르지만 이런 경우에 보통 같다고 할 수 있을 것 같습니다.)
public class Test {
static class Student {
int number;
String name;
public Student(int number, String name) {
this.number = number;
this.name = name;
}
}
public static void main(String[] args) {
Student s1 = new Student(1, "배지");
Student s2 = new Student(1, "배지");
if (s1 == s2) System.out.println("Same");
else {
System.out.println("Not Same");
System.out.println(s1);
System.out.println(s2);
}
if (s1.equals(s2)) System.out.println("Same");
else System.out.println("Not Same");
}
}
---
Not Same
test.Test$Student@56cbfb61
test.Test$Student@1134affc
Not Same
위와 같은 결과가 나왔습니다. 일반적으로 우리는 같은 친구일 것이라고 생각하는데 그렇지 않습니다. 이유는 객체에서 ==
을 활용해서 값을 비교할 때에는 해당 주소값이 같은지를 확인하기 때문에 내부 원소가 같은지를 비교하지 않습니다.
그리고 equals()
을 활용해서 비교를 해도 같지 않다고 나오는데 이 부분은 기본적으로 Object
을 상속받아서 만들어지기 때문에 모두 equals()
메소드를 상속받습니다. 내부 구조를 보게 되면
public boolean equals(Object obj) {
return (this == obj);
}
이런 구조인데 위에 ==
하는 것도 똑같다고 생각합니다.
이 부분을 해결하기 위해서는 equals()
를 오버라이딩해야 됩니다. intellij에서는 해당 메소드 오버라이딩을 쉽게 할 수 있기 때문에 바로 만들 수 있습니다. 혹은 롬복을 사용하면 됩니다. 롬복은 사용하는데 있어서 유의사항이 있습니다.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return number == student.number &&
Objects.equals(name, student.name);
}
---
Not Same
test.Test$Student@179770
test.Test$Student@179770
Same
위와 같은 코드를 추가해서 동작하게 되면 단순 ==
을 하는 부분은 여전히 같다고 나오지 않지만 equals()
을 통해서 진행하는 로직에서는 같다고 나옵니다.
기존 C++을 공부했을 때 연산자 오버라이딩이라는 개념으로 클래스끼리 연산자를 정의할 수가 있었는데 Java에서는 지원하지 않습니다.
instanceof
해당 키워드는 형 변환이 가능한지, 해당 class와 같은 타입인지 혹은 자식 타입인지를 확인하는 키워드입니다.
a(변수) instanceof A(class)
이런 식으로 사용됩니다.
a instanceof Object
에서 a에 어떤 타입을 넣어도 true가 반환되는 이유는 Object는 모든 객체의 부모 클래스이기 때문입니다.
예외로는 null instanceof Object
는 false가 나오게 됩니다.
assignment(=) operator
우리는 어떤 값 (value)를 특정 변수에 주입하기 위해서 =
을 사용하게 됩니다.
여기서 활용할 수 있는 것은 이 =
와 함께 사용되는 친구들이 존재한다는 것입니다. +=
, -=
, >>=
이런 식으로 표현할 수가 있습니다.
int a = 5;
a += 30;
---
int a = 5;
a = a + 30;
a = 5 + 30;
a = 35;
위와 같은 전개로 진행된다고 생각하면 될 것 같습니다.
화살표(->) 연산자
주로 stream()
을 활용해서 map
, filter
을 활용하는 경우에 자주 볼 수 있는 연산자입니다.
해당 연산자는 기본적으로 인터페이스를 직접 구현하는데 있어서 사용된다고 생각하면 됩니다. 자세하게 설명드리면
public interface hello {
int get(int number);
}
이런 interface가 있다고 가정했을 때 우리는 이 interface를 구현해야지 실제 해당 메소드를 사용할 수 있습니다. (default method는 여기서는 배제하겠습니다.)
class helloImp implements hello {
@Override
pubilc get (int number) {
return something;
}
}
---
main() {
hello h1 = new hello() {
@Override
pubilc get (int number) {
return something;
}
}
}
이런 식으로 구현을 해서 해당 메소드를 사용할 수 있을텐데요. 혹은 두번쨰 방식인 바로 구현해서 사용하는 방식이 있을 것 입니다. 하지만 이렇게 구현하게 되면 굉장히 번거롭고 가독성도 떨어트릴 수 있습니다. 이런 부분을 보완하기 위해서 나왔다는 생각이 듭니다.
해당 연산자를 사용하게 되면 위와 같은 경우는 아래와 같이 변경해서 사용할 수 있습니다.
hello h1 = (number) -> return somthing;
3항 연산자
해당 로직 움직이는 것 + 컨벤션
우리는 if
을 활용해서 어떤 같이 true이면 A로직, false이면 B로직을 동작하도록 많이 만듭니다. 이런 부분을 아주 간단하게 만들어준 것이 바로 3항 연산자
입니다.
boolean flag = false;
String result;
if(flag) {
result = "true";
} else {
result = "false";
}
---
boolean flag = false;
String result = flag ? "true" : "false";
위와 아래는 모두 같은 결과물을 만들어 낸다. ?
는 3항 연산자의 시작이라고 생각하면 됩니다. 현재 예제에서 flag
라는 변수의 true, false 값을 가지고 3항 연산자가 진행하겠다는 이야기입니다. :
를 기준으로 왼쪽은 true
인 경우에 해당하는 값이고 오른쪽은 false
에 해당하는 값이다.
연산자 우선 순위
표 넣기
(optional) Java 13. switch 연산자
In live study
-
논리 연산자는 피연산자가 boolean이냐 아니냐에 따라 다르다.
-
java 13에서 사용되는 switch operator (statement는 X)부터 아무 설정없이 사용가능하게 됨.
-
if문을 진행하면서 한쪽이 참이면 그 뒤에 있는 조건을 확인하지 않는다.
-
중간 값을 구할 때는 어떻게 할까요? ``` int start = 0; int end = 10;
int mid = (start + end) / 2 // 이 부분에서 오버 플로우가 날수가 있음.
안전한 방식
int mid = start + (end - start) / 2; // why?
int mid = (start + end) »> 1; // 비트 연산자 관련 ‘»>’. 양수에서만. 음수에서는 ㄴㄴ
- 내 눈에는 한 줄이지만 실제로 바이트 코드 상에서는 두 줄, 세 줄이 될 수 있다. 그것으로 인해서 멀티 프로그래밍 환경에서 문제가 발생할 수 있는 것이다.
- 문제: 배열에 하나 숫자를 제외하고 나머지 숫자들은 모두 2개씩 들어있다. 어떻게 해야될까요?
``` java
XOR 사용~~~
기선님이 추천한 글 모임
https://hsm622.blog.me/222150928707
https://catch-me-java.tistory.com/
https://b-programmer.tistory.com/226
https://velog.io/@uhan2/Java-Operator
https://whereishq.blogspot.com/2020/11/3.html
https://yadon079.github.io/2020/java%20study%20halle/week-03
Leave a comment