Notice
Recent Posts
Recent Comments
Link
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[Java] 예제 소스 정리 - 메서드(개념, 가변 파라미터, 배열 파라미터, call by value, call by reference, main(), JVM 아규먼트) 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[Java] 예제 소스 정리 - 메서드(개념, 가변 파라미터, 배열 파라미터, call by value, call by reference, main(), JVM 아규먼트)

끈기JK 2022. 12. 30. 19:10

com.eomcs.lang.ex07

 

예제 소스 정리

 

메서드

 

 

별 출력하기

 1단계: 공백 출력 코드를 메서드로 추출하기
 2단계: 별을 출력하는 코드를 메서드로 추출하기
 3단계: while 대신 for 문 사용하기
 4단계: 공백 계산 식을 메서드로 추출하기

public class Exam0114 {

  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("*");
    }
  }

  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();
    keyScan.close();

    for (int starLen = 1; starLen <= len; starLen += 2) {
      printSpaces(getSpaceLength(len, starLen));
      printStars(starLen);
      System.out.println();
    }
  }
}

 

 

# 메서드 : 사용 후

 스페이스를 출력하는 코드들을 관리하기 쉽도록 별도의 블록에 모아 놓는다. 그리고 그 블록에 대해 이름을 붙인다.
 => 이렇게 정의한 블록을 "메서드(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();
    }
  }

 

 

# 메서드 : 리팩토링
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();
  }
}

 

 

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

- 특정 기능을 수행하는 명령문들을 모아 둔 블록

 

## 메서드 문법
  [리턴값의 타입] 함수명(파라미터선언, ...) {명령문들}
- 리턴 값(return value)의 타입?
  => 함수 블록에 들어있는 명령문을 수행 완료한 후 
     그 결과로 놓이는 값의 타입.
- 파라미터(parameter) 선언?
  => 함수 블록을 실행할 때 외부로부터 받은 값을 저장할 변수 선언.

1) 명령문 블록을 실행할 때 값을 받지 않고 결과도 리턴하지 않는다.
void 메서드명() {
  문장1;
  문장2;
}

2) 명령문 블록을 실행할 때 작업에 필요한 값을 받는다. 그러나 결과는 리턴하지 않는다.
void 메서드명(변수선언1, 변수선언2, ...) {
  문장1;
  문장2;
}

3) 명령문 블록을 실행할 때 값을 받지 않는다. 작업 결과는 리턴한다.
리턴타입 메서드명() {
  문장1;
  문장2;
}

4) 명령문 블록을 실행할 때 작업에 필요한 값을 받는다. 그리고 작업 결과를 리턴한다.
리턴타입 메서드명(변수선언1, 변수선언2, ...) {
  문장1;
  문장2;
}

public class Exam0210 {
  // 
  // 예1) 메서드 : 리턴값(X), 파라미터(X)
  // - 함수 블록을 실행할 때 특정 값을 함수에 넘겨 줄 필요가 없고,
  //   함수 블록 실행을 완료한 후 어떤 값도 돌려주지 않는다.
  // - "여러분, 부자되세요!", "여러분, 식사하고 오세요!"
  //
  static void hello() {
    System.out.println("안녕하세요!");
    System.out.println("이 메서드는 어떤 값도 리턴하지 않습니다.");
  }

