산술 연산자는 사칙연산을 다루는 연산자로, 가장 기본적이면서 가장 많이 사용되는 연산자 중의 하나.
산술 연산자는 모두 두 개의 피연산자를 가지는 이항 연산자이며, 결합 방향은 왼쪽에서 오른쪽이다.
연산자 | 설명 |
---|---|
+ | 더하기 연산 수행(문자열 연결 가능) |
- | 마이너스 연산 수행 |
* | 곱하기 연산 수행 |
/ | 나누기 연산 수행(정수형은 몫 연산자) |
% | 나머지 연산 수행 |
int result = 1 + 2;
System.out.println(result);
String ss = "연습";
System.out.println(ss + " 입니다.");
int product = 5 * 4;
System.out.println(product);
System.out.println("정수형 나누기");
System.out.println(5 / 4);
System.out.println(5 % 4);
System.out.println("실수형 나누기");
System.out.println(5.0 / 4.0);
System.out.println(5.0 % 4.0);
System.out.println(5 / 0); // java.lang.ArithmeticException: / by zero
System.out.println(5.0 / 0.0); // Infinity
System.out.println(5.0 % 0.0); // NaN
System.out.println(-5.0 / 0.0); // -Infinity
System.out.println(-5.0 % 0.0); // NaN
정수형일때 조심해야 하는 점은 보는 것과 같이 나눗셈 연산을 수행할 때이다.
그냥 실행이 잘 되기 때문에 % 연산을 수행할 때에도 음수값으로 나눌때 값이 다르다.
실수형일때는 연산 과정에서는 오차가 발생할 수 있고 이것을 조심해야 한다.
0으로 나누거나 나머지 연산을 사용하면 Infinity
, NaN
가 나온다.
+
를 붙이면 양수를 나타냄. (생략 가능)-
를 붙이면 음수 값을 나타낸다.++
를 붙이면 값을 1씩 증가시킨다. 하지만 피연산자의 위치에 따라 계산이 살짝 달라진다.
앞쪽에 ++변수
의경우 식을 진행하기 전에 1을 증가시키고, 뒤쪽변수++
에 위치하는 경우 식을 진행한 후에 1을 증가시킨다.
--
도 이와 같다.
int result = 1;
for (int i = 0; i < 5; i++) {
System.out.print(result++ + " ");
}
System.out.println();
int result2 = 1;
for (int i = 0; i < 5; i++) {
System.out.print(++result2 + " ");
}
두 증감 연산자에 대한 차이이다.
논리 연산자와 비슷하지만, 비트 단위로 논리 연산을 할 때 사용하는 연산자이다.
비트 단위로 왼쪽이나 오른쪽으로 전체 비트를 이동하거나 1의 보수를 만들 때도 사용된다.
연산자 | 설명 |
---|---|
& | 대응되는 비트가 모두 1이면 1을 반환함.(AND 연산) |
ㅣ | 대응되는 비트중 하나라도 1이면 1을 반환함.(OR 연산) |
^ | 대응되는 비트가 서로 다르면 1을 반환함.(XOR 연산) |
~ | 비트를 1이면 0으로, 0이면 1로 바꿈(NOT 연산, 1의 보수) |
<< | 명시된 수 만큼 비트를 전부 왼쪽으로 이동(left shift 연산) |
>> | 부호를 유지하면서 지정한 수 만큼 전부 오른쪽으로 이동(right shift 연산) |
>>> | 지정한 수만큼 비트를 전부 오른쪽으로 이동시키며, 새로운 비트는 전부 0이 됨. |
관계 연산자는 피연산자가 값이 어떤지 비교하는 연산자들이다.
==
: 같다.!=
: 다르다.>
: 크다>=
: 크거나 같다.<
: 작다.<=
: 작거나 같다.문자열이 같은지를 보려면 equals()
를 사용해야 한다.
==은 주소값을 비교하는 것이기 때문에 String 은 equals를 사용하자.
피연산자 값으로 boolean을 받고 true/false를 판단할 경우에 사용한다.
&&
||
!
객체 타입을 확인하는데 주로 사용하고, 나같은 경우에는 테스트 코드를 작성했을 때, 많이 사용한 메서드이다. 속성 자체는 연산자이고, 형 변환이 가능한지 여부를 true/false 로 가르쳐준다. 부모, 자식 객체인지 확인하는데 쓴다고 생각하면 된다.
class A {
}
class B extends A {
}
public class InstanceTest {
public static void main(String[] args) {
A a = new A();
B b = new B();
System.out.println(a instanceof A);
System.out.println(b instanceof A);
System.out.println(a instanceof B);
System.out.println(b instanceof B);
}
}
결과는 true, true, false, true 가 나온다.
b는 부모 클래스인 a를 상속받았기 때문에 A의 객체타입이 맞다.
그래서 true이고 A클래스는 반대로 B에 대한 부모 클래스이기 때문에 B의 객체 타입일 수는 없으므로 false를 출력하게 된다.
할당 연산자는 연산자 기준 오른쪽값을 왼쪽의 피연산자에게 할당한다.
int x = 0;
에서 x라는 int형 변수에 0을 할당하는 것이다.
A a = new A();
처럼 객체를 할당할 수도 있다.
public class Test {
public static void main(String[] args) {
int a = 0;
a = a + 1;
a += 1;
}
}
a = a + 1
의 경우
a += 1
의 경우
서로 바이트 코드가 다르다!!!
아래의 경우가 속도가 조금 빠르지 않을까 생각한다.
화살표 연산자는 Java8에서 람다 표현식으로 익명클래스를 대체한 것이다.
public interface Test {
void test();
}
public class Main {
public static void main(String[] args){
Test test = new Test() {
@Override
public void test() {
//로직 구현
}
};
}
}
이런식으로 익명 클래스를 사용하여 구현을 해줬었다.
근데 이 화살표 연산자가 생긴뒤로는 이렇게 구현하지 않고 아래와 같이 구현한다.
@FunctionalInterface
public interface Test {
void test();
}
Test test = () -> 로직;
이렇게 구현을 해준다.
@FunctionalInterface
를 인터페이스에 붙여주게 되면 interface에는 하나의 추상메서드만 정의가 가능하다.
람다를 다시 공부할때 포스팅 하도록 하겠다.
삼항 연산자는 조건 ? 참일 경우 : 거짓일 경우
로 구현하는데 피연산자를 세개를 받으므로 삼항 연산자라고 불린다.
if-else
문과 비슷한 역할을 수행하지만 한줄로 작성이 가능하다.
public class Ternary {
public static void main(String[] args) {
int n1 = 5;
int n2 = 10;
int max = (n1 > n2) ? n1 : n2;
System.out.println("max : " + max);
}
}
기본적으로 연산자에는 우선순위가 있으며, 괄호의 우선순위가 제일 높고,
산술 > 비교 > 논리 > 대입의 순서이며, 단항 > 이항 > 삼항의 순서다.
연산자의 연산 진행방향은 왼쪽에서 오른쪽으로 수행되며,
단항 연산자와 대입 연산자의 경우에는 오른쪽에서 왼쪽으로 수행된다.
어떠한 값이 맞는다면 해당하는 식을 수행하게끔 만든 연산자이다.
단일 값으로 평가되는 하나의 표현식.
java 15 버전에서는 case를 case ->
로 표현한다고 한다.
public class Main {
public static void main(String[] args) {
String day = "월";
switch (day) {
case "월":
System.out.println("월요일");
break;
case "화":
System.out.println("화요일");
break;
case "수":
System.out.println("수요일");
break;
case "목":
System.out.println("목요일");
break;
case "금":
System.out.println("금요일");
break;
}
}
}
이것을
switch (day) {
case "월" -> System.out.println("월요일");
case "화" -> System.out.println("화요일");
case "수" -> System.out.println("수요일");
case "목" -> System.out.println("목요일");
case "금" -> System.out.println("금요일");
case "토", "일" -> System.out.println("주말");
}
이렇게 표현이 가능하다.
if-else
문은 원하는 조건이 나올때까지 순서대로 모든 경우를 비교하고
switch
문은 jump-table
을 사용해서 한번에 원하는 곳에 이동한다.
그래서 if문은 조건문의 개수만큼 O(n)
의 시간복잡도를 갖게 되어 성능에 단점이 있고,
switch문은 case의 개수만큼 jump-table
을 차지하므로 메모리에 단점이 있다.
때문에 성능면으로 보면 switch문이 더 빨라서
조건이 3개 이상일 경우에는 switch를 사용하는 것이 더 좋다고 한다.
그렇지만 사실 그 차이는 컴파일러의 처리 속도에 따라 차이가 생기는 것이고
요즘 컴파일러들이 워낙 우수하기 때문에 차이가 미비하다고 하다.
if-else 를 쓰든 switch 를 쓰든 (특별히 성능과 메모리의 이슈를 갖고 있지 않은 이상) 각자의 취향이 아닐까.
가독성
을 높이는 쪽으로 선택해서 사용하면 될 것 같고
나는 되도록이면 Early Return
을 하려고 한다.
String string;
if (조건) {
string = "참";
} else {
string = "거짓";
}
이런게 있다고 하면 조건에 부합하는 문자열만 보내주면 된다.
그래서
String string = getString();
public String getString() {
if (조건) {
return "참";
}
return "거짓";
}
이렇게 분리하는 방법이 좋은것 같다.
이걸 더 분리한다고 치면 이제 공부했던 State패턴을 사용하면 되겠다.
이거는 다시 정리하는것이기 때문에 나중에 객체지향 개념을 들어가게 되면 구체적으로 정리를 해보도록 하겠다.