본문 바로가기
네이버클라우드/JAVA 웹 프로그래밍

JAVA 9일차 (2023-06-02) 자바 기초 DAY7

by prometedor 2023. 6. 2.

switch 문

switch (값) {} 값으로 가능한 값

# 흐름 제어문 - switch 문법

public class Exam0230 {
  public static void main(String[] args) {
    // switch (값) {}
    // 값으로 가능한 데이터 타입은?
    // => int 정수(byte,short,int,char), 문자열, 특별한 상수 Enum 타입
    // => case 값으로 변수를 사용할 수 없음 -> 리터럴만 가능
    byte b = 2;
    switch (b) {
      case 1:
      case 2:
      default:
    }

    short s = 2;
    switch (s) {
      case 1:
      case 2:
      default:
    }

    int i = 2;
    switch (i) {
      case 1:
      case 2:
      default:
    }

    char c = 'A'; // A문자의 유니코드 값(UTF-16) 0x41(65)을 c에 저장
    switch (c) {
      // case 의 값도 int 값이면 무엇이든 됨
      case 'A': // 0x41 = 65
      case 66:
      case 0x43:
      default:
    }

    // String 값을 switch와 case의 값으로 사용할 수 있음
    String str = "hello";
    switch (str) {
      // case 의 값으로 String 가능
      case "hello":
      case "ohora":
      case "hul":
      default:
    }

  }
}

 

public class Exam0231 {
  public static void main(String[] args) {
    // switch (값) {} 값으로 가능한 데이터 타입
    // => int 정수(byte,short,int,char), 문자열, 특별한 상수 Enum 타입
    // => case 값으로 변수를 사용할 수 없음 -> 리터럴만 가능

    // case에는 리터럴만 올 수 있음
    // 즉 변수를 사용할 수 없음
    int x = 1, y = 300;
    switch (x) {
      case 1 * 300: // OK
        //      case 1 * y: // 컴파일 오류!
    }
  }
}

// - 4바이트를 넘어가는 정수는 사용할 수 없음
// - 부동소수점은 사용할 수 없음
// - boolean 값을 switch와 case에 사용할 수 없음

 

final 을 이용하여 코드 깔끔하게 만들기

# 흐름 제어문 - switch 문법 II

public class Exam0241 {

  public static void main(String[] args) {
    int level = 1;

    // 상수를 사용하면 주석없이 바로 이해할 수 있음
    // => case 문자의 값으로 변수를 사용할 수 없음
    // => 단 값이 변경되지 않는 final 변수라면 사용할 수 있음

    final int GUEST = 0, MEMBER = 1, ADMIN = 2;

    switch (level) {
      case GUEST:
        System.out.println("조회만 가능합니다.");
        break;
      case MEMBER:
        System.out.println("글작성 가능합니다.");
        break;
      case ADMIN:
        System.out.println("다른 회원의 글을 변경, 삭제할 수 있습니다.");
        break;
    }
  }
}

ㄴ final 을 이용하여 int 상수 만들기

 

enum 타입 

# 흐름 제어문 - switch 문법 II

public class Exam0242 {

  // 상수를 좀 더 조직적으로 관리하는 방법
  // => enum을 사용하여 상수를 정의함
  // => nested enum은 기본이 static 이므로 static을 생략해도 됨

  enum Level {
    GUEST, MEMBER, ADMIN
  }

  public static void main(String[] args) {
    // enum으로 정의된 상수를 사용하려면 enum 타입의 변수를 선언해야 함
    // => final int처럼 직접 값을 지정하지 않아도 됨
    // => 값을 직접 지정할 수도 있음
    
    // enum을 사용하는 주된 이유
    // => 100, 200, "admin" 과 같이 값을 직접 지정할 필요가 없음
    // => enum 변수에는 그 타입에 정의된 값만 저장할 수 있음
    // => 안전한 코드를 작성할 수 있음

    Level level = Level.MEMBER;

    // 다음과 같이 switch나 case 값으로 enum 타입의 값이 올 수 있음
    switch (level) {
      case GUEST:
        System.out.println("조회만 가능합니다.");
        break;
      case MEMBER:
        System.out.println("글작성 가능합니다.");
        break;
      case ADMIN:
        System.out.println("다른 회원의 글을 변경, 삭제할 수 있습니다.");
        break;
    }
  }
}

=> case 안에는 Level.level 이라고 해줄 필요 없음

ㄴ enum 은 특별한 상수타입으로 정의

 

while 에서의 break continue

# 흐름 제어문 - break와 continue 활용

