[자바 스터디] 3, 4주차 : 연산자, 제어문

2023. 1. 22. 00:16·스터디/자바

3주차 - 연산자

산술 연산자

  • 변수와 데이터에 대한 산술 연산을 수행하는데 사용됨
연산자 수행 연산
+ 덧셈
- 뺄셈
* 곱셈
/ 나눗셈 (몫만 취함)
% Modulo 연산 (나누기 연산 후 나머지만 취함)

Overflow 가능성

  • 연산 결과가 피연산자 타입의 저장 가능 범위를 벗어나게 되면 오버플로우가 발생, 의도치 않은 값이 변수에 저장됨
    int a = 2_100_000_000;
    int b = 2_000_000_000;

    System.out.println(a + b); // -194967296
  • 따라서 연산 시, 오버플로우 발생 가능성을 염두에 두어야 함

/와 %

  • /는 몫만 취하고, %는 나머지만 취함
    • 즉, 6 / 4 = 1, 6 % 4 = 2.
  • 나눗셈 연산 시

/의 반환 타입

  • a / b 에서 a와 b가 모두 정수이면 반환 값도 정수
  • a와 b 중 하나라도 floating-point number 이면 반환 값도 floating-point number임
  • 예제
    9 / 2 == 4
    9 / 2.0 == 4.5
    9.0 / 2 == 4.5
    9.0 / 2.0 == 4.5

분모가 0인 나눗셈

  • 정수 연산의 경우 ArthmenticException 발생
  • 실수 연산의 경우
    • / -> Infinity (무한대)
    • % -> NaN(Not a Number)
    • 각각 Double.isInfinite(), Double.isNan()으로 확인해주어야 함
  • 실수 계산 시 분모가 0임을 확인하는 방법 (실수 비교)
    • 일정 임계치를 범위로 두고 0에 가까운 값인지 확인 (참고자료)

비트 연산자

연산자 수행 연산
| Bitwise OR
& Bitwise AND
^ Bitwise exclusive OR (XOR)
~ Bitwise Complement
<< Left Shift
>> (Signed) Right Shift
>>> Unsigned Right Shift
  • 예시
    // Bitwise OR
    0 | 0 == 0
    0 | 1 == 1
    1 | 0 == 1
    1 | 1 == 1

    // Bitwise AND
    0 | 0 == 10
    0 | 1 == 0
    1 | 0 == 0
    1 | 1 == 1

    // Bitwise XOR
    0 | 0 == 1
    0 | 1 == 0
    1 | 0 == 0
    1 | 1 == 1

    // Bitwise Complement
    ~0 == 1
    ~1 == 0
    ~1001 == 0110

    // Left Shift
    // 빈 공간은 0으로 채워짐
    // 좌측으로 밀린 비트는 타입에 따라 공간이 없다면 버려짐
    2 << 2 == 8 // 0010(2) << 2 == 1000(8)

    // Signed Right Shift
    // 우측으로 비트를 미는데, 부호 비트 (+ == 0, - == 1)을 좌측 빈 공간에 채운다
    int number1 = 8;
    int number2 = -8;

    number1 >> 2 == 2 // 좌측 빈 비트가 0으로 채워잠
    number2 >> 2 == -2 // 좌측 빈 비트가 1로 채워짐

    // Unsigned Right Shift
    // 무조건 좌측 빈 비트가 0으로 채워짐

    int number1 = 8;
    int number2 = -8;

    number1 >>> 2 == 2
    number2 >>> 2 == 1073741822

관계(비교) 연산자

연산자 수행 연산
== Is Equal To
!= Not Equal To
> Greater Than
< Less Than
>= Greater Than or Equal To
<= Less Than of Equal To

논리 연산자

연산자 수행 연산
&& Logical AND
! Logical NOT (Bitwise Complement과 다름)

Short Circuit Logical Operators

  • 논리 연산 수행 시 두번째 logical expression을 수행하기 전에 결과가 확정되면, 두번째는 수행하지 않는 것을 말함

Logical AND

  • 첫번째 논리식이 true면 두번째 논리식까지 수행해봐야 결과를 알 수 있으므로 모두 수행한다.
  • 첫번째 논리식이 false면 무조건 전체 결과가 false이므로 두번째 논리식을 아예 수행하지 않는다

