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