public class Exam0321 {
  public static void main(String[] args) {
    int count = 0;
    int sum = 0;


    // 1부터 100까지의 짝수의 합은?
    // => continue 사용 전
    count = 0;
    sum = 0;
    while (count < 100) {
      count++;
      if ((count & 1) == 0) { // count & 1 ==> count & 0x01 ==> count % 2
        sum += count;
      }
    }
    System.out.printf("count=%d, sum=%d\n", count, sum);

    System.out.println("------------------------");

    // => continue 사용 후
    count = 0;
    sum = 0;
    while (count < 100) {
      count++;
      if (count % 2 == 1)
        continue; // 다음 문장을 실행하지 않고 즉시 조건 검사로 이동
      sum += count;
    }
    System.out.printf("count=%d, sum=%d\n", count, sum);
  }
}

 

ㄴ while 문 안에서 전위연산자나 후위연산자나 기능은 같지만 후위연산자를 많이 씀

ㄴ 짝수 여부 확인할 때 count & 1 사용하는 게 더 좋음

ㄴ continue // 다음 문장을 실행하지 않고 즉시 조건 검사(조건문)로 이동함

 

 

중첩 반복문 break

# 흐름 제어문 - 중첩된 반복문 탈출

public class Exam0330 {
  public static void main(String[] args) {
    int x = 2, y = 1;

    // 5 * 5 까지만 출력하라

    while (x <= 9) {

      while (y <= 9) {
        System.out.printf("%d * %d = %d\n", x, y, x * y);
        if (x == 5 && y == 5)
          break; // 이 break는 자신이 소속된 가장 가까운 반복문을 나감
        y++;
      }

      System.out.println();
      x++;
      y = 1;
    }
    System.out.println("종료!!");
  }
}

 

ㄴ break; => 가장 가까운 반복문을 나감

 

 

중첩 반복문 break - label 붙이기

# 흐름 제어문 - 중첩된 반복문 탈출

public class Exam0331 {
  public static void main(String[] args) {
    int x = 2, y = 1;

    // 라벨명: 반복문1 { 반복문2 {break 라벨명;}}
    // 라벨 문법:
    //      라벨: 문장;
    //      라벨: {문장1, 문장2, ...}

    myloop:  
      while (x <= 9) {

        while (y <= 9) {
          System.out.printf("%d * %d = %d\n", x, y, x * y);
          if (x == 5 && y == 5)
            break myloop; // myloop 라벨에 소속된 문장을 나감
          y++;
        }

        System.out.println();
        x++;
        y = 1;
      }
    System.out.println("종료!!");

    System.out.println("-----------------------------");
  }
}

ㄴ 라벨 붙이기

ㄴ 라벨 묶기 => 중괄호로 묶기 가능 

ㄴ break 라벨명;

 

 

for 반복문

# 흐름 제어문 - for 반복문

public class Exam0410 {
  public static void main(String[] args) {
    // for (변수선언 및 초기화; 조건; 증감문) 문장;
    // for (변수선언 및 초기화; 조건; 증감문) {문장1; 문장2; ...}

    // for 문의 전형적인 예
    for (int i = 1; i <= 5; i++)
      System.out.println(i);
    // 실행 순서
    // 1) 변수초기화  => int i = 1
    // 2) 조건 => i <= 5
    // 3) 문장 => System.out.print(i + " ")
    // 4) 변수증가문 => i++
    // 조건이 참인 동안 2 ~ 4를 반복

    // for 문에서 선언한 변수는 그 for 문 안에서만 사용할 수 있음
    //    System.out.println(i); // 컴파일 오류!
  }
}

ㄴ 조건 거짓이면 for 문 나감

 

 

for( ; ; ) => 무한루프

# 흐름 제어문 - for 반복문

public class Exam0413 {
  public static void main(String[] args) {
    // for (변수선언 및 초기화; 조건; 증감문) 문장;
    // for (변수선언 및 초기화; 조건; 증감문) {문장1; 문장2; ...}

    // 조건문 제거
    int i = 1;
    for (  ;  ;  ) {
      if (i > 5)
        break;
      System.out.println(i);
      i++;
    }

  }
}

 

for 중첩과 continue

# 흐름 제어문 - for 중첩과 continue

public class Exam0433 {
  public static void main(String[] args) {
    // continue
    for (int i = 1; i <= 10; i++) {
      for (int j = 1; j <= i; j++) {
        if (j % 2 == 0)
          continue; // 다음 줄로 가지 않고 '변수증가문'으로 이동
        System.out.print(j + " ");
      }
      System.out.println();
    }
  }
}

continue 만나면 증감으로 이동

 

 

for( ; ; ) 와 배열

package com.eomcs.lang.ex06;

// # 흐름 제어문 - for(;;) 와 배열
//
public class Exam0440 {
  public static void main(String[] args) {

    //    String[] names = new String[5]; 
    //    names[0] = "홍길동"; 
    //    names[1] = "임꺽정"; 
    //    names[2] = "유관순";
    //    names[3] = "윤봉길"; 
    //    names[4] = "안중근";

    //    String[] names;
    //    names = new String[] {"홍길동", "임꺽정", "유관순", "윤봉길", "안중근"};

    // 배열 변수 선언과 동시에 배열 초기화를 실행할 때는  new String[] 을 생략할 수 있다. 
    String[] names = {"홍길동", "임꺽정", "유관순", "윤봉길", "안중근"};

    for (int i = 0; i < names.length; i++)
      System.out.println(names[i]);

  }
}