Logical OR

  • 첫번째 논리식이 false면 두번째 논리식까지 수행해봐야 결과를 알 수 있으므로 모두 수행한다.
  • 첫번째 논리식이 true면 무조건 전체 결과가 true이므로 두번째 논리식을 아예 수행하지 않는다

활용방안 / 주의점

  • 예를 들어, true인지 평가하고 싶은 대상이 null일 수 있을 때 첫번째 논리식에 대상 != null를 넣고 logical AND 연산을 수행하면 대상이 null이 아닐 경우에만 두번째 논리식을 수행하게 할 수 있음
  • 하지만, 두번째 논리식에 중요한 비지니스 로직이 포함된 경우 위와 같은 예시에 의해 로직이 아예 실행되지 않을 수 있으므로 주의해야 함

instanceof

  • 객체가 어떤 클래스인지, 어떤 클래스를 상속받았는지 확인하는데 사용

문법

  • object instanceOf type
  • object가 1.type 이거나 2.type을 상속받는 클래스라면 true를 리턴
    • type을 적을 땐 .class 없이 사용 가능

예시

    new ArrayList() instanceOf List // true
    new ArrayList() instanceOf Set // false

object가 null일 경우

  • 항상 false 리턴

제네릭에선?

  • 제네릭을 사용하는 객체 자체는 타입 체크 가능 (ArrayList, List 와 같이)
  • 다만, 제네릭(ex. T) 자체는 컴파일 시 타입이 결정되므로 아래와 같이 사용할 수 없음 (컴파일 에러 발생)
    public <T> boolean sample(List<T> list) {
        if (T instanceOf Integer) return true;
        return false;
    }

assignment(=, 대입) operator

연산자 수행 연산
= (오른쪽의 피연산자를) 왼쪽의 피연산자에 대입
+= 왼쪽의 피연산자에 오른쪽 피연산자를 더한 후, 그 결과값을 〃
-= 왼쪽의 피연산자에서 오른쪽의 피연산자를 뺀 후, 그 결과값을 〃
*= 왼쪽의 피연산자에 오른쪽 피연산자를 곱한 후, 그 결과값을 〃
/= 왼쪽의 피연산자를 오른쪽의 피연산자로 나눈 후, 그 결과값을 〃
%= 왼쪽의 피연산자를 오른쪽의 피연산자로 나눈 후, 그 나머지를 〃
&= 왼쪽의 피연산자를 오른쪽의 피연산자와 비트 AND 연산 후, 그 결과값을 〃
|= 왼쪽의 피연산자를 오른쪽의 피연산자와 비트 OR 연산 후, 그 결과값을 〃
^= 왼쪽의 피연산자를 오른쪽의 피연산자와 비트 XOR 연산 후, 그 결과값을 〃
<<= 왼쪽의 피연산자를 오른쪽의 피연산자만큼 왼쪽 시프트 한 후, 그 결과값을 〃
>>= 왼쪽의 피연산자를 오른쪽의 피연산자만큼 signed 오른쪽 시프트 한 후, 그 결과값을 〃
>>>= 왼쪽의 피연산자를 오른쪽의 피연산자만큼 unsigned 오른쪽 시프트 한 후, 그 결과값을 〃
  • -= 를 =-로 입력할 경우, 음수를 왼쪽 피연산자에 단순 대입 하는 연산자로 동작하니 주의하기

화살표(->) 연산자

  • 자바 8에서 람다 표현식이 등장하며 도입된 문법
    • 익명 함수를 만들기 위해 사용
    • FuntionalInterface의 메소드를 구현할 때 주로 사용 (Consumer, Supplier, …)
    • 15주차 - 람다식이 있어 여기서는 간단히 문법에 대해서만 다룸
    // 기본 형태
    (parameters) -> { statements; }

    // 파라미터는 0개 이상
    () -> { statements; }

    // 파라미터가 1개면 ( ) 생략 가능
    parameter -> { statements; }

    // statements가 하나면 { } 생략 가능, 이때 ; 도 생략 가능
    (parameters) -> statement

    // 활용 예시
    List<State> states = ... ;

    states.forEach(state -> state.doSomething());
    states.stream()
        .map(state -> state.mapToSomething())
        .collect(...);