  public static void main(String[] args) {

    // ## 메서드를 사용하는 방법
    // [리턴값을 받을 변수] = 메서드명(아규먼트);
    // - 아규먼트(argument)?
    //   => 메서드 블록에 들어 있는 명령을 실행할 때 필요한 값
    //   => 즉 파라미터 변수에 넘겨주는 값
    //   => 파라미터 변수의 타입과 개수와 순서에 맞게 값을 넘겨줘야 한다.
    //      만약 변수의 타입과 값의 타입이 다르면 컴파일 오류!
    //      만약 변수의 개수와 값의 개수가 다르면 컴파일 오류!
    //      변수 선언 순서와 값의 순서가 다르면 컴파일 오류!
    //
    // - 리턴값을 받을 변수
    //   => 메서드 블록을 실행한 후 리턴되는 값을 받을 변수이다.
    //   => 메서드가 값을 리턴한다 하더라도 값을 받기 싫으면
    //      변수를 선언하지 않아도 된다.
    //      그러면 리턴 되는 값은 버려진다.
    //   => 값을 리턴하지 않는 메서드에 대해 변수를 선언하면 컴파일 오류!

    // ## 메서드 블록의 명령을 실행하기
    // => 메서드 실행하기
    // => 메서드 호출하기
    //    즉 다음은 "hello"라는 이름을 가진 메서드 블록을 찾아가서 실행하라는 의미.
    hello();
    // ## 실행 과정
    // 1) hello() 메서드의 블록으로 간다.
    // 2) 메서드 바디를 실행한다.
    // 3) 다시 원래 위치로 돌아온다.
    // 4) 다음 줄을 실행한다.

    System.out.println("hello() 실행 완료!");
  }
}

 ## 메서드(method) = 함수(function)?
 - 명령문을 기능 단위로 관리하기 쉽게 별도로 분리하여 묶어 놓은 것.
 - 반복적으로 자주 사용하는 명령문을 재사용하기 쉽도록 별도로 분리하여 묶어 놓은 것.
 - "코드를 관리하기 쉽고 재사용하기 쉽도록 기능 단위로 묶어 놓는 문법"

 ## 용어
 - 메서드명, 파라미터 변수 선언 : 메서드 시그너처(method signature)
      예) hello()
 - 메서드 블록 : 메서드 몸체(method body)
      예) {
            System.out.println("안녕하세요!");
            System.out.println("이 메서드는 어떤 값도 리턴하지 않습니다.");
          }

 ## 메서드 종류?
 1) 클래스 메서드
    - 클래스에 소속되어 있다.
    - 모든 인스턴스가 공유한다.
    - static이 붙는다.
 2) 인스턴스 메서드
    - 특정 인스턴스에 대해 사용한다.
    - static이 붙지 않는다.

 

 

# 메서드를 잘못 사용한 예
public class Exam0211 {

  static void hello() {
    System.out.println("안녕하세요!");
    System.out.println("이 메서드는 어떤 값도 리턴하지 않습니다.");
  }

  public static void main(String[] args) {

    // hello 메서드는 파라미터 변수가 없기 때문에 호출할 때 값을 넣으면
    //    hello(100);// 컴파일 오류!

    // hello 메서드는 값을 리턴하지 않기 때문에 변수로 값을 받으려 하면
    int i;
    //    i = hello(); // 컴파일 오류!

  }
}

 

 

# 메서드 : 개념 및 기본 문법 II
public class Exam0220 {

  // 예2) 메서드 : 리턴값(X), 파라미터(O)
  // - 메서드 블록을 실행할 때 값이 필요하다면 파라미터 변수를 선언하라!
  // - "여기 돈 줄테니 밥먹고 와!", "여기 등록금이다. 학비 내라."
  static void hello(String name, int age) {
    // 파라미터?
    // - 메서드를 실행할 때 사용할 값을 외부로부터 받기 위해 선언한 로컬 변수.
    // - 메서드를 실행할 때 생성되고 메서드 실행이 끝나면 제거된다.
    System.out.printf("%d살 %s님 반갑습니다.\n", age, name);

    //    System.out.println(age + "살 " + name + "님 반갑습니다.");
  }

  public static void main(String[] args) {

    System.out.println("main()11111");

    // hello 메서드 호출하기
    hello("홍길동", 20);
    // hello 메서드 실행이 완료되면 다시 이리로 되돌아 와서
    // 다음 명령을 실행한다.

    System.out.println("main()22222");

    // 메서드는 언제든 필요할 때 마다 반복하여 실행할 수 있다.
    hello("임꺽정", 30);

    System.out.println("main()33333");

    // 또 메서드 호출
    hello("유관순", 16);

    System.out.println("main()44444");
  }
}

 아규먼트(argument)
 - 메서드를 호출할 때 넘겨주는 값
 - 예) hello("홍길동", 20);
 "홍길동", 20 이 아규먼트이다.

 파라미터(parameter)
 - 아규먼트를 받는 변수
 - 예) void hello(String name, int age) {...}
 name과 age 변수가 파라미터이다.

 현장에서는 "아규먼트"와 "파라미터"를 구분하지 않고 사용한다.

 

 

# 메서드 : 개념 및 기본 문법 II
public class Exam0221 {

  static void hello(String name, int age) {
    System.out.printf("%d살 %s님 반갑습니다.\n", age, name);
  }