for(:) 와 배열

package com.eomcs.lang.ex06;

// # 흐름 제어문 - for(:) 와 배열
//
public class Exam0450 {
  public static void main(String[] args) {
    String[] names = {"홍길동", "임꺽정", "유관순", "윤봉길", "안중근"};

    // 배열의 처음부터 끝까지 값을 꺼내는 것이라면
    // 다음의 for 문법을 사용하라! 아주 편하다!
    // for (배열에서 꺼낸 값을 저장할 변수 선언 : 배열주소) 문장;
    for (String name : names)
      System.out.println(name);

    // 위의 문장은 컴파일하면 아래의 문장으로 변경된다.
    // for (int i = 0; i < names.length; i++) {
    //   String name = names[i];
    //   System.out.println(name);
    // }
  }
}

//
// # for (:)
// - 배열 전체를 반복하거나 컬렉션 객체(java.util.Iterable 구현체) 전체를 반복할 때 유용한다.
// - 배열의 일부만 반복할 수 없다.
// - 배열의 값을 다룰 때 인덱스를 사용할 필요가 없어 편리하다.
//
// 문법:
// for (변수 선언 : 배열, Iterable 구현체) 문장1;
// for (변수 선언 : 배열, Iterable 구현체) { 문장1; 문장2; ...}
// - 변수의 타입은 배열이나 Iterable 구현체의 항목 타입과 같아야 한다.
// - 반복문을 돌 때 마다 항목을 값을 꺼내 변수에 담는다.

 

String.format 

# 메서드 : 개념 및 기본 문법 IV

public class Exam0240 {

  // 4) 메서드 : 리턴값(O), 파라미터(O)
  // => "이 돈 갖고 과자좀 사와!"
  static String hello(String name, int age) {
    String retVal = String.format("%d살 %s님을 환영합니다!", age, name);
    return retVal;
  }

  public static void main(String[] args) {

    // hello() 메서드를 실행하고, 그 리턴 값을 변수에 담음
    String r = hello("홍길동", 20);
    System.out.println(r);

    // 앞의 예제와 마찬가지로 리턴 값을 한 번만 사용한다면, 사용할 곳에 메서드 호출 코드를 둬라
    // => 리팩토링 기법 중에서 "replace temp with query" 라 부름
    System.out.println(hello("홍길동", 20));

    // 리턴 값을 안 받아도 됨
    hello("임꺽정", 30); // 리턴 값은 버려진다.
  }
}

=> printf 와 String.format 의 차이

ㄴ printf : 문자열을 만들어서 출력

ㄴ String.format : 문자열을 만드느 것 까지만 함

 

 

리팩토링 

import java.util.Scanner;

# 메서드 : 사용 후

public class Exam0120 {

  // 스페이스를 출력하는 코드들을 관리하기 쉽도록 별도의 블록에 모아 놓음
  // 그리고 그 블록에 대해 이름을 붙임
  // => 이렇게 정의한 블록을 "메서드(method)" 또는 "함수(function)"이라 부름
  // => 자바는 "함수" 보다는 주로 "메서드"라는 이름으로 부름
  // => 메서드 이름은 명령 형태의 동사구로 지음
  //    예) getName(), setName(), printName(), doFilter(), parseInt() 등
  // => 물론 명사구나 전치사구 형태로 짓는 경우도 있음
  //    예) valueOf(), toString() 등
  //
  static void printSpaces(int len) {
    for (int i = 0; i < len; i++) {
      System.out.print(" ");
    }
  }

  // '*' 문자를 출력하는 코드를 관리하기 쉽게 별도의 블록으로 빼둠
  // 그리고 그 블록의 이름을 붙임
  // 이렇게 별도로 빼둔 코드 블록에 이름을 붙인 것을 "메서드=함수"라고 부름
  //
  static void printStars(int len) {
    for (int i = 0; i < len; i++) {
      System.out.print("*");
    }
  }

  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);
    System.out.print("밑변의 길이? ");
    int len = keyScan.nextInt();
    keyScan.close();

    for (int starLen = 1; starLen <= len; starLen += 2) {
      // 명령 코드들을 기능 별로 묶어 놓고 필요할 때마다 다음과 같이 사용하면 코드를 읽기가 쉬워짐
      printSpaces((len - starLen) / 2);
      printStars(starLen);
      System.out.println();
    }
  }
}
import java.util.Scanner;

# 메서드 : 리팩토링

public class Exam0130 {

  public static void printSpaces(int len) {
    for (int i = 0; i < len; i++) {
      System.out.print(" ");
    }
  }

  public static void printStars(int len) {
    for (int i = 0; i < len; i++) {
      System.out.print("*");
    }
  }