3항 연산자

  • if-else 문을 한 번에 작성 가능
    int a = 10;
    int b = 20;

    // 기존 if-else 코드
    if (a < b) {
        return a;
    } else {
        return b;
    }

    // 위와 동일하게 동작하는 3항 연산자 코드
    return a < b ? a : b;
  • 대부분의 경우 3항 연산자를 쓰는 것보다 if-else 그대로 사용하는게 가독성 및 디버깅에 유리함
    • else도 쓰지 않는게..!
    • 컴파일러의 구현에 따라 번역되는 바이트코드는 달라질 수 있음

단항 연산자

  • 오직 하나의 피연산자만을 받아 값을 계산하는 연산자
연산자 수행 연산
+ 양수 표현 (쓰지 않아도 기본값)
- 음수 표현, 피연산자를 음수 취급
++ 피연산자의 값을 1 증가시킴
-- 피연산자의 값을 1 감소시킴
! 피연산자의 논리값을 반전시킴

Prefix, Postfix

  • 단항 연산자를 prefix로 사용하면, 단항 연산 수행 후 주어진 연산(메소드 호출, 대입 등)을 수행
  • 단항 연산자를 postfix로 사용하면, 주어진 연산(메소드 호출, 대입 등)을 먼저 수행하고 단항 연산을 수행
  • 예시
    int var = 5;

    method(++var); // method에 6 전달
    System.out.println(var); // 6 출력

    var = 5;
    method(var++); // method에 5 전달
    System.out.println(var); // 6 출력

연산자 우선 순위

  • 할당 연산을 제외한 이항 연산자는 연산자의 왼쪽 -> 오른쪽 순으로 평가
    • 할당 연산은 연산자의 오른쪽 -> 왼쪽 순으로 평가

우선 순위 표

  • 동일한 행의 연산자들은 동일한 우선순위를 가짐 (left -> right 규칙 적용)
연산자 우선 순위
expr++ expr-- 1
++expr --expr +expr -expr ~ ! 2
* / % 3
+ - 4
<< >> >>> 5
< > <= >= instanceof 6
== != 7
& 8
^ 9
| 10
&& 11
|| 12
?: 13
= += -= *= /= %= &= ^= |= <<= >>= >>>= 14

피드백

  • 중간 값을 구할 때 int + int 는 오버플로우를 일으킬 수 있음
    • 따라서, 작은 값 + (큰 값 - 작은 값) / 2 로 구하거나,
    • (작은 값 + 큰 값) >>> 1 (비트 연산) 로도 구할 수 있음 (양수만 가능)
  • XOR 연산
    • 5(101) ^ 0(000) == 5(101), 5(101) ^ 5(101) == 0(000)
    • XOR 연산은 순서에 관계가 없으므로 오직 1번만 등장하는 숫자 찾기 등에 활용 가능하다

4주차 - 제어문

선택문 (조건문)

if-else

  • if 조건이 참인 경우에 if문 블럭 내의 내용을 수행
    if (true) {
        // 본문 내용 수행
        return value;
    }
  • if 조건이 거짓인 경우 else문 블럭 내의 내용을 수행
    • else if 문이 있을 경우 먼저 else if 조건이 참인지 평가하고 참이면 else if 블럭 내용을 수행함
    if (false) {
        // 실행 X
        return a;
    } else if (true) {
        // 실행 O
        return b;
    } else {
        // 실행 X
        return c;
    }

    if (false) {
        // 실행 X
        return a;
    } else if (false) {
        // 실행 X
        return b;
    } else {
        // 실행 O
        return c;
    }
  • if 문 블럭 내부에 또 다시 if문을 넣을 수도 있음 (중첩 if문)
    if (true) {
        if (false) {
            // 실행 X
        } else {
            // 실행
            return value;
        }
    }

