개발자입니다
[Java] 예제 소스 정리 - 메서드(개념, 가변 파라미터, 배열 파라미터, call by value, call by reference, main(), JVM 아규먼트) 본문
[Java] 예제 소스 정리 - 메서드(개념, 가변 파라미터, 배열 파라미터, call by value, call by reference, main(), JVM 아규먼트)
끈기JK 2022. 12. 30. 19:10com.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);
// }
}
}
'네이버클라우드 AIaaS 개발자 양성과정 1기 > Java' 카테고리의 다른 글
[비트캠프] 41일차(9주차1일) - Java(메서드) (0) | 2023.01.02 |
---|---|
[Java] 예제 소스 정리 - 콘솔 출력 (1) | 2022.12.30 |
[비트캠프] 40일차(8주차5일) - Java(클래스), myapp-03~06 실습 (0) | 2022.12.30 |
[Java] 예제 소스 정리 - 제어문(if, for, while, ArrayList) (1) | 2022.12.30 |
[Java] 예제 소스 정리 - 연산자(산술, 논리, 비트, 조건, 증감, 할당) (0) | 2022.12.29 |