  // 코드를 유지보수하기 쉽도록 가능한 기능 별로 묶어둠
  // 그래서 Exam0120에 있던 코드 중에서 공백을 계산하는 코드를 별도의 블록으로 분리하여 이름을 부여함
  public static int getSpaceLength(int totalStar, int displayStar) {
    return (totalStar - displayStar) / 2;
  }

  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);
    System.out.print("밑변의 길이? ");
    int len = keyScan.nextInt();

    for (int starLen = 1; starLen <= len; starLen += 2) {
      // 출력할 스페이스의 개수를 계산하는 코드를 블록에 묶어 놓고 이름을 부여해두고 사용하면 코드를 이해하기가 더 쉬움
      printSpaces(getSpaceLength(len, starLen));
      printStars(starLen);
      System.out.println();
    }
    keyScan.close();
  }
}

 

 

가변 파라미터

# 메서드 : 가변 파라미터

public class Exam0250 {

  // 가변 파라미터
  // [리턴타입] 메서드명(타입... 변수) {...}
  // => 0 개 이상의 값을 받을 때 선언하는 방식
  // => 메서드 내부에서는 배열처럼 사용
  //
  // 다음은 hello()를 호출할 때 String 값을 0개 이상 전달할 수 있음
  static void hello(String... names) {
    for (int i = 0; i < names.length; i++) {
      System.out.printf("%s님 반갑습니다.\n", names[i]);
    }
  }

  public static void main(String[] args) {

    hello(); // 이 경우 names 배열의 개수는 0
    System.out.println("-------------------");

    hello("홍길동"); // 이 경우 names 배열의 개수는 1
    System.out.println("-------------------");

    hello("홍길동", "임꺽정", "유관순"); // 이 경우 names 배열의 개수는 3
    System.out.println("-------------------");

    // 가변 파라미터 자리에 배열을 직접 넣어도 됨
    String[] arr = {"김구", "안중근", "윤봉길", "유관순"};

    hello(arr);
    System.out.println("-------------------");

    //    hello("홍길동", 20, "오호라"); // 다른 타입은 안됨 -> 컴파일 오류!
  }
}

 

# 메서드 : 가변 파라미터

public class Exam0251 {

  // 가변 파라미터에 배열을 넘길 경우
  // => 가변 파라미터에 배열을 넘길 경우 그 배열을 그대로 받아 사용
  static void hello(String... names) {
    for (int i = 0; i < names.length; i++) {
      names[i] += "^^";
      System.out.printf("%s님 반갑습니다.\n", names[i]);
    }
  }

  public static void main(String[] args) {

    String[] arr = { "김구", "안중근", "윤봉길", "유관순" };

    // 가변 파라미터에 배열을 넘길 경우
    hello(arr);
    System.out.println("-------------------");

    for (String value : arr) {
      System.out.println(value);
    }
  }
}

ㄴ 가변파라미터일 경우 배열을 넘겨도 됨

 

가변 파라미터 vs 배열 파라미터

# 메서드 : 가변 파라미터 vs 배열 파라미터

public class Exam0260 {

  // 가변 파라미터
  static void hello(String... names) {
    for (int i = 0; i < names.length; i++) {
      System.out.printf("%s님 반갑습니다.\n", names[i]);
    }
  }

  // 배열 파라미터
  static void hello2(String[] names) {
    for (int i = 0; i < names.length; i++) {
      System.out.printf("%s님 반갑습니다.\n", names[i]);
    }
  }

  public static void main(String[] args) {

    // 가변 파라미터의 메서드를 호출할 때는
    // => 다음과 같이 낱개의 값을 여러 개 줄 수도 있고,
    hello("홍길동", "임꺽정", "유관순");
    // String[] temp = {"홍길동", "임꺽정", "유관순"};
    // hello(temp);

    System.out.println("-------------------");

    // => 또는 다음과 같이 배열에 담아서 전달할 수도 있음
    String[] arr = {"김구", "안중근", "윤봉길", "유관순"};
    hello(arr);
    System.out.println("-------------------");

    // 배열 파라미터의 메서드를 호출할 때는 가변 파라미터와 달리 낱개의 값을 여러 개 줄 수 없음
    
    //    hello2("홍길동", "임꺽정", "유관순");
    //    System.out.println("-------------------");

    // => 오직 배열에 담아서 전달해야 함

    String[] arr2 = {"김구", "안중근", "윤봉길", "유관순"};
    hello2(arr2);
    System.out.println("-------------------");
  }
}

 

가변 파라미터 => 여러 개 선언 불가