switch / case

  • 조건으로 주어진 하나의 값이 어떤 값인지에 따라 많은 양의 다른 로직을 수행하고자 할 때 유용
    • 다중 if문에 비해서는 가독성이 높음
    • 각 case마다 로직 종료 후 break를 걸지 않으면 계속해서 다음 case의 로직을 수행하기 때문에 조심해야 함
    int a = getValue();
    switch(a) {
        case 0:
            doSomeThingA();
            break;
        case 1:
            doSomeThingB();
            break;
        case 2:
            doSomeThingC();
            break;
        default:
            doSomethingDefault();
        }

반복문

for문 (기본)

  • for(초기화 ; 조건문 ; 증감식) {} 의 구조를 사용함
    • 조건문이 참인 동안 내부 로직 수행 -> "증감식" 수행 을 반복함
    • 최초 동작 시 (조건문이 참일 경우) 초기화 -> 조건문 확인 -> 내부 로직 수행 -> 증감식 수행 -> 조건문 확인 -> … 순으로 동작하므로 최초 선언된 조건문이 참이면 최소 1회는 본문 내용이 수행됨
    int iterationCount = getIterationCount();
    for (int i = 0 ; i < iterationCount ; i++) {
        doSomething(i);
    }

while문

  • while (조건) {} 의 구조를 사용
    • 조건이 참일 동안 본문 로직을 반복 수행
  • 최초 조건이 거짓이면 1회도 본문 로직이 수행되지 않을 수 있음
    • 반면, 조건이 변화하지 않으면 무한 루프에 빠질 수 있으므로 주의 (루프 내에서 통해 조건이 변화해야 함)
    int i = 0;
    while (i <= 1) {
        // i == 0, i == 1인 경우 총 2회 수행
        i++;
    }

do-while문

  • while과 달리 최소 1회는 본문 로직이 수행됨
    • 즉, 먼저 구문을 실행한 후 마지막에 조건을 확인
    int i = 0;
    do {
        // i == 0, i == 1인 경우 총 2회 수행
        System.out.println(i); // 0, 1
        i++;
    } while (i <= 1); // 조건이 참이면 다시 위로 올라가 본문을 수행

for-each문 (향상된 for문)

  • 컬렉션의 원소를 하나씩 가져와 for문처럼 반복을 수행할 수 있음
    List<String> names = getNames();
    for (String name : names) {
        // names의 원소를 하나씩 꺼내서 해당 원소를 name에 대입하여 아래 로직을 수행
        doSomething(name);
    }

컬렉션에서의 forEach(Consumer)

  • 컬렉션에서 제공하는 메소드 forEach()에 인자로 익명 함수 (함수형 인터페이스 Consumer의 메소드를 구현) 전달하면 간단히 컬렉션 원소에 특정 로직을 반복시킬 수 있음
    List<String> names = getNames();
    names.forEach(name -> name.doSomething()); // (name::doSomething) 으로도 가능

break과 continue

  • break
    • 자신이 속한 가장 안쪽의 반복문을 중단
  • continue
    • 자신이 속한 가장 안쪽의 반복문에서 다음 반복으로 넘어감

Collection의 Iterator()

  • Iterator를 사용하는 모든 콜렉션은 iterator() 메소드를 구현
    • 이 메서드는 Iterator 인터페이스를 리턴

Iterator 인터페이스

  • Iterator는 hasNext(), next() 메소드를 필수로 갖고, remove() 메소드를 선택적으로 지원
    public interface Iterator<E> {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    ...
  }

hasNext(), next()

  • hasNext() - 다음번 엘리멘트가 있다면 true를 반환, 없다면 false를 반환
  • next() - 다음번 엘리멘트를 반환
    • 최초 호출 시, 첫번째 엘리멘트를 반환함

for문에서의 활용

    list = 아무아무 컬렉션;
    for (Iterator<Integer> itr = list.iterator() ; itr.hasNext() ; ) {
        // itr.next() 수행으로 인해 itr.hasNext()의 값이 바뀌게 되므로 for문의 증감식은 필요하지 않음
        doSomething(itr.next());
    }

⭐️ remove()

  • 오라클 자바 공식 문서에서는 아래와 같이 Iterator.remove가 반복 도중 원소를 삭제하는 유일하게 안전한 방법이라고 소개하고 있음