  public static void main(String[] args) {
    // 파라미터의 타입, 개수, 순서가 일치해야 한다.

    //    hello("윤봉길"); // 컴파일 오류!
    //    hello(20, "윤봉길"); // 컴파일 오류!
    //    String r = hello("안중근", 30); // 컴파일 오류!
    //    void r = hello("안중근", 30); // 컴파일 오류!
  }
}

 

 

# 메서드 : 개념 및 기본 문법 III
public class Exam0230 {

  // 3) 메서드 : 파라미터(X), 리턴값(O)
  //    => 메서드 블록을 실행한 후 값을 리턴하는 메서드.
  //       메서드 정의할 때 어떤 값을 리턴하는 지 그 타입을 적어야 한다.
  //       메서드에서도 종료하기 전에 반드시 그 타입의 값을 리턴해야 한다.
  //    => 리턴 타입은 반드시 한 개만 가능하다.
  //       만약 여러 개의 값을 리턴하고 싶다면, 배열에 담거나 객체에 담아 리턴하라!
  //    => "손들어. 돈내놔!"
  static String hello() {
    // 값을 리턴하는 문법
    // return 값;
    return "안녕하세요!"; // 리턴 명령을 실행하면 메서드 실행을 종료한다.

    // 메서드를 리턴한 후에 작업을 수행할 수 없다.
    //    int a; // 컴파일 오류!
    //    System.out.println("NO!"); // 컴파일 오류!
  }

  public static void main(String[] args) {

    // hello() 메서드를 실행하고, 그 리턴 값을 변수에 담는다.
    // => 리턴 값을 받을 변수를 준비한다.
    // => 변수에 리턴 값을 받는다.
    // => 리턴 값과 변수의 타입이 같아야 한다.
    String r = hello();
    System.out.println(r);

    // 메서드가 리턴한 값을 한 번만 사용할 경우 
    // 쓸데없이 로컬 임시 변수를 만들지 않는다.
    // 사용할 곳에 바로 메서드 호출 코드를 둔다.
    System.out.println(hello());

    // 메서드가 값을 리턴한다고 해서 반드시 그 값을 변수에 받아야 하는 것은 아니다.
    // 변수에 받을 지 여부는 호출하는 쪽의 마음이다.
    hello(); // 값을 받는 변수가 없으면 리턴 값은 버려진다.

    // 리턴 타입과 다른 타입의 변수로 값을 받으려 하면 컴파일 오류!
    //    int r2 = hello(); // 컴파일 오류!
  }
}

 

 

# 메서드 : 개념 및 기본 문법 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); // 리턴 값은 버려진다.
  }
}

 

 

# 메서드 : 가변 파라미터

 가변 파라미터
 [리턴타입] 메서드명(타입... 변수) {...}
 => 0 개 이상의 값을 받을 때 선언하는 방식.
 => 메서드 내부에서는 배열처럼 사용한다.

public class Exam0250 {

  // 다음은 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, "오호라"); // 다른 타입은 안된다. 컴파일 오류!
  }
}

 

 

# 메서드 : 가변 파라미터

 가변 파라미터에 배열을 넘길 경우 
 기존 배열을 그대로 사용할까? 아니면 파라미터로 받은 배열을 복제해서 사용할까?
 => 가변 파라미터에 배열을 넘길 경우 그 배열을 그대로 받아 사용한다.

  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 배열 파라미터

가변 파라미터는 내부적으로 이렇게 수행된다. 배열을 넘겨도 되고, 인자 목록을 넘겨도 된다.

 

hello("홍길동", "임꺽정", "유관순");

String[] temp = {"홍길동", "임꺽정", "유관순"};
hello(temp);

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) {} // 컴파일 오류!

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

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

    // 가변 파라미터 사용 예:
    System.out.printf("==> %s|%s\n", "aaa", "bbb");
  }

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

 

 

# 메서드 : 가변 파라미터의 단점
  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) {
    // 컴파일 확인하라!
  }

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

 

 

# 메서드 : 메서드 중첩 호출
  public static void main(String[] args) {
    // 2 + 3 + 4 + 5 = ?
    //
    // 1) 메서드의 리턴 값을 변수로 받을 때
    int result = plus(2, 3);
    result = plus(result, 4); // result 변수가 넘어가는 것이 아니라 result 변수의 값이 넘어 간다.
    result = plus(result, 5);
    System.out.println(result);

    // 2) 메서드의 리턴 값을 바로 파라미터에 전달할 때
    result = plus(plus(plus(2, 3), 4), 5);
    // 실행 과정
    // - 메서드를 호출하는 문장의 가장 안쪽부터 실행된다.
    //
    // result = plus(plus(5, 4),5);
    // result = plus(9, 5);
    // result = 14;
    //
    System.out.println(result);

    int r = plus(100, 200);
    System.out.printf("100 + 200 = %d\n", r);

    // 위의 문장은 다음과 같다.
    System.out.printf("100 + 200 = %d\n", plus(100, 200));
  }

  static int plus(int a, int b) {
    return a + b;
  }

 

 