1) 가변 파라미터는 여러 개 선언할 수 없음
     => 아규먼트의 시작과 끝을 구분할 수 없음
        예) m1("aaa", "bbb", "aaa@test.com", "bbb@test.com");
        어느 값이 names 배열에 들어가고, 어느 값이 emails 배열에 들어가는가?
     static void m1(String... names, String... emails) {} // 컴파일 오류!
     static void m1(String[] names, String[] emails) {} // OK!
  
     => 중간에 다른 타입이 온다 하더라도 안됨
   static void m1(String... names, int a, String... emails) {}// 컴파일 오류!
   static void m1(String[] names, int a, String[] emails) {} // OK!
  
  위의 메서드는 값을 구분할 수 있을 것 같은데?
  => 그냥 다음과 같이 호출하면 되는 것 아닌가?
  예) m1("aaa", "bbb", 100, "ccc", "ddd", "eee");
  => 사람들은 쉽게 구분할 수 있음
     그러나 컴파일러가 이런 상황을 구분하려면 굉장히 복잡해짐
  => 그래서 가변 파라미터라는 문법의 이점은 사용하되 너무 복잡한 사용법은 지양하기 위해서 사용 방법을 간단히 한 것

  2) 가변 파라미터는 반드시 맨 끝에 와야 함
  => 아규먼트의 시작과 끝을 구분할 수 없음
  예) m2("aaaa");
   static void m2(String... names, String a) {} // 컴파일 오류!
   static void m2(boolean b, String... names, int a) {} // 컴파일 오류!

 

# 메서드 : 가변 파라미터의 단점

public class Exam0271 {

  static void m2(int a, String... names) {} // OK!

  // 배열 파라미터는 여러 개 선언할 수 있음
  static void x1(String[] names, String[] emails) {}

  // 배열 파리미터는 순서에 상관 없음
  static void x2(String[] names, int a) {}

  public static void main(String[] args) {
    // 컴파일 확인하라
  }
}


// - 메서드에 가변 파라미터는 한 개만 사용할 수 있음
// - 가변 파라미터는 반드시 맨 뒤에 와야 함
// - 그 이유는 복잡한 사용을 막기 위해서

 

 

 

 

https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-2.html#jvms-2.6

ㄴ Frames 은 메서드가 호출될 때마다 새 프레임이 생성됨

 

https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-2.html#jvms-2.6

ㄴ 로컬 변수와 부분 결과를 보유하고 메서드 호출 및 반환에 참여함

 

메서드 호출과 JVM Stack 메모리

Test.java

public class Test {
  public static void main(String[] args) { // args 도 local 변수
    System.out.println(args);
    int a = 100, b = 200;
    swap(a, b);
    System.out.printf("main() : %d, %d\n", a, b);
  }

  static void swap(int a, int b){
    int temp = a;
    a = b;
    b = temp;
    System.out.printf("swap() : %d, %d\n", a, b);
  }
}

 

# 메서드 : call by value

public class Exam0310 {

  static void swap(int a, int b) {
    System.out.printf("swap(): a=%d, b=%d\n", a, b);
    int temp = a;
    a = b;
    b = temp;
    System.out.printf("swap(): a=%d, b=%d\n", a, b);
  }

  public static void main(String[] args) {
    int a = 100;
    int b = 200;

    // swap() 호출할 때 a 변수의 값과 b 변수의 값을 넘김
    // => 그래서 "call by value"라 부름
    // => 비록 swap()에서 a와 b라는 이름의 변수가 있지만,
    //    이 변수는 main()에 있는 변수와 다른 변수임
    swap(a, b);
    System.out.printf("main(): a=%d, b=%d\n", a, b);
  }
}

// call by value
// => 아규먼트가 primitive data type인 경우, 메서드를 호출할 때 값을 넘김
// => 자바에서는 primitive data type에 대해서 메모리(변수) 주소를 넘기는 방법이 없음

ㄴ call by value => 값을 넘기는 것

ㄴ 로컬 변수는 JVM Stack 에 만들어지고, new 로 만들어지는 변수는 heap 에 저장됨

 

 

call by reference

# 메서드 : call by reference

public class Exam0320 {

  static void swap(int[] arr) {
    System.out.printf("swap(): arr[0]=%d, arr[1]=%d\n", arr[0], arr[1]);
    int temp = arr[0];
    arr[0] = arr[1];
    arr[1] = temp;
    System.out.printf("swap(): arr[0]=%d, arr[1]=%d\n", arr[0], arr[1]);
  }

  public static void main(String[] args) {
    int[] arr = new int[] {100, 200};
    swap(arr); // 배열 인스턴스(메모리)를 넘기는 것이 아님 => 주소를 넘기는 것
    // => 그래서 "call by reference" 라 부름
    System.out.printf("main(): arr[0]=%d, arr[1]=%d\n", arr[0], arr[1]);
  }
}

ㄴ JVM Stack => 메서드의 로컬변수를 두는 영역

ㄴ Heap => new 라는 명령으로 만들어지는 변수를 두는 영역

 

 

객체와 call by reference

# 메서드 : call by reference II

public class Exam0330 {