Note that Iterator.remove is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress.

  • 향상된 for문에서 remove를 시도할 경우
    • ConcurrentModificationException 발생
    List<String> names = getNames();
    for (String name : names) {
        if (name.equals("현상혁")) {
            names.remove(name);  // ConcurrentModificationException 💥
        }
    }
  • 기존 인덱스 기반으로 remove를 시도할 경우
    • 이 경우 삭제는 되지만, 삭제 시마다 인덱스가 조정되기 때문에 따로 인덱스를 보정해주지 않는다면 의도한대로 모든 원소를 순회할 수 없음
    • 아래 예시에서는 3을 삭제하면서 인덱스가 4를 스킵하고 바로 5를 가리키게 되므로 삭제가 정상적으로 수행되지 못함
    List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
    for (int i = 0; i < numbers.size(); i++) {
        Integer number = numbers.get(i);
            if (number.equals(3) || number.equals(4)) {
                numbers.remove(number);
            }
        }

        System.out.println(numbers); // [1, 2, 4, 5]
    }
  • Iterator.remove() 사용 시
    • 내부적으로 콜렉션이 아닌 복제본을 순회 (추가 메모리 사용) 하기 때문에 가능한 일
        List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
        for (Iterator<Integer> itr = numbers.iterator(); itr.hasNext();) {
            Integer number = itr.next();
            if (number.equals(3) || number.equals(4)) {
                itr.remove(); // 컬렉션이 아닌 itr에게 remove()를 해야 함
            }
        }

        System.out.println(numbers); // [1, 2, 5]
  • Iterator.remove()의 더 간편한 사용 방법
    • 컬렉션.removeIf(Predicate)으로 순회를 직접 작성하지 않아도 원하는 원소를 삭제할 수 있다
    List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
    numbers.removeIf(number -> number.equals(3) || number.equals(4));

    System.out.println(numbers); // [1, 2, 5]

JUnit 필수 개념 정리

JUnit5 소개

  • 자바 개발자가 가장 많이 사용하는 테스팅 프레임워크
    • 자바 8 이상을 필요로 함
  • 구성
    • JUnit Platform - 테스트를 실행해주는 런쳐 제공. TestEngine API 제공.
    • Jupiter - TestEngine API 구현체, Junit 5 제공
    • Vintage - TestEngine API 구현체, Junit 3, 4 제공

의존성 추가

  • 2.2+ 버전의 스프링 부트 프로젝트 생성 시 자동으로 JUnit 5 의존성이 같이 받아짐
  • 스프링 부트 미사용 시
    <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>원하는_버전_작성</version>
    <scope>test</scope>
    </dependency>