# 메서드 : call by value
  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 reference
  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]);
  }

 

 

# 메서드 : call by reference II

 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);
  }

 

 

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

 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 메모리
  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 메모리 영역
  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 메모리 영역

 Heap 메모리에 어떤 변수를 만들어야 하는지 적어 놓은 설계도
 => 나중에 new 명령을 사용하여 메모리를 만들라고 하면,
    MyObject에 적어 놓은 변수를 Heap 영역에 생성하라는 뜻이다.

  static class MyObject {
    int a;
    int b;
  }

  static MyObject getMyObject() {
    MyObject ref = new MyObject();
    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 반납.

 

 

# 메서드 : 스택 메모리 응용 I
  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) 종료!

 

 

# 메서드 : 스택 메모리 응용 II - 재귀호출
  static int sum(int value) {
    if (value == 1)
      return 1;

    return value + sum(value - 1);
  }

  public static void main(String[] args) {
    // 다음과 같이 작은 수를 계산 할 때는 재귀호출을 사용하는 것이
    // 코드도 간단하고 이해하기도 쉽다.
    System.out.println(sum(18289));
  }

 JVM Stack 메모리의 사용
 0) 시작
 1) main()
 2) main() => sum(5) 
           => 5 + sum(4) 
                  => 4 + sum(3)
                         => 3 + sum(2)
                                => 2 + sum(1)
                                       => 1
 3) main()
 4) 종료!

 재귀호출(recursive call)
 - 쫄지 마라!
 - 그냥 다른 메소드를 호출했다고 생각해라.
 - 메서드가 호출되면? 스택에 그 메소드가 사용할 변수가 생성된다. 이것만 기억하라!
 - 수학식을 코드를 표현하기가 편하다.
 - 코드가 간결하다.
 - 그러나 반복문을 사용하는 경우보다 메모리를 많이 사용한다.
 - 멈춰야 할 조건을 빠뜨리면 스택 메모리가 극한으로 증가하여
   메모리가 부족한 사태에 빠진다.
   이런 사태를 "stackoverflow"라 부른다.
 - 그래서 큰 수(즉 많이 호출되는 경우)에 대해서 
   재귀호출을 할 때 스택오버플로우가 자주 발생한다.
 - 메서드 호출이 너무 깊게 들어가지 않는 상황에서 재귀호출을 사용하라.

 

 

# 메서드 : 스택 오버플로우 오류!

 스택 오버플로우(stack overflow)?
 => JVM 스택 메모리가 꽉 차서 더이상 
    메서드 실행에 필요한 로컬 변수를 만들 수 없는 상태이다.

 예) 
 다음과 같이 큰 수를 계산할 때는 
 재귀호출의 수가 많아져서 쉽게 스택 메모리가 부족해진다.
 따라서 호출 단계가 깊지 않은 작은 수를 다룰 경우에는 
 재귀호출을 써도 되지만,
 호출 단계가 많은 큰 수를 다룰 때는 재귀호출 대신 반복문을 사용하라!

  static int sum(int value) {
    System.out.println(value);
    if (value == 1)
      return 1;

    return value + sum(value - 1);
  }

  public static void main(String[] args) {

    System.out.println(sum(19000));

    // 메소드 호출이 너무 깊어지는 경우는 재귀호출 대신 다른 방법을 사용하라.
    /*
    long sum = 0;
    for (int i = 1; i <= 100000; i++) {
      sum += i;
    }
    System.out.println(sum);
     */
  }

 

 