  // main()에서 만든 int a와 int b의 값을 바꾸고 싶다면,
  // primitive data type 값을 직접 넘기지 말고 
  // 객체에 담아 넘겨라
  static class MyObject {
    // => class 는 메모리의 구조를 설계하는 문법
    // => new 명령을 이용하여 변수를 생성할 수 있음
    int a;
    int b;
  }

  static void swap(MyObject ref) {
    System.out.printf("swap(): a=%d, b=%d\n", ref.a, ref.b);
    int temp = ref.a;
    ref.a = ref.b;
    ref.b = temp;
    System.out.printf("swap(): a=%d, b=%d\n", ref.a, ref.b);
  }

  public static void main(String[] args) {
    // MyObject 설계도에 따라 int a와 int b 메모리를 만듦
    // 그리고 그 메모리(인스턴스=객체)의 주소를 ref 변수에 저장
    MyObject ref = new MyObject();
    ref.a = 100;
    ref.b = 200;

    // a, b 변수가 들어 있는 인스턴스(객체=메모리)의 주소를 
    // swap()에 넘김 => 그래서 "call by reference"인 것
    swap(ref);
    System.out.printf("main(): a=%d, b=%d\n", ref.a, ref.b);
  }
}

ㄴ 레퍼런스 => 주소를 담는 변수

ㄴ MyObject 클래스에서 non-static 변수를 힙에 준비

ㄴ Heap에 저장되어있는 메모리는 연속되어있으며, 각 변수는 기본 값이 0으로 초기화 되어있음

ㄴ new 명령으로 만들어진 변수의 연속된 메모리들을 MyObject 의 Instance 라고 하며, 이는 더 큰 의미로 말하면 MyObject 의 객체(Object) 라고 함

 

 

 

객체 생성과 리턴

# 메서드 : 레퍼런스를 리턴하기

public class Exam0340 {

  // swap()에서 만든 int a와 int b의 값을 main()에서 사용하기
  // primitive data type 값을 객체에 담아 넘겨라

  static class MyObject {
    int a;
    int b;
  }

  static MyObject swap(int a, int b) {
    MyObject ref = new MyObject();
    ref.a = b;
    ref.b = a;
    return ref;
  }

  public static void main(String[] args) {
    int a = 100;
    int b = 200;

    MyObject ref = swap(a, b);

    System.out.printf("main(): ref.a=%d, ref.b=%d\n", ref.a, ref.b);
  }
}

 

 

 

 

 

 

메서드 : JVM 메모리

public class Exam0410 {

  static void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    System.out.printf("swap(): a=%d, b=%d\n", a, b);
  }

  public static void main(String[] args) {
    int a = 100;
    int b = 200;
    swap(a, b);
    System.out.printf("main(): a=%d, b=%d\n", a, b);
  }
}
// 실행 순서와 메모리
// 1) java -classpath bin com.eomcs.lang.ex07.Exam0410
//    => JVM은 클래스 정보를 Method Area 영역에 로드
// 2) main() 호출
//    => JVM Stack 영역에 main() 메서드가 사용할 로컬 변수를 준비
// 3) swap() 호출
//    => JVM Stack 영역에 swap() 메서드가 사용할 로컬 변수를 준비
// 4) swap() 실행 완료
//    => JVM Stack 영역에 있던 swap()이 사용한 메모리를 제거
// 5) main() 실행 완료
//    => JVM Stack 영역에 있던 main()이 사용한 메모리를 제거
// 6) JVM 실행 종료
//    => OS가 JVM에게 사용하라고 빌려줬던 모든 메모리를 회수함


// JVM이 메모리를 다루는 방법
// - 크게 다음 세가지 영역으로 나눠 관리

// 1) Method Area
// - 클래스 명령 코드를 둠
// - static 변수를 둠

// 2) Heap
// - new 명령으로 만든 메모리(인스턴스=객체)를 둠
// - Garbage Collector(GC)가 관리하는 영역임

// 3) JVM Stack
// - 스레드 별로 JVM Stack 메모리를 따로 관리
// - 메서드의 로컬 변수를 둠
// - 각 메서드마다 프레임 단위로 관리함
// - 메서드 호출이 끝나면 그 메서드가 사용한 프레임 메모리가 제거됨
// - 이렇게 메서드가 호출될 때 로컬 변수가 준비되고 맨마지막에 호출한 메서드가 먼저 삭제된다고 해서
//   "스택(stack)" 메모리라 부름
//   스택? 접시 쌓는 것을 생각하라
// - 스택 방식을 "Last In First Out(LIFO;후입선출, FILO;선입후출)"라 부름

// JVM이 종료하면 JVM이 사용했던 모든 메모리를 OS가 회수함

 

메서드 : Heap 메모리 영역

public class Exam0420 {

  static int[] getArray() {
    int[] arr = new int[] {100, 200, 300};
    // => int 배열 주소를 담을 arr 변수를 JVM Stack 영역에 준비
    // => 100, 200, 300 값을 담은 배열을 Heap 영역에 준비
    // => Heap 영역에 준비한 배열 메모리의 주소를 JVM Stack 메모리에 있는 arr 변수에 넣음

    return arr;
  }