테스트 이름 표시

  • @DisplayNameGeneration()
    • 클래스 어노테이션으로 작성하면 미리 구현된 혹은 직접 구현한 테스트 이름 표시 규칙을 적용해줌
    • 기본 구현체 중 ReplaceUnderscores 제공 -> 테스트 메소드의 _를 공백으로 바꿔줌
    @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
    class FramesTest {

    public static final int GAME_FINISHING_BOWL_TRIES = 12;

    @Test
    void 게임종료여부_종료되지않은_상태() {
        assertThat(framesProvider(1).gameFinished()).isFalse();
    }

  • @DisplayName()
    • @DisplayNameGeneration()보다 우선순위가 높음
    • 어떤 테스트 인지 메소드명 말고 별도로 테스트 이름을 작성할 수 있는 기능

Assertj 라이브러리를 사용한 대표적인 Assertions

  • Assertj
    • JUnit과 호환되는 3rd-party 라이브러리
    • 더욱 편리하고 가독성 좋게 테스트 코드를 작성할 수 있음
    • JUnit 팀에서도 정식 추천하는 라이브러리
    • 메서드 체이닝을 사용해 편하게 검증을 수행할 수 있음
  • 대표적인 Assertions
assertj 코드 검증 내용
isEqualTo(Object o) 실제 값이 주어진 값과 같은지 확인 ( equals() )
isSameAs(Object o) 실제 값이 주어진 값과 같은지 확인 ( == )
isInstanceOf(Class type) 실제 값이 주어진 유형의 인스턴스인지 확인
isExactlyInstanceOf(Class type) 실제 값이 정확히 주어진 유형의 인스턴스인지 확인
isTrue() / isFalse() 주어진 값이 참 / 거짓인지 확인
contains(Object o) 컬렉션이 주어진 값을 포함하는지 확인
containsOnly(Object o) 컬렉션이 주어진 값만 포함하는지 확인
containsExactly(Object o) 컬렉션이 정확히 순서까지 고려하여 주어진 값만 포함하는지 확인
startsWith(Object o) 컬렉션이 주어진 값으로 시작하는지 확인
doesNotContainNull() null을 포함하지 않는지 확인

테스트 반복

  • RepeatedTest
    • 반복 횟수 / 반복 테스트 이름을 설정할 수 있음

ParameterizedTest

  • 여러 다른 매개변수를 대입해가며 테스트를 반복 실행 가능
  • 인자 값을 받는 방법들
    • @ValueSource
    • @NullSoure, @EmptySource, @NullAndEmptySource
    • @MethodSource
    • @CsvSource
    • …

과제 0

// TODO 과제 작성

피드백

  • Queue의 동작
    • 각각 삽입, 삭제, 확인 기능
    • offer(), poll(), peek() 은 수행이 불가해도 예외가 터지지 않고, false나 null을 반환
    • add(), remove(), element()는 수행 불가 시 예외가 터짐
    • 메소드 구현 시 일관성에 대해 고민하기

참고문헌

  • https://www.programiz.com/java-programming/operators
  • https://codechacha.com/ko/java-instance-of/
  • http://www.tcpschool.com/java/java_operator_assignment
  • https://www.delftstack.com/ko/howto/java/java-arrow-operator/
  • https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html
  • https://www.notion.so/Live-Study-4-ca77be1de7674a73b473bf92abc4226a
  • https://imasoftwareengineer.tistory.com/84
  • https://docs.oracle.com/javase/tutorial/collections/interfaces/collection.html
  • https://www.daleseo.com/how-to-remove-from-list-in-java/
  • 백기선님 인프런 강의 - 더 자바, "코드를 테스트 하는 다양한 방법"

'스터디 > 자바' 카테고리의 다른 글

[자바 스터디] 7주차 : 패키지  (0) 2023.01.30
[자바 스터디] 6주차 : 상속  (0) 2023.01.23
[자바 스터디] 5주차 : 클래스  (0) 2023.01.23
[자바 스터디] 2주차 : 자바 데이터 타입, 변수 그리고 배열  (0) 2023.01.22
[자바 스터디] 1주차 : JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가.  (0) 2023.01.21
'스터디/자바' 카테고리의 다른 글
  • [자바 스터디] 6주차 : 상속
  • [자바 스터디] 5주차 : 클래스
  • [자바 스터디] 2주차 : 자바 데이터 타입, 변수 그리고 배열
  • [자바 스터디] 1주차 : JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가.
gmelon
gmelon
백엔드 개발을 공부하고 있습니다.
  • gmelon
    gmelon's greenhouse
    gmelon
  • 전체
    오늘
    어제
    • 분류 전체보기 (91)
      • 개발 공부 (28)
        • Java (6)
        • Spring (10)
        • 알고리즘 (11)
        • 기타 (1)
      • 프로젝트 (12)
        • [앱] 플랭고 (4)
        • 졸업 프로젝트 (8)
      • 스터디 (0)
        • 자바 (30)
      • 기록 (15)
        • 후기, 회고 (9)
        • SSAFYcial (5)
        • 이것저것 (1)
      • etc. (6)
        • 모각코 (6)
  • 블로그 메뉴

    • 홈
    • 방명록
    • github
    • 스크랩
  • 인기 글

  • 태그

    싸피 회고
    AWS 프리티어 종료
    java
    Java Collector
    졸업프로젝트
    2024 회고
    groupingBy()
    태초마을이야
    자바 Collector
    2024 상반기 회고
    groupingBy mapping
    Collector groupingBy()
    한글프로그래밍언어
    자바
    프리티어 종료
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
gmelon
[자바 스터디] 3, 4주차 : 연산자, 제어문
상단으로

티스토리툴바