# 메서드 : 스택 오버플로우 오류!

 호출하는 메서드의 로컬 변수 메모리가 많을 때는 스택 메모리가 빨리 찬다.
 => 즉 스택 오버플로우는 메서드 호출 회수에 영향을 받는 것이 아니라,
    메서드에서 생성하는 로컬 변수의 크기에 영향을 받는다.

  static int sum(int value, int value2, int value3, int value4, int value5, int value6) {
    System.out.println(value);
    if (value == 1)
      return 1;

    return value + sum(value - 1, value2, value3, value4, value5, value6);
  }

  public static void main(String[] args) {
    System.out.println(sum(11000, 0, 0, 0, 0, 0));
  }

 

 

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

 프로그램 아규먼트
 - jvm을 실행할 때 프로그램에 전달하는 값
 - 예)
 > java -cp bin 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

 합계를 출력하는 프로그램을 작성하라.
 $ java -cp ./bin/main com.eomcs.lang.ex07.Exam0530 200 43 56

  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

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

  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;
    }

    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);
  }

 

 

# JVM 아규먼트

JVM 아규먼트?
 - JVM에게 전달하는 값
 - 형식
 $java -cp ./bin/main -D이름=값 -D이름=값 -D이름=값 com.eomcs.basic.ex07.Exam0610

  public static void main(String[] 변수명은상관없다) {
    // JVM 아규먼트의 값 꺼내기
    // => System.getProperty("이름");
    //
    String value1 = System.getProperty("a");
    String value2 = System.getProperty("b");
    String value3 = System.getProperty("c");

    System.out.println(value1);
    System.out.println(value2);
    System.out.println(value3);
  }

 

 

# JVM 아규먼트 응용 I

 JVM 아규먼트로 학생의 이름과 국영수 점수를 입력 받아 총점과 평균을 출력하라
 $ java -cp ./bin/main -Dname=홍길동 -Dkor=100 -Deng=100 -Dmath=90 com.eomcs.basic.ex07.Exam0620
 이름: 홍길동
 총점: 290
 평균: 96.9

  public static void main(String[] 변수명은상관없다) {
    // JVM 아규먼트로 학생의 이름과 국영수 점수를 입력 받아 총점과 평균을 출력하라
    // $ java -cp ./bin/main -Dname=홍길동 -Dkor=100 -Deng=100 -Dmath=90 com.eomcs.basic.ex07.Exam0620
    // 이름: 홍길동
    // 총점: 290
    // 평균: 96.9
    //
    String name = System.getProperty("name");
    String s1 = System.getProperty("kor");
    String s2 = System.getProperty("eng");
    String s3 = System.getProperty("math");

    if (name == null || s1 == null || s2 == null || s3 == null) {
      System.out.println(
          "실행 형식: java -cp ./bin/main -Dname=이름 -Dkor=국어점수 -Deng=영어점수 -Dmath=수학점수 com.eomcs.basic.ex07.Exam0620");
      return;
    }
    int kor = Integer.parseInt(s1);
    int eng = Integer.parseInt(s2);
    int math = Integer.parseInt(s3);

    int sum = kor + eng + math;

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

 

 

# JVM 아규먼트 응용 II

JVM에 기본으로 설정되어 있는 프로퍼티를 모두 출력하라!

public class Exam0630 {

  public static void main(String[] 변수명은상관없다) {
    // JVM의 전체 프로퍼티 목록 가져오기
    java.util.Properties props = System.getProperties();

    // 1) Properties 객체에 저장되어 있는 값의 이름(key)을 알아낸다.
    // => keySet() 이 리턴하는 것은 이름이 들어 있는 집합이다.
    java.util.Set keySet = props.keySet();

    // 이름이 들어 있는 집합에서 한 개의 이름을 가져와서 그 이름으로 저장된 값을 꺼낸다.
    // => 집합에서 꺼낸 이름이 실제는 String 타입이지만,
    //    문법 상으로는 Object로 되어 있어서 
    //    변수를 선언할 때 Object 타입으로 변수를 선언해야 한다.
    for (Object key : keySet) {
      // getProperty()에 이름을 전달할 때는 String 을 전달해야 한다.
      // 물론 key에 들어 있는 것은 String 이 맞지만 
      // 문법 상으로는 key 변수가 Object로 되어 있다.
      // 따라서 getProperty()에 key 변수에 들어 있는 값을 전달할 때
      // String 이라고 컴파일러에게 알려줄 필요가 있다.
      String value = props.getProperty((String) key);
      System.out.printf("%s ==> %s\n", key, value);
    }

    // 위 문장을 다음과 같이 바꿔도 된다.
    //    for (Object key : keySet) {
    //      String value = System.getProperty((String) key);
    //      System.out.printf("%s = %s\n", key, value);
    //    }
  }
}