  public static void main(String[] args) {
    int[] arr;
    arr = getArray();
    System.out.println(arr[1]); // 200
  }
}

// 1) main() 호출
//    => JVM Stack: args, arr 변수 생성
// 2) getArray() 호출
//    => JVM Stack: arr 변수 생성
//    => Heap: new int[] 배열 생성
// 3) getArray() 호출 끝
//    => JVM Stack: getArray() 관련 메모리(arr 변수) 제거
//    => new int[] 배열 주소 리턴
// 4) main() 호출 끝
//    => JVM Stack: main() 관련 메모리 제거 
// 5) JVM 종료
//    => JVM이 사용한 모든 메모리(Method Area, JVM Stack, Heap 등)를 OS 반납

 

배열의 생성은 어디서 하는 것이 좋은가?

public class Exam0421 {

  public static void main(String[] args) throws Exception {

    int[] moneys = new int[] {100, 200, 300};
    float[] totals = new float[moneys.length];

    // 호출하는 쪽에서 결과를 담을 배열을 주는 경우
    compute(moneys, totals, 0.0089f);

    for (int i = 0; i < moneys.length; i++) {
      System.out.printf("%d => %.1f\n", moneys[i], totals[i]);
    }

    System.out.println("---------------------");

    float[] result;
    // 메서드 쪽에서 결과를 담을 배열을 만들어 리턴하는 경우
    result = compute2(moneys, 0.0089f);

    for (int i = 0; i < moneys.length; i++) {
      System.out.printf("%d => %.1f\n", moneys[i], result[i]);
    }

  }

  static void compute(int[] moneys, float[] totals, float interest) {
    for (int i = 0; i < moneys.length; i++) {
      totals[i] = moneys[i] + (moneys[i] * interest);
    }
  }

  static float[] compute2(int[] moneys, float interest) {
    float[] totals = new float[moneys.length];
    for (int i = 0; i < moneys.length; i++) {
      totals[i] = moneys[i] + (moneys[i] * interest);
    }
    return totals;
  }

}

 

메서드 : 인스턴스와 Heap 메모리 영역

public class Exam0430 {

  // Heap 메모리에 어떤 변수를 만들어야 하는지 적어 놓은 설계도
  // => 나중에 new 명령을 사용하여 메모리를 만들라고 하면,
  //    MyObject에 적어 놓은 변수를 Heap 영역에 생성하라는 뜻
  static class MyObject {
    int a;
    int b;
  }

  static MyObject getMyObject() {
    MyObject ref = new MyObject(); // MyObject에 선언된대로 변수를 Heap에 만들어라
    ref.a = 100;
    ref.b = 200;

    return ref;
  }

  public static void main(String[] args) {
    MyObject ref;
    ref = getMyObject();
    System.out.println(ref.a);
    System.out.println(ref.b);
  }
}

// 1) main() 호출
//    => JVM Stack: args, ref 변수 생성
// 2) getMyObject() 호출
//    => JVM Stack: ref 변수 생성
//    => Method Area: MyObject 클래스를 로딩
//    => Heap: MyObject 설계도에 따라 인스턴스 생성
// 3) getMyObject() 호출 끝
//    => JVM Stack: getMyObject() 관련 메모리(ref 변수) 제거
//    => MyObject의 인스턴스의 주소 리턴
// 4) main() 호출 끝
//    => JVM Stack: main() 관련 메모리 제거
// 5) JVM 종료
//    => JVM이 사용한 모든 메모리(Method Area, JVM Stack, Heap 등)를 OS 반납

 

메서드 : 스택 메모리 응용

public class Exam0440 {

  static int m1(int value) {
    int r1 = m2(value);
    int r2 = m3(value);
    return r1 + r2;
  }

  static int m2(int value) {
    return value + 100;
  }

  static int m3(int value) {
    return value + 200;
  }

  public static void main(String[] args) {
    int r = m1(5);
    System.out.println(r);
  }
}
// JVM Stack 메모리의 사용
// 0) 시작
// 1) main()
// 2) main() => m1()
// 3) main() => m1() => m2()
// 4) main() => m1()
// 5) main() => m1() => m3()
// 6) main() => m1()
// 7) main()
// 8) 종료

 

 

 

# 메서드 : main() 메서드

public class Exam0510 {

  // JVM이 클래스를 실행할 때 main() 메서드를 호출함
  // 메인 메서드는 반드시 다음과 같은 메서드 시그너처(함수 프로토타입)를 가져야 함
  // public static void main(String[] 변수명)
  // 
  public static void main(String[] 변수명은상관없음) {
    System.out.println("Hello!");
  }
}

 

 

 

main()의 아규먼트

# 메서드 : main() 메서드 - 프로그램 아규먼트

public class Exam0520 {

