백기선 선생님의 자바 스터디 글이다
github.com/whiteship/live-study/issues/3
- 산술 연산자
- 비트 연산자
- 관계 연산자
- 논리 연산자
- instanceof
- assignment(=) operator
- 화살표(->) 연산자
- 3항 연산자
- 연산자 우선 순위
- (optional) Java 13. switch 연산자
이것이 자바다 책에서는 연산의 개념을 다음과 같이 정의하고 있다
연산자의 기본 개념
연산(operations) : 프로그램에서 데이터 처리하여 결과를 산출하는 것
연산자(operator) : 연산에 사용되는 표시나 기호
피연산자(operand) : 연산되는 데이터
연산식(expressios) : 연산자와 피연산자의 연산 과정
단어와 뜻도 비슷하면서도 헷갈리다.
하지만 꼭 머리에 외울려고 노력안해도 개발언어는 실습으로 체험해보는게 가장 빠르게 습득된다
우선 연산자의 종류부터 알아보고 진행하자
연산자 종류 | 연산자 | 피연산자 수 | 산출값 | 기능 설명 |
산술 | +, -, *, /, % | 이항 | 숫자 | 사칙연산 및 나머지 계산 |
부호 | +, - | 단항 | 숫자 | 음수와 양수의 부호 |
문자열 | + | 이항 | 문자열 | 두 문자열을 연결 |
대입 | =, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>=, >>>= | 이항 | 다양 | 우변의 값을 좌변의 변수에 대입 |
증감 | ++, -- | 단항 | 숫자 | 1만큼 증가/감소 |
비교(관계) | ==, !=, >, <, >=, <=, instanceof | 이항 | boolean | 값의 비교 |
논리 | !, &, |, &&, || | 단항 이항 |
boolean | 논리적 NOT, AND, OR 연산 |
조건 | (조건식) ? A(참) : B(거짓) | 삼항 | 다양 | 조건식에 따라 A 혹은 B 선택 |
비트 | ~, &, |, ^ | 단항 이항 |
숫자 boolean |
비트 NOT, AND, OR, XOR 연산 |
쉬프트 | >>, <<, >>> | 이항 | 숫자 | 비트를 좌측/우측으로 밀어서 이동 |
여기서 단항, 이항, 삼항이 나오는데 이는 연산자가 필요로 하는 피연산자 수를 의미한다
단항은 하나
이항은 두개
삼항은 세개이다
다음은 간단한 예시이다
// 단항 - 하나의 피연산자가 필요
x++;
// 이항자 - 두개의 피연산자가 필요
sum = x + y;
// 삼항 - 세개의 피연산자가 필요
(sum > 90) ? "A" : "B;"
산술 연산자
흔히 사회에서도 많이 사용하는 사칙연산자 더하기(+), 빼기(-), 곱하기(*), 나누기(/), 나머지(%)를 말한다
다음은 간단한 예시이다
int a = 10;
int b = 4;
System.out.println("a + b = " + (a + b)); // 14
System.out.println("a - b = " + (a - b)); // 6
System.out.println("a * b = " + (a * b)); // 40
System.out.println("a / b = " + (a / b)); // 2
System.out.println("a % b = " + (a % b)); // 2
정수의 자료형 int 를 이용한 결과이다
값은 잘 나왔다. 하지만 무언가 이상하지 않은가?
a / b 결과값이 2 이다. 10 을 4 로 나누면 2.5 가 나와야 되지 않은가?
이전에 리뷰했던 2주차를 참고하면 정수형 자료형에서는 실수를 표현할 수 없기에 생긴 일로 잘못된 결과물이 산출되었다
많이 실수하는 부분 중 하나로 실수가 나올 수 있는 계산식인지 파악 후 자료형을 잘 정해주길 바란다
double a = 10;
double b = 4;
System.out.println("a / b = " + (a / b)); // 2.5
정확한 결과가 산출되었다
다음은 정수형과 실수형의 연산 결과이다
int a = 10;
double b = 4d;
System.out.println("a + b = " + (a + b)); // 14.0
System.out.println("a - b = " + (a - b)); // 6.0
System.out.println("a * b = " + (a * b)); // 40.0
System.out.println("a / b = " + (a / b)); // 2.5
System.out.println("a % b = " + (a % b)); // 2.0
정수형과 실수형이 서로 연관된 결과이다. 지난 주 타입 변환에 대해 공부했다면 감이 왔을거다. 앞으로 수 없이 많은 연산을 하다보면 매번 접하게 될 캐스팅 유형으로 서로 연산된 자료형 중 더 큰 자료형으로 변환되었다. 현재 위의 연산 결과의 자료형은 double 이다.
사칙연산을 하다보면 발생하는 유의사항이 존재한다
그것은 바로 나머지(%), 나눗셈(/) 에 0 이 사용되는 경우이다
int a = 5;
System.out.println("a % 0 = " + (a % 0)); // 에러 발생
System.out.println("a / 0 = " + (a / 0)); // 에러 발생
정수형으로 나눌 경우 에러가 발생한다 정수형으론 값을 표현할 수 없기 때문이다
실수형으로 계산을 하면 값을 표현할 수 있다
double a = 5d;
System.out.println(a / 0); // Infinity
System.out.println(a % 0); // NaN
Infinity, NaN 결과가 나오면 더 이상 연산을 권하지 않는다. 무엇을 더하거나 빼도 Infinity, NaN 이 산출되어 데이터가 엉망이 되기 때문이다
double infi = 5.0 / 0;
double nan = 5.0 % 0;
System.out.println(infi + 1); // Infinity
System.out.println(infi - 1); // Infinity
System.out.println(infi * 1); // Infinity
System.out.println(infi / 1); // Infinity
System.out.println(infi % 1); // Infinity
System.out.println(nan + 1); // NaN
System.out.println(nan - 1); // NaN
System.out.println(nan * 1); // NaN
System.out.println(nan / 1); // NaN
System.out.println(nan % 1); // NaN
비트 연산자
데이터 비트(bit) 단위로 연산해서 비트연산자이다. 비트 단위인 0과 1인 피연산자이며 정수형만 연산이 가능하다.
비트 연산자는 2가지로 구분된다.
1. 비트 논리 연산자 (&, |, ^, ~)
구분 | 연산식 | 결과 | 설명 | ||
---|---|---|---|---|---|
AND (논리곱) |
1 | & | 1 | 1 | 두 비트 모두 1일 경우에만 결과는 1 |
1 | 0 | 0 | |||
0 | 1 | 0 | |||
0 | 0 | 0 | |||
OR (논리합) |
1 | | | 1 | 1 | 두 비트 중 하나만 1이면 결과는 1 |
1 | 0 | 1 | |||
0 | 1 | 1 | |||
0 | 0 | 0 | |||
XOR (배타적 논리합) |
1 | ^ | 1 | 0 | 두 비트 중 하나는 1 하나는 0일 경우 결과는 1 |
1 | 0 | 1 | |||
0 | 1 | 1 | |||
0 | 0 | 0 | |||
NOT (논리 부정) |
~ | 1 | 0 | 보수 | |
0 | 1 |
이해하기 쉽게 직접 실행해보자
// 비트 논리 연산
int a = 20; // 0000 0000 0000 0000 0000 0000 0001 0100
int b = 11; // 0000 0000 0000 0000 0000 0000 0000 1011
// 0001 0100
// &
// 0000 1011
// -> 0000 0000 으로 10진법으로 0
System.out.println(a & b);
// 0001 0100
// |
// 0000 1011
// -> 0001 1111 으로 10진법으로 31
System.out.println(a | b);
// 0001 0100
// ^
// 0000 1011
// -> 0001 1111 으로 10진법으로 31
System.out.println(a ^ b);
// 0000 0000 0000 0000 0000 0000 0001 0100
// ~
// -> 1111 1111 1111 1111 1111 1111 1110 1011 으로 10진법으로 -21
System.out.println(~a);
// 0000 0000 0000 0000 0000 0000 0000 1011
// ~
// -> 1111 1111 1111 1111 1111 1111 1111 0100 으로 10진법으로 -12
System.out.println(~b);
2. 비트 이동 연산자 (<<, >>, <<<)
구분 | 연산식 | 설명 | |||||
이동(쉬프트) | a | << | b | 정수 a의 각 비트를 b만큼 왼쪽으로 이동(빈자리는 0으로 채움) | |||
a | >> | b | 정수 a의 각 비트를 b만큼 오른쪽으로 이동(빈자리는 정수 a의 최상위 부호 비트값으로 채움) | ||||
a | <<< | b | 정수 a의 각 비트를 b만큼 오른쪽으로 이동(빈자리는 0으로 채움) |
여기서 잠깐! 10진법 표현에서 2진법 표현으로 값을 보고 싶다면 Integer.toBinaryString 을 사용하면 된다.
// 비트 이동 연산
int a = 33;
// Tips : Integer.toBinaryString 를 사용하면 비트를 알 수 있다.
System.out.println(Integer.toBinaryString(a)); // 0000 0000 0000 0000 0000 0000 0010 0001
// 10001 를 좌측으로 3칸 이동하라
// 0000 0000 0000 0000 0000 0001 0000 1000
System.out.println(Integer.toBinaryString(a << 3));
// 10001 를 우측으로 4칸 이동하라
// 0000 0000 0000 0000 0000 0000 00?? ??10
// 이동되어 변한 물음표 ? 에는 최상위 비트값이 들어간다. 현재 양수로 0 이 들어감. 음수면 1 이 들어가게됨
System.out.println(Integer.toBinaryString(a >> 4)); // 10
// 10001 를 우측으로 5칸 이동하라
// 0000 0000 0000 0000 0000 0000 0000 0001
System.out.println(Integer.toBinaryString(a >>> 5)); // 1
관계(비교) 연산자
관계 연산자는 피연산자를 비교하여 true/false 을 산출한다. 흐름 제어문(조만간 다룰 예정)에 자주 사용된다.
관계 연산자는 동등비교와 크기비교로 구분된다
구분 | 연산식 | 설명 | |||||
동등비교 | 피연산자1 | == | 피연산자2 | 두 연산자의 값이 같은 지 비교 | |||
피연산자1 | != | 피연산자2 | 두 연산자의 값이 다른 지 비교 | ||||
크기비교 | 피연산자1 | > | 피연산자2 | 피연산자1 이 큰지 비교 | |||
피연산자1 | >= | 피연산자2 | 피연산자1 이 크거나 같거나 비교 | ||||
피연산자1 | < | 피연산자2 | 피연산자1 이 작은지 비교 | ||||
피연산자1 | <= | 피연산자2 | 피연사자1이 작거나 같거나 비교 |
// 관계 연산자
int a = 10;
// int b = 20;
byte b = 20;
// 동등비교
System.out.println("a == b : " + (a == b)); // false
System.out.println("a != b : " + (a != b)); // true
// 크기비교
System.out.println("a > b : " + (a > b)); // false
System.out.println("a >= b : " + (a >= b)); // false
System.out.println("a < b : " + (a < b)); // true
System.out.println("a <= b : " + (a <= b)); // true
부동소수점으로 최대한 근사값을 가지는 float, double 도 동등비교 == 를 사용하면 어떻게 나올까?
float f = 0.1f;
double d = 0.1d;
System.out.println("f == d : " + (f == d)); // false
둘 다 0.1 을 넣었지만 각자 표현하는 최소한의 근사값이 서로 달라 false 가 반환되었다. 같은 자료형으로 변환해야하거나 정수로 변환해서 비교해야한다.
논리 연산자
비트 연산자와 비슷하지만 boolean 만을 사용한다
구분 | 연산식 | 결과 | 설명 | ||
---|---|---|---|---|---|
AND (논리곱) |
true | & 혹은 && |
true | true | 두 연산자 모두가 true일 경우에만 결과는 true |
true | false | false | |||
false | true | false | |||
false | false | false | |||
OR (논리합) |
true | | 혹은 || |
true | true | 두 연산자 중 하나만 true면 결과는 true |
true | false | true | |||
false | true | true | |||
false | false | false | |||
XOR (배타적 논리합) |
true | ^ | true | false | 두 연산자 중 하나는 true 하나는 false일 경우 결과는 true |
true | false | true | |||
false | true | true | |||
false | false | false | |||
NOT (논리 부정) |
! | true | false | 피연산자의 논리값을 뒤바꾼다 | |
false | true |
System.out.println(true & true); // true
System.out.println(true & false); // false
System.out.println(false & true); // false
System.out.println(false & false); // false
System.out.println(true && true); // true
System.out.println(true && false); // false
System.out.println(false && true); // false
System.out.println(false && false); // false
System.out.println(true | true); // true
System.out.println(true | false); // true
System.out.println(false | true); // true
System.out.println(false | false); // false
System.out.println(true || true); // true
System.out.println(true || false); // true
System.out.println(false || true); // true
System.out.println(false || false); // false
System.out.println(true ^ true); // false
System.out.println(true ^ false); // true
System.out.println(false ^ true); // true
System.out.println(false ^ false); // false
System.out.println(!true); // false
System.out.println(!false); // true
여기서 궁금증이 하나 생긴다 & 와 && 혹은 | 와 || 의 차이점은 무엇일까? 결과는 동일하지만 차이점은 존재한다. && 와 || 는 첫번째 피연산자의 평가에 따라 두번째 피연산자를 평가없이 결과를 산출하고, & 와 | 는 피연산자 모두 평가해서 결과를 산출한다. && 와 || 를 사용하는게 효율적이다.
instanceof
참조타입의 변수가 참조타입의 클래스인지 확인하는데 사용한다. 형변환 여부까지 판단이 가능하다
자바스터디 진행하면서 아직 상속이 주제로 나오진 않았지만 상속 관련해서 자식 부모 관계에 사용된다
public class JavaStudy03 {
public static void main(String[] args) {
Member_a member_a = new Member_a();
Member_b member_b = new Member_b();
Member_c member_c = new Member_c();
System.out.println(member_a instanceof Member_a); // true
System.out.println(member_b instanceof Member_a); // true
System.out.println(member_c instanceof Member_a); // true
System.out.println(member_a instanceof Member_b); // false
System.out.println(member_b instanceof Member_b); // true
System.out.println(member_c instanceof Member_b); // true
System.out.println(member_a instanceof Member_c); // false
System.out.println(member_b instanceof Member_c); // false
System.out.println(member_c instanceof Member_c); // true
}
}
class Member_a {}
class Member_b extends Member_a {}
class Member_c extends Member_b {}
assignment(=) operator
대입(할당) 연산자로 단순 대입과 복합 대입으로 나눠진다.
단순 대입은 단순하게 왼쪽 피연산자인 변수에 오른쪽 피연산자의 값을 저장한다.
int i = 30; // 단순 대입 연산자
복합 대입은 정해진 연산을 수행 후 변수에 값을 저장한다
위에서 했던 행위들에 변수에 저장하는게 복합 대입이라 보면 된다.
int a = 10;
System.out.println("a += 10 -> " + (a += 10)); // a = a + 10 과 동일하다
System.out.println("a -= 10 -> " + (a -= 10)); // a = a - 10 과 동일하다
System.out.println("a *= 10 -> " + (a *= 10)); // a = a * 10 과 동일하다
System.out.println("a /= 10 -> " + (a /= 10)); // a = a / 10 과 동일하다
System.out.println("a %= 10 -> " + (a %= 10)); // a = a % 10 과 동일하다
System.out.println("a &= 10 -> " + (a &= 10)); // a = a & 10 과 동일하다
System.out.println("a |= 10 -> " + (a |= 10)); // a = a | 10 과 동일하다
System.out.println("a ^= 10 -> " + (a ^= 10)); // a = a ^ 10 과 동일하다
System.out.println("a <<= 10 -> " + (a <<= 10)); // a = a << 10 과 동일하다
System.out.println("a >>= 10 -> " + (a >>= 10)); // a = a >> 10 과 동일하다
System.out.println("a >>>= 10 -> " + (a >>>= 10)); // a = a >>> 10 과 동일하다
화살표 (->) 연산자
Java8 부터 등장한 표현으로 주로 람다식에 사용된다
추후에 스터디 주제로 다루겠지만 우선 맛보기로 알아보자
(a) -> { System.out.println(a); }
위의 식이 함수적 스타일의 람다식이다. 여기서 화살표가 사용된다.
-> 표현은 매개변수 a 를 이용해서 {} 를 실행하는 걸 표현한다.
자세한 내용은 람다식을 다룰 때 알아보자!
삼항 연산자
삼항 연산자는 단순하게 조건 연산으로 보면 되고, 사용 방법은 단순하다
(조건식) ? A : B
조건식 결과에 따라 true 이면 A 가 실행되고, false 이면 B 가 실행된다
int a = 10;
int b = 5;
String res = (a > b) ? "크다" : "작다";
System.out.println("a 는 b 보다 " + res); // a 는 b 보다 크다
단순한 조건일 때 삼항연산자를 쓰는 개발자도 있고 그냥 if문을 쓰는 개발자도 있고 개발자 성향에 따라 사용 빈도수가 달라진다
복잡한 조건과 로직일 경우엔 삼항연산자는 가독성이 떨어져서 안쓰는걸 추천한다.
연산자 우선 순위
연산자 종류에는 여러가지가 있다보니 연산 우선순위가 존재한다
연산자 | 연산방향 | 우선순위 |
증감(++, --), 부호(+, -), 비트(~), 논리(!) | <--- | 1 |
산술(*, /, %) | ---> | 2 |
산술(+, -) | ---> | 3 |
쉬프트(<<, >>, >>>) | ---> | 4 |
비교(<, >, <=, >=, instanceof) | ---> | 5 |
비교(==, !=) | ---> | 6 |
논리(&) | ---> | 7 |
논리(^) | ---> | 8 |
논리(|) | ---> | 9 |
논리(&&) | ---> | 10 |
논리(||) | ---> | 11 |
조건(?:) | ---> | 12 |
대입(=, +=, -=, /=, %=, ^=, |=, <<=, >>=, >>>=) | <--- | 13 |
우선순위를 가져도 다양하게 사용하다보면 보기가 힘들고 연산순서를 파악하기 힘들어진다
그럴 경우 () 를 사용하여 처리순서를 정리하는 것도 좋다
// 우선순위 파악하기 힘들다
100 + 5 * 1 / 5 * 100 > 90 ? 100 : 50
// () 로 파악하기 쉬워진다
100 + (5 * 1 / 5) * (100 > 90 ? 100 : 50)
(optional) Java 13. switch 연산자
다음주 주제로 다루게 될 제어문 중 하나인 switch 이다.
String a = "5";
int result;
switch (a) {
case "1" :
result = 100;
break;
case "2" :
result = 90;
break;
case "3" :
result = 80;
break;
case "4" :
result = 70;
break;
default:
result = 30;
break;
}
System.out.println(result);
위의 방식으로 사용하던 switch문이 Java13 이 되면서 다음과 같이 개선이 되었다
String a = "5";
int result = switch (a) {
case "1" :
yield 100;
case "2" :
yield 90;
case "3" :
yield 80;
case "4" :
yield 70;
default:
yield 30;
}
System.out.println(result);
추가된 yield 를 이용해서 원하는 값을 return 할 수 있다
제어문은 다음 주에 자세히 다룰 예정이라 이만 끝내겠다
'개발 & 방법론 > Java' 카테고리의 다른 글
5주차 : 클래스(Class) (0) | 2020.12.21 |
---|---|
4주차 : 제어문 (0) | 2020.12.12 |
2주차 과제: 자바 데이터 타입, 변수 그리고 배열 (0) | 2020.11.19 |
1주차 : JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가. (0) | 2020.11.17 |
자바 스터디 같이 할래? (0) | 2020.11.17 |