  // 프로그램 아규먼트
  // - jvm을 실행할 때 프로그램에 전달하는 값
  // - 예)
  // > java -cp bin/main com.eomcs.lang.ex07.Exam0520 aaa bbb cccc
  // aaa bbb cccc 가 프로그램 아규먼트임
  //
  public static void main(String[] args) {
    // 프로그램 아규먼트는 스트링 배열에 담겨서 main()를 호출할 때 넘어옴
    // 프로그램 아규먼트는 공백을 기준으로 문자열을 잘라서 배열을 만듦
    // 아규먼트가 없으면 빈 배열이 넘어옴

    for (String value : args) {
      System.out.printf("[%s]\n", value);
    }
    System.out.println("종료!");
  }
}

 

메서드 : main() 메서드 - 프로그램 아규먼트 응용 I

public class Exam0530 {

  public static void main(String[] args) {
    // 합계를 출력하는 프로그램을 작성하라
    // $ java -cp ./bin/main com.eomcs.lang.ex07.Exam0530 200 43 56  // 문자열

    int sum = 0;
    for (String arg : args)
      sum += Integer.parseInt(arg);
    System.out.printf("합계: %d\n", sum);
  }
}

// # 프로그램 아규먼트(arguments)
// - 프로그램을 실행할 때 넘겨주는 값
// - 어떻게 아규먼트를 넘기는가?

// $ java 클래스명 값1 값2 값3

// - 아규먼트는 공백으로 구분
// - JVM은 아규먼트의 개수만큼 문자열 배열을 만들어 저장
// - 아규먼트가 없으면 빈 배열을 만듦
// - 그 후 main()을 호출할 때 그 배열의 주소를 넘겨줌

 

메서드 : main() 메서드 - 프로그램 아규먼트 응용 II

public class Exam0540 {

  public static void main(String[] args) {
    // 학생의 이름과 국영수 점수를 입력 받아 총점과 평균을 출력하라
    // $ java -cp ./bin/main com.eomcs.lang.ex07.Exam0540 홍길동 100 100 90
    // 이름: 홍길동
    // 총점: 290
    // 평균: 96.9


    if (args.length < 4) {
      System.out.println(
          "실행 형식: java -cp ./bin/main com.eomcs.lang.ex07.Exam0540 이름 국어점수 영어점수 수학점수");
      return; // <-- JVM 종료
    }

    int sum = 0;
    for (int i = 1; i < args.length; i++)
      sum += Integer.parseInt(args[i]);

    System.out.printf("이름: %s\n", args[0]);
    System.out.printf("총점: %d\n", sum);
    System.out.printf("평균: %.1f\n", sum / 3f);
  }
}

 

 

실습

import java.util.Scanner;

public class App {
  
  static Scanner scanner = new Scanner(System.in);

  static final int MAX_SIZE = 100;
  static int[] no = new int[MAX_SIZE];
  static String[] name = new String[MAX_SIZE];
  static String[] email = new String[MAX_SIZE];
  static String[] password = new String[MAX_SIZE];
  static char[] gender = new char[MAX_SIZE];
  static int userId = 1;
  static int length = 0;
  
  public static void main(String[] args) {

    printTitle();

    while (length < MAX_SIZE) {
      inputMember();
      if (!promptContinue()) {
        break;
      }
    }

    printMembers();

    scanner.close();
  }

  static void printTitle() {
    System.out.println("나의 목록 관리 시스템");
    System.out.println("----------------------------------");
  }

  static void inputMember() {

    System.out.print("이름? ");
    name[length] = scanner.next();

    System.out.print("이메일? ");
    email[length] = scanner.next();

    System.out.print("암호? ");
    password[length] = scanner.next();

    loop: while (true) {
      System.out.println("성별: ");
      System.out.println("  1. 남자");
      System.out.println("  2. 여자");
      System.out.print("> ");
      String menuNo = scanner.next();
      scanner.nextLine(); // 입력 값(token)을 읽고 난 후에 남아 있는 줄바꿈 코드를 제거한다.

      switch (menuNo) {
        case "1":
          gender[length] = 'M';
          break loop;
        case "2":
          gender[length] = 'W';
          break loop;
        default:
          System.out.println("무효한 번호입니다.");
      }
    }

    no[length] = userId++;
    length++;
  }

  static boolean promptContinue() {
    System.out.print("계속 하시겠습니까?(Y/n) ");
    String response = scanner.nextLine();
    if (!response.equals("") && !response.equalsIgnoreCase("Y")) {
      return false;
    }
    return true;
  }

  static void printMembers() {
    System.out.println("---------------------------------------");
    System.out.println("번호, 이름, 이메일, 성별");
    System.out.println("---------------------------------------");

    for (int i = 0; i < length; i++) {
      System.out.printf("%d, %s, %s, %c\n", no[i], name[i], email[i], gender[i]);
    }
  }
}

 

ㄴ static 변수 => static 메서드끼리 공유하여 사용 가능