일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 | 31 |
- @Aspect
- 스프링 입문(무료)
- 자바의 정석 기초편 ch9
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch4
- 스프링 mvc2 - 로그인 처리
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch7
- jpa - 객체지향 쿼리 언어
- 자바의 정석 기초편 ch11
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch12
- 스프링 db1 - 스프링과 문제 해결
- 자바의 정석 기초편 ch8
- 자바의 정석 기초편 ch1
- 스프링 mvc1 - 서블릿
- 코드로 시작하는 자바 첫걸음
- 2024 정보처리기사 수제비 실기
- 게시글 목록 api
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch14
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch3
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch6
- 자바의 정석 기초편 ch13
- 스프링 mvc1 - 스프링 mvc
- 자바 기본편 - 다형성
- jpa 활용2 - api 개발 고급
- Today
- Total
나구리의 개발공부기록
코드로 시작하는 자바 첫걸음4, 배열, 메서드 본문
출처 : 인프런 - 자바 입문 - 코드로시작하는 자바 첫걸음 (무료) / 김영한님
1. 배열
1) 배열이 필요한 이유
- 같은 타입의 변수를 반복해서 선언하고 사용할 때 편리하게 사용할 수 있도록 제공하는 기능
- 만약 int타입의 여러가지 변수에 숫자를 저장해야한다고 하면 int타입의 변수를 이름을 각각 다르게 계속 반복적으로 선언하고 숫자의 값을 초기화해야하는 번거로움이 있는데, 이것을 하나의 변수명으로 동일한 타입의 값을 입력받을 수 있음
- 뭔가 반복문을 활용해서 변수명을 바꿔가면서 값을 입력하겠다고 시도해봐도 그런 기능은 자바에서 지원하지않음(필자도 과거 .. 처음 개발을 시작할 때 이런 생각을 해봤음..)
- 즉, 배열은 같은 타입의 변수를 사용하기 편하게 하나로 묶어둔 것임
2) 배열의 선언과 생성
int[] students; // 배열 변수 선언
students = new int[5]; // 배열 생성
int[] students2 = new int[5]; // 배열 변수의 선언과 배열 생성을 한번에 할 수 있음
(1) 배열 변수 선언
- 배열을 사용하려면 배열 변수를 선언해야하는데 일반 변수와의 차이점은 int[] 처럼 타입 다음에 대괄호가 들어감
- 배열 변수를 선언한다고해서 아직 사용할 수 있는 배열이 만들어진 것은 아니고 배열 변수에 선언된 타입으로 만들어진 배열을 만들 수 있다는 뜻임
(2) 배열 생성
- 배열을 사용하기위해서는 배열을 생성해야하는데 new int[5]라고 입력하면 5개의 값을 입력할 수 있는 int형 변수가 만들어짐
- new라는 키워드는 새로 생성한다는 뜻이고 int[5]는 int형 변수 5개라는 뜻이므로 int형 변수 5개를 다룰 수 있는 배열을 새로 만들어짐
(3) 배열과 초기화
- 자바는 배열을 생성할 때 그 내부값을 자동으로 초기화하는데 각 타입마다 지정된 기본값으로 초기화함
- 숫자는 0, boolean은 false, String이나 문자는 null(없다는 뜻)으로 초기화 됨
(4) 배열 참조값 보관
- new 키워드로 배열을 생성하면 배열의 크기만큼 메모리를 확보함, (int형을 5개 사용하면 4byte * 5로 20byte를 확보함)
- 배열을 생성하고 나면 자바는 메모리 어딘가에 있는 이 배열에 접근할 수 있는 참조값(주소)를 반환함
- 즉, 선언한 배열변수 int[] students에 생성된 배열의 참조값을 보관하여 이 참조값을 통해 배열을 참조할 수 있으며 참조한다는 것은 메모리에 있는 실제 배열에 접근해서 사용한다는 것임
- 참고로 배열을 생성하는 new int[5] 자체에는 아무런 이름이 없이 int형 변수를 5개 연속으로 메모리 어딘가에 만드는 것이기에 생성한 배열에 접근하는 방법이 필요하기에 배열을 생성할 때 반환되는 참조값을 어딘가에 보관두었다가 사용해야 함
- 그 참조값을 배열 변수에 보관해 두었다가 실제 배열을 사용하려고할 때 이 변수를 통해서 배열에 접근할 수 있음
3) 배열 사용
(1) 인덱스
- 배열은 변수와 사용법이 비슷한데 차이점은 배열변수에 대괄호[ ]에 숫자 번호를 입력하여 접근할 수 있음
- 이 숫자 번호는 배열의 위치를 나타내는 인덱스(Index)를 입력하는 것임
- 배열의 인덱스는 0부터 시작하기 때문에 배열의길이를 5로 선언했으면 0, 1, 2, 3, 4의 인덱스 번호가 만들어짐
- 즉, 0 ~ (n-1)으로 인덱스의 범위가 생성되며 students[4]가 배열의 마지막 요소가 됨
- 만약 인덱스의 허용 범위를 넘어서면 ArrayIndexOutOfBoundsException 오류가 발생함
(2) 배열에 값 대입 및 읽기
- 배열에 값을 초기화하든, 사용하든 일반적인 변수와 사용법은 같으며 [ ]안에 인덱스만 넣어주면 됨
- student[0] = 90; 으로 배열에 값을 대입하면 변수에 있는 참조값을 통해 실제 배열에 접근하고 인덱스를 사용해서 해당 위치의 요소에 접근하여 값을 대입함
- 즉 student변수는 배열 자체가아니기에 배열의 주소값만 가지고 있으며, 주소값을 통해 실제 배열에 접근하고 [ ] 대괄호에 입력되어있는 실제 배열의 인덱스번호에 값을 저장하게 되는 것임
- 읽는 것도 동일한 프로세스로 변수에 저장된 참조값을 통해 실제 배열에 접근하고 입력된 인덱스 번호의 값을 꺼내와서 반환함
(3) 기본형 vs 참조형
- 자바의 변수 데이터 타입을 가장 크게 보면 기본형과 참조형으로 분류할 수 있음
- 사용하는 값을 직접 넣을 수 있는 기본형과 방금 배열 변수와 같이 메모리의 참조값을 넣을 수 있는 참조형으로 분류할 수 있음
- 기본형(Primitive Type) : 지금까지 다뤘던 int, long, double, boolean 처럼 변수에 사용할 값을 직접 넣을 수 있는 데이터 타입
- 참조형(Reference Type) : int[] students와 같이 데이터에 접근하기 위한 참조값을 저장하는 데이터 타입(뒤에서 학습하는 객체나 클래스를 담을 수 있는 변수들도 모두 참조형임)
** 참고
- 참고가 복잡하게 참조형 타입을 사용해야하는 이유는 기본형은 모두 사이즈가 명확하게 정해져있는 반면에, 배열은 동적으로 사이즈를 변경할 수 있기 때문임
- 기본형은 선언과 동시에 크기가 정해지므로 크기를 동적으로 바꾸거나 할 수는 없지만 배열과 같은 참조형은 크기를 동적으로 할당할 수 있음
- 예를 들어 Scanner를 사용해서 사용자 입력에 따라 size 변수의 값이 변하고 생성되는 배열의 크기고 달라질 수 있는데 이런것을 동적 메모리 할당이라고 함
- 즉, 기본형은 정적으로 선언과 동시에 사이즈가 정해지지만, 참조형은 동적으로 크기가 변하도록 유연성을 제공할 수 있음
- 또한 기본형은 사용할 값을 직접 저장하는 반면, 참조형은 메모리에 저장된 배열이나 객체의 참조를 저장하기에 더 복잡한 데이터 구조를 만들고 관리할 수 있고 기본형은 더 빠르고 메모리를 효율적으로 처리함
- 기본형과 참조형에 대한 내용은 객체를 설명할 때 더 자세히 다룸
4) 배열의 리펙토링
(1) 리펙토링(Refactoring)
- 기존의 코드 기능은 유지하면서 내부 구조를 개선하여 가독성을 높이고 유지 보수를 용이하게 하는 과정을 뜻함
- 코드의 중복을 제거하고, 복잡성을 줄이며 이해하기 쉬운 코드로 만들기 위해 수행됨
- 리펙토링은 버그를 줄이고 프로그램의 성능을 향상시킬 수도 있으며 코드의 설계를 개선하는데에도 도움이 되며 쉽게 작동하는 기능은 같은데 코드를 개선하는 것을 뜻함
(2) 리펙토링 전 배열 선언, 생성, 초기화, 사용코드
- 배열선언과, 배열생성을 각각 코드로 입력,
- 배열의 초기화를 변수명[인덱스번호]로 한줄한줄 입력하여 초기화
- 출력도 초기화와 동일한 방식으로 입력
public static void main(String[] args) {
int[] students; // 배열 변수 선언
students = new int[5]; // 배열 생성
// 배열 초기화
students[0] = 90;
students[1] = 80;
students[2] = 70;
students[3] = 80;
students[4] = 60;
System.out.println("학생1 점수: " + students[0]);
System.out.println("학생2 점수: " + students[1]);
System.out.println("학생3 점수: " + students[2]);
System.out.println("학생4 점수: " + students[3]);
System.out.println("학생5 점수: " + students[4]);
}
(3) 리펙토링 진행
- 배열은 { }을 사용하여 생성과 동시에 편리하게 초기화를 하는 기능을 제공함
- 그러나 이렇게 초기화와 동시에 배열을 선언하고자 할 때에는 한줄에 모든 코드가 있어야하며 리팩토링 전의 코드처럼 배열 변수선언과 초기화코드를 각각 나누면 오류가 발생함
- for 문의 조건에 students.length로 입력하면 배열의 길이 만큼 반복문을 돌릴 수 있어 배열의 길이가 늘어나거나 줄어들어도 반복문을 수정하지 않아도 동작하는 장점이 있음
- 배열의 길이만큼 반복해야하는 반복의 횟수가 정해져있기 때문에 for문을 활용하여 배열의 출력하는 코드를 리팩토링 할 수 있음
public static void main(String[] args) {
int[] students = {90, 80, 70, 60, 50};
/* 에러 발생
int[] students;
students = {90, 80, 70, 60, 50};
*/
for (int i = 0; i < students.length; i++) {
System.out.println("학생" + (i + 1) + " 점수: " + students[i]);
}
}
5) 2차원 배열
(1) 시작
- 지금까지 학습한 배열은 단순히 순서대로 나열되어있는 1차원 배열이였음
- 일반적인 표의 구성처럼 되어있는 배열은 2차원 배열이라고하며 2차원 배열은 행(row)과 열(column)로 구성되어있음
- 아래의 코드 처럼 2차원 배열을 선언할 수 있으며 [ ] 대괄호가 하나 추가되는 것을 제외하고 1차원 배열과 사용법이 같음
- 첫번째 대괄호는 행의 인덱스 번호이고 두번째 대괄호는 열의 인덱스 번호임
// 행의 길이가2, 열의 길이가 3인 int 타입 배열을 생성
int[][] arr = new int[2][3]
(2) 2차원 배열의 사용
- 단순한 사용법은 제외하고 반복문을 사용하여 활용하는 방법만 작성(가장 많이 사용)
- 1차원 배열은 1차 반복문으로, 2차원 배열은 중첩 반복문으로 값을 초기화하거나 활용할 수 있음
- 밖에 있는 반복문은 행의 반복을, 내부에 있는 반복문은 열의 반복을 지정하여 활용할 수 있으며, 행의 길이는 배열변수.length로, 열의 길이는 배열변수[행의인덱스번호].length로 알 수 있음
- 즉, 행은 1차원 배열의 길이를 구하는 방법과 똑같고 각 행의 인덱스마다 또다른 1차원 배열이 있는 구조이기 때문에 배열변수[인덱스번호].length로 행에 들어있는 배열의 길이를 구할 수 있는 것임
- 아래처럼 길이를 이용하여 배열을 활용하면 1차원 배열과 마찬가지로 행과 열의 길이가 변경되어도 반복문의 구조의 변경없이 코드가 잘 돌아감
public static void main(String[] args) {
// 2 x 3 2차원 배열 생성
int[][] arr = new int[2][3];
// 반복문을 이용하여 2차원 배열의 값을 초기화
int i = 1;
for (int row = 0; row < arr.length; row++) {
for (int column = 0; column < arr[row].length; column++) {
arr[row][column] = i++;
}
}
// 2차원 배열을 출력
for (int row = 0; row < arr.length; row++) {
for (int column = 0; column < arr[row].length; column++) {
System.out.print(arr[row][column] + " ");
}
System.out.println();
}
}
6) 향상된 for문
(1) 설명
- 향상된 for문(Enhanced For Loop)을 이해하려면 배열을 먼저 알아야하기에 배열 차트에 강의가 들어가 있음
- 각각의 요소를 탐색한다는 의미로 for-each문이라고도 많이 부르며 향상된 for문을 사용하면 배열을 사용할 때 기존 for문보다 편리하게 사용할 수 있음
- 개발자 입장에서는 배열의 순서를 처음부터 끝까지 탐색할 때 많은 번잡한 일을 해야하는데 이를 해결하기위해서 등장하였으며 실무에서 엄청 자주 사용함
- 인텔리제이에서는 iter + 탭만으로 향상된 for문의 구조를 만들어 줌
(2) 예제
- 향상된 for문을 사용하면 for (변수 : 배열 또는 컬렉션)의 구조로 배열의 각 요소를 하나씩 순차적으로 꺼내서 지정한 변수에 담아서 꺼낼 수 있으며 인덱스 없이 배열 요소를 직접 접근하기에 배열을 순서대로 처리할 때 간편하게 사용됨
- for문의 코드블록에서는 지정한 변수를 가지고 코드를 작성할 수 있음
- 일반 for문보다 향상된 for문을 활용하면 훨씬 깔끔하고 가독성이 좋아짐
- 마지막의 코드처럼 향상된 for문을 활용하지 못하는 경우가 있는데, index의 번호가 직접적으로 for문의 코드에서 필요할 경우에는 억지스럽게 향상된 for문으로 구현하는 것보다 일반 for문으로 구현하는 것이 훨씬 깔끔함
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
//일반 for문
for (int i = 0; i < numbers.length; ++i) {
int number = numbers[i];
System.out.println(number);
}
//향상된 for문, for-each문
for (int number : numbers) {
System.out.println(number);
}
//for-each문을 사용할 수 없는 경우, 증가하는 index 값 필요
for(int i = 0; i < numbers.length; ++i) {
System.out.println("number" + i + "번의 결과는: " + numbers[i]);
}
}
7) 문제와 풀이
(1) 배열과 역순 출력
- 사용자에게 5개의 정수를 입력받아서 배열에 저장하고 입력받은 순서의 반대인 역순으로 출력
- 출력시 출력 포멧은 5, 4, 3, 2, 1과 같이 ,쉼표를 사용해서 구분하고 마지막에는 쉼표를 넣지 않아야 함
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int[] numbers = new int[5];
System.out.println("5개의 정수를 입력하세요:");
for (int i = 0; i < numbers.length; i++) {
numbers[i] = scanner.nextInt();
}
System.out.println("입력한 정수를 역순으로 출력");
for (int i = numbers.length-1; i >= 0; i--) {
System.out.print(numbers[i]);
if (i > 0) {
System.out.print(", ");
}
}
}
(2) 2차원 배열
- 사용자로부터 학생수를 입력받고 입력받은 학생의 국어, 수학, 영어 점수를 입력받아 각 학생의 총점과 평균을 계산하는 프로그램을 작성하고 2차원 배열을 사용해야함
- 학생의 모든 성적은 2차원 배열에 입력을 받도록 중첩 반복문을 활용하여 각 행마다 학생의 국어, 영어, 수학점수를 입력받도록 코드를 작성
- 반복문 내부에서 과목명을 출력해야하기에 과목을 별도의 String 배열로 생성하여 index로 접근할 수 있도록 작성
- 성적의 합계와 평균을 구하기 위해 2차원 배열의 각 행에서 합계점수를 구하고 합계점수를 과목명이 담긴 배열의 길이를 double 타입으로 형변환 하여 나누기 연산을 수행하여 평균을 구한뒤 형식에 맞춰서 출력
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("학생수를 입력하세요:");
int count = scanner.nextInt();
String[] subject = {"국어", "영어", "수학"};
int[][] scores = new int[count][subject.length];
for (int i = 0; i < scores.length; i++) {
System.out.println((i + 1) + "번 한생의 성적을 입력하세요:");
for (int j = 0; j < scores[i].length; j++) {
System.out.print(subject[j] + "점수: ");
scores[i][j] = scanner.nextInt();
}
}
for (int i = 0; i < scores.length; i++) {
int total = 0;
for (int j = 0; j < scores[i].length; j++) {
total += scores[i][j];
}
double average = total / (double) subject.length;
System.out.println((i + 1) + "번 학생의 총점: " + total + ", 평균: " + average);
}
}
(3) 상품 관리 프로그램 만들기 - 문제
- 상품 등록: 상품 이름과 가격을 입력받아 저장
- 상품 목록: 지금까지 등록한 모든 상품의 목록을 출력
- 첫 화면에서 사용자에게 세 가지 선택을 제시, "1. 상품 등록, 2. 상품 목록, 3. 종료"
- 상품 등록을 선택하면 사용자로부터 상품 이름과 가격을 입력받아 배열에 저장
- 상품 목록을 선택하면 배열에 저장된 모든 상품을 출력
- 종료를 선택하면 프로그램을 종료
- 상품은 최대 10개까지 등록할 수 있음
- String[] productNames: 상품 이름을 저장할 String 배열
- int[] productPrices: 상품 가격을 저장할 int 배열
- int productCount: 현재 등록된 상품의 개수를 저장할 int 변수
(3) 상품 관리 프로그램 만들기 - 결과
- 최대 상품 등록 한계 값과 현재 등록된 상품의 개수를 담는 변수를 각각 선언
- 최대 상품 등록 한계 값은 10개이므로 해당 변수의 값으로 상품의 이름과 상품의 가격을 담을 배열을 생성
- while문의 무한반복문으로 프로그램을 구동하고, 3번을 누르면 종료가 되도록 if문을 통해 break문을 작성
- 1번옵션은 현재 상품의 개수가 최대 상품 등록 한계 값보다 작으면 상품 등록 코드가 실행되며 등록 후 상품 등록값을 1 증가시킴
- 상품의 개수가 최대 한도보다 크거나 같으면 더 이상 상품을 등록할 수 없다는 멘트를 출력하고 continue로 while문으로 돌아감
- 2번옵션은 상품의 개수가 0이면 등록된 상품이 없다는 멘트를 출력하고 continue문으로 while문으로 돌아가고, 상품 등록 개수가 1 이상이면 등록된 상품의 개수를 전부 출력
- 1,2,3이 아닌 숫자를 입력하면 잘못된 메뉴를 선택했다는 멘트를 출력
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int maxProducts = 10;
String[] productNames = new String[maxProducts];
int[] productPrices = new int[maxProducts];
int productCount = 0;
while (true) {
System.out.println("1. 상품 등록 | 2. 상품 목록 | 3. 종료");
System.out.print("메뉴를 선택하세요:");
int option = scanner.nextInt();
scanner.nextLine();
if (option == 1) {
if (productCount >= maxProducts) {
System.out.println("더 이상 상품을 등록할 수 없습니다.");
continue;
}
System.out.print("상품 이름을 입력하세요:");
productNames[productCount] = scanner.nextLine();
System.out.print("상품 가격을 입력하세요:");
productPrices[productCount] = scanner.nextInt();
productCount++;
} else if (option == 2) {
if (productCount == 0) {
System.out.println("등록된 상품이 없습니다.");
continue;
}
for (int i = 0; i < productCount; i++) {
System.out.println(productNames[i] + ": " + productPrices[i] + "원");
}
} else if (option == 3) {
System.out.println("프로그램을 종료합니다");
break;
} else {
System.out.println("잘못된 메뉴를 선택하셨습니다.");
}
}
}
2. 메서드
1) 시작
(1) 메서드란?
- 같은 기능을 하는 코드를 중복해서 사용할 경우 이를 재사용할 수 있도록 해주는 기능
- 변경이나, 삭제가 일어날 때에도 메서드로 작성한 부분의 코드만 바꿔주면 되기 때문에 유지보수성이 상당히 좋아짐
- 동일한기능을 하는 코드를 중복해서 작성하지 않고 메서드로 정의해두면 해당 기능이 필요한곳에 메서드를 호출해서 사용기때문에 동일한 기능을 코드의 작성없이 재사용이 가능함
- 프로그램 언어들은 오래 전부터 이러한 문제를 해결하기 위해서 수학의 함수를 개념을 차용해서 사용하였으며 자바에서는 이런 기능을 메서드라고 부르며, 메서드도 함수의 한 종류라고 생각하면 됨
2) 메서드 사용
(1) 메서드 정의
- 주석에서 메서드 정의라고 작성한 부분이 메서드이며 함수를 정의하는 것과 같이 메서드를 정의한다고 표현함
- 만일 메서드가 없이 아래의 코드를 작성했다면, 메서드에 정의했던 부분을 2번 반복해서 작성해주어야 sum1과 sum2의 결과를 얻을 수 있었을 텐데 기능을 메서드로 정의함으로써 해당 기능이 필요할 때 메서드를 호출해서 사용하여 코드의 중복을 제거할 수 있음
- 수학의 함수와 유사하게 생겼으며 함수에 값을 입력하면 어떤 연산을 처리한 다음에 결과를 반환함
- 메서드는 크게 메서드 선언과 메서드 본문으로 나눌 수 있음
public class Method1Ref {
public static void main(String[] args) {
int sum1 = add(5, 10);
System.out.println("결과1 출력 = " + sum1);
int sum2 = add(15, 20);
System.out.println("결과2 출력 = " + sum2);
}
// 메서드 정의
public static int add(int a, int b) {
System.out.println(a + "+" + b + " 연산 수행");
int sum = a + b;
return sum;
}
}
(2) 메서드 선언
public static int add(int a, int b)
- 해당 부분이 메서드의 선언 부분으로 메서드 이름, 반환 타입, 매개 변수(파라미터) 목록을 포함하며 이름 그대로 이런 메서드가 있다고 선언하는 것임
- 메서드 선언 정보를 통해 다른 곳에서 해당 메서드를 호출할 수 있음
- public, static과 같은 내용의 자세한내용은 뒤에서 다루기 때문에 지금은 단순하게 메서드를 만들 때 사용해야 한다고 생각하면됨
- int add(int a, int b)에서 add는 메서드의 이름을 부여하고 메서드의 이름으로 메서드를 호출할 수 있으며 add 앞에 있는 int는 메서드의 반환타입임
- 메서드를 호출할 때 전달하는 입력 값을 정의하고 이 변수들은 해당 메서드 안에서만 사용되며, 이렇게 메서드 선언에 사용되는 변수를 영어로 파라미터(parameter), 한글로 매개변수라고하며 각각의 파라미터도 타입이 있음
(3) 메서드 본문
{
System.out.println(a + "+" + b + " 연산 수행");
int sum = a + b;
return sum;
}
- 메서드가 수행해야하는 코드 블록이며 메서드를 호출하면 메서드 본문이 순서대로 실행됨
- 메서드 본문은 블랙박스처럼 메서드를 호출하는 곳에서는 메서드 선언은 알지만 메서드 본문은 전혀 알지 못함
- 메서드의 실행 결과를 반환하려면 return문을 사용해야하며 return문 다음에 반환할 결과를 적어주면 됨
(4) 메서드 호출
int sum1 = add(5, 10);
int sum2 = add(15, 20);
- 정의한 메서드를 실행하기 위해선 메서드를 호출해야하며 메서드 이름에 입력 값을 전달하면 되고 보통 메서드를 호출한다고 표현함
- add 메서드에 입력값을 넣어주면 메서드는 계산을 끝내고 결과를 반환함
- 메서드 호출이 끝나면 메서드 정의에 사용한 파라미터 변수는 물론 그 안에서 정의한 변수들도 모두 제거됨
- 메서드를 호출할 때는 메소드에 넘기는 값과 매개변수(파라미터)의 타입, 개수, 순서 모두 맞아야 함
- 즉, call(int b, String str)로 메서드의 파라미터가 정의되어있는데, 호출할 때의 아규먼트를 ("String", 10) 이렇게 정의된 파라미터의 순서와 다른 타입으로 입력하게되면 오류가 발생함
(5) 인수(argument)
- 메서드를 호출할 때 넘기는 값으로 영어로 Argument(아규먼트), 한글로 인수 또는 인자라고함
- 실무에서 모든 용어를 사용하며 파라미터와 헷갈리는 경우가 많음
- 인수라는 용어는 '인'과 '수'의 합성어로 들어가는 수라는 의미로 메서드 내부로 들어가는 값을 의미함(인자도 동일)
(6) 매개변수(Parameter)
- 메서드를 정의할 때 선언한 변수들로 매개변수, 파라미터라고함
- 메서드를 호출할 때 넘긴 인수들이 매개변수에 대입이되어 메서드의 코드를 수행할 때 사용됨
- 마찬가지로 모든 용어를 실무에서 사용함
- '매개'와 '변수'의 합성어로 중간에서 전달하는 변수라는 의미를 가지며 메서드 호출부와 메서드 내부 사이에서 값을 전달하는 역할을 하는 변수라는 뜻임
3) 메서드 정의
public static int add(int a, int b) {
// 메서드 본문, 실행 코드
}
제어자 반환타입 메서드이름(매개변수 목록) {
메서드 본문
}
(1) 제어자(Modifier)
- public, static과 같은 부분이며 제어자는 뒤에서 자세히 다룰 예정임
- 지금은 메서드를 사용할 때 public static 키워드를 입력해서 사용
(2) 반환 타입(Return Type)
- 메서드가 실행 된 후 반환하는 데이터 타입을 지정
- 메서드가 값을 반환하지 않는 경우 없다는 뜻의 void를 사용해야 하며 반환타입이 없을 경우 return문을 생략할 수 있음
(3) 메서드 이름(Method Name)
- 메서드의 이름으로 메서드를 호출하는데 사용됨
- 메서드를 정의할 때 수행되는 기능에 대한 정의를 직접 작성해야함
(4) 매개변수(Parameter)
- 입력 값으로 메서드 내부에서 사용할 수 있는 변수
- 매개변수는 옵션이기에 입력값이 필요 없는 메서드인 경우 매개변수를 지정하지 않아도되며, 매개변수가 없는 메서드를 호출할 때에도 아규먼트의 값이 없이 메서드 이름만으로 메서드를 호출함
(5) 메서드 본문(Method Body)
- 실제 메서드의 코드가 위치함
- 중괄호{ } 사이에 메서드가 수행할 코드를 작성함
(6) void와 return 생략
- 모든 메서드는 항상 return을 호출해야하며 return을 만나야 해당 메서드가 종료됨
- 그러나 반환 타입이 void인 경우에는 예외로 return문을 생략할 수 있으며 자바가 알아서 자동으로 컴파일 시점에 return문을 마지막 줄에 넣어줌
4) 반환 타입
(1) 반환 타입이 있으면 반드시 값을 반환해야 함
- 반환 타입이 있는 메서드는 반드시 return을 사용해서 값을 반환해야 하며 특히 조건문과 함께 사용할 때 주의해야함
- 아래의 메서드에서 if문에만 return문을 작성하면 if문의 조건을 만족하지 않을 때에 실행할 return문이 없기 때문에 return문을 누락했다는 컴파일 오류가 발생함(missing return statement)
- 메서드에 조건문을 사용할 때는 모든 조건에 return문이 들어가도록 작성해주어야 함
public static void main(String[] args) {
boolean result = add(2);
System.out.println("result = " + result);
}
public static boolean add(int i) {
if (i % 2 == 1) {
return true;
} else {
return false;
}
}
(2) return문을 만나면 그 즉시 메서드를 빠져나감
- 메서드는 return문을 만나면 바로 빠져나가기에 아래의 코드에서 checkAge(10)으로 메서드를 호출하면 미성년자는 출입이 불가능하다는 메시지를 띄우고 return문을 만난 후 바로 메서드가 종료됨
- 만약 return문이 없다면 마치 switch문에서 break문을 만나지않은 case문처럼 아래의 코드들을 전부 수행하게 됨
- 마지막줄의 출력문에서 return문이 없어도 되는이유는, 반환타입이 void이기 때문에 return을 생략할 수 있기 때문에 오류가 없이 프로그램이 수행됨
public static void main(String[] args) {
checkAge(10);
checkAge(20);
}
public static void checkAge(int age) {
if (age < 18) {
System.out.println(age + "살, 미성년자는 출입이 불가능합니다.");
return;
}
System.out.println(age + "살, 입장하세요.");
}
(3) 반환 값 무시
- 반환 타입이 있는 메서드를 호출했는데 만약 반환 값이 필요없다면 사용하지 않아도 됨
- 대부분은 반환타입이 있는 경우는 해당 결과를 사용하겠지만 반환값이 필요 없는경우에는 억지로 반환값을 사용할 필요없이 메서드 호출만 해도 오류 발생없이 사용할 수 있음
public static boolean add(int i) {
// 코드 생략
}
add(5); // 반환값이 있는 메서드도 반환값을 사용하지 않아도 됨
5) 메서드 호출과 값 전달
(1) 변수와 값 복사
- 자바는 항상 변수의 값을 복사해서 대입한다는 대원칙을 반드시 이해해야함
- 그러면 아무리 복잡한 상황에서도 코드를 단순하게 이해할 수 있음
- 아래의 예제를 실행해보면 num1에는 5가, num2에는 num1의 값을, num2에 다시 10을 입력하는 코드를 작성하면 num1은 5, num2는 10의 값이 출력됨
- 여기서 값을 복사해서 대입하는 부분은 int num2 = num1; 이부분인데, num1에 있는 값 5를 복사해서 num2에 입력한 것임
- 복사해서 표현한다고 한 이유는 num1의 값을 읽어도 num1에 있는 기존 값이 유지되고 새로운 값이 num2에 들어가기 때문에 마치 num1의 값이 num2에 복사가 된 것 같기 때문임
- num1이라는 변수 자체가 num2에 들어가는 것이 아니라 num1에 들어있는 값을 읽고 복사해서 num2에 입력하는 것처럼 동작함
- 즉, 간단하게 num1에 있는 값을 num2에 대입한다고 표현하는데 실제로는 그 값을 복사해서 대입한다고 이해해야함
public static void main(String[] args) {
int num1 = 5;
int num2 = num1;
num2 = 10;
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
/* 출력결과
num1 = 5
num2 = 10
*/
(2) 메서드 호출과 값 복사1
- 위와 같은 상황은 쉽게 이해가 될 것인데 메서드를 호출했을 때의 상황을 한번 생각해보면 조금 헷갈릴 수 있다
- 아래와 같이 코드가 구성되어있을 때 1번도 5, 2번도 5가 출력될 것이고, 3번은 *2가 실행되었기 때문에 10으로 출력될 것임
- 만약 4번을 10으로 출력될 것이라고 생각했다면, 항상 변수는 복사해서 값을 전달한다는 대원칙을 생각해야함
- 즉, changeNumber(num1)로 인수로 전달이 될때 num1 자체가 전달이 된것이 아니라 복사해서 전달이 되었기 때문에 메서드 내부에서는 값이 변경이 되었지만 메서드 밖에서의 num1은 그대로 변경되지 않았으므로 num1은 그대로 5가 출력됨
- 자바는 항상 값을 복사해서 전달하기 때문에 num2의 값을 바꾸더라고 num1에는 영향을 주지 않음
public static void main(String[] args) {
int num1 = 5;
System.out.println("1. changeNumber 호출 전, num1: " + num1);
changeNumber(num1);
System.out.println("4. changeNumber 호출 후, num1: " + num1);
}
// 파라미터의 값을 2배 곱하는 메서드
public static void changeNumber(int num2) {
System.out.println("2. changeNumber 변경 전, num2: " + num2);
num2 = num2 * 2;
System.out.println("3. changeNumber 변경 후, num2: " + num2);
}
(3) 메서드 호출과 값 복사2
- 자바 프로그램이 실행되는 main()도 메서드임
- 지금처럼 main메서드에서 선언되어 사용된 변수 number와 changeNumber에서 사용된 변수의 이름이 number로 이름이 같다고 해서 같은 변수라고 이해하면 안됨
- 각 메서드에서 선언된 변수는 지역변수이기에 앞에서 배운 스코프 범위가 벗어나면 해당 변수는 제거가 되기 때문에 이름만 같을 뿐 각 메서드의 선언된 변수는 각각의 변수임
- 그렇기 때문에 결과는 메서드 호출과 값 복사1과 동일하게 4번도 5가 출력이 됨
public static void main(String[] args) {
int number = 5;
System.out.println("1. changeNumber 호출 전, num1: " + number);
changeNumber(number);
System.out.println("4. changeNumber 호출 후, num1: " + number);
}
// 파라미터의 값을 2배 곱하는 메서드
public static void changeNumber(int number) {
System.out.println("2. changeNumber 변경 전, num2: " + number);
number = number * 2;
System.out.println("3. changeNumber 변경 후, num2: " + number);
}
(4) 메서드 호출과 값 반환받기
- 메서드를 사용해서 값을 변경하려면 메서드의 호출 결과를 받환 받아서 사용해야함
- 정의한 메서드를 반환타입을 지정하고, 메서드를 호출할때 결과를 받을 변수를 지정해주면 됨
- num1의 값을 변경하고 싶다면 아래처럼 메서드의 연산결과를 num1의 변수에 전달해주면 num1이 값이 변경되게 됨
public static void main(String[] args) {
int num1 = 5;
System.out.println("changeNumber 호출 전, num1: " + num1);
num1 = changeNumber(num1);
System.out.println("changeNumber 호출 후, num1: " + num1);
}
// 파라미터의 값을 2배 곱하는 메서드
public static int changeNumber(int num2) {
num2 = num2 * 2;
return num2;
}
(5) 꼭 기억하자!
- 자바는 항상 변수의 값을 복사해서 대입함
- 뒤에서 참조형이라는 것을 학습하는데 이때도 똑같이 적용되며, 참조형 변수에 있는 값인 참조값(주소값)을 복사하는 것임
6) 메서드와 형변환
(1) 명시적 형변환
- 메서드를 호출하는데 인자와 매개변수의 타입이 맞지않을 때에는 명시적으로 형변환을 해주면 됨
- 메서드의 파라미터 타입은 int인데 전달하는 인수의 타입이 double이면 컴파일 오류인 타입 오류가 발생함
- 그럼에도 강제로 메서드 호출이 꼭 필요하다면 변수에 (int)처럼 변환하고자 하는 타입을 명시적으로 작성해주면 형변환이 가능함(앞서 설명한 형변환의 원리와 동일함)
public static void main(String[] args) {
double number = 1.5;
//printNumber(number); // double을 int형에 대입하므로 컴파일 오류
printNumber((int) number); // 명시적 형변환을 사용해 double을 int로 변환
}
public static void printNumber(int n) {
System.out.println("숫자: " + n);
}
(2) 자동 형변환
- 기존에 배웠던 자동 형변환의 원리와 동일하게 적용되므로 큰 그릇은 작은 그릇을 담을 수 있다는 원리로 접근하면 이해가 쉬움
- 인수의 타입은 int이고 메서드의 파라미터의 타입은 double이기에 double타입은 int보다 훨씬 범위가 크기 때문에 자동으로 형변환이 적용되어 오류없이 메서드가 실행됨
- 즉, 메서드를 호출할 때는 전달하는 인수의 타입과 매개변수의 타입이 맞아야하는 것이 기본 원칙이나 타입이 달라도 위와같은 상황에서는 자동형변환이 가능하며 정상적으로 코드가 수행됨
public static void main(String[] args) {
int number = 100;
printNumber(number); // int에서 double로 자동 형변환
}
public static void printNumber(double n) {
System.out.println("숫자: " + n);
}
7) 메서드 오버로딩
(1) 설명
- 자바는 메서드의 이름뿐 아니라 매개변수 정보를 함께 사용해서 메서드를 구분하기 때문에 이름이 같고 매개변수가 다른 메서드를 정의할 수 있는데 이를 메서드 오버로딩(Overloading)이라함
- 오버로딩은 같은 이름의 메서드를 여러개 정의했다고 이해하면됨
- 해당 기능은 동일한 기능을 수행하는 메서드인데 파라미터의 개수나 타입만 다르게 입력받고 싶을때가 있을 때 동일한 이름으로 메서드를 정의할 수 있는 기능임
- 만약 이런 기능이 없다면 지금과 같은 상황에서 메서드를 계속 새롭게 정의해줘야 하여 상당히 번거로워지는 문제를 해결해줌
(2) 오버로딩 규칙
- 메서드의 이름이 같아도 매개변수의 타입 및 순서가 다르면 오버로딩을 할 수 있음
- 반환 타입은 인정하지 않음
- 아래의 예제를 보면 오버로딩의 규칙을 이해할 수 있음
// 오버로딩 실패
int add(int a, int b)
double add(int a, int b)
// 오버로딩 적용
add(int a, int b)
add(int a, int b, int c)
add(double a, double b)
** 참고
- 메서드 시그니처(method signature)
- 메서드 시그니처 = 메서드 이름 + 매개변수 타입(순서)
- 자바에서 메서드를 구분할 수 있는 고유한 식별자나 서명을 뜻하며, 메서드의 이름과 매개변수 타입(순서 포함)을 합쳐서 메서드 시그니처라고함
- 즉, 메서드를 구분할 수 있는 기준이며 자바 입장에서는 각각의 메서드를 고유하게 구분할 수 있어야 어떤 메서드를 호출 할 지 결정할 수 있기 때문에 이러한 방식으로 메서드를 구분함
- 메서드 이름이 같아도 메서드 시그니처가 다르면 다른 메서드로 간주하고 반환타입은 시그니처에 포함되지 않기 때문에 오버로딩의 조건에서도 반환타입이 빠져있음
(3) 예제
- add의 메서드를 매개변수의 개수, 매개변수의 타입을 바꾸면서 각각 메서드 오버로딩을 적용할 수 있음
- 여기에서 메서드 오버로딩 1의 메서드를 제거하면 에러가 발생하는 것이 아니라 메서드 오버로딩 2가 실행되며 자동형변환이 동작함
- 즉, 본인의 타입에 최대한 맞는 메서드를 찾아서 실행을 하고 그래도 없으면 자동 형변환이 가능한 타입의 메서드를 찾아서 실행하도록 동작함
public static void main(String[] args) {
System.out.println(add(5, 10)); // 기본 add 메서드 호출
System.out.println(add(5, 10, 10)); // 메서드 오버로딩 1, 호출
System.out.println(add(5, 10, 10.5)); // 메서드 오버로딩 2, 호출
}
public static int add(int a, int b) {
System.out.println("1번 호출");
return a + b;
}
// 메서드 오버로딩 1
public static int add(int a, int b, int c) {
System.out.println("2번 호출");
return a + b + c;
}
// 메서드 오버로딩 2
public static double add(int a, int b, double c) {
System.out.println("2번 호출");
return a + b + c;
}
8) 문제와 풀이
(1-1) 단순 리펙토링
- 아래의 코드를 메서드를 사용해서 리펙토링
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = 3;
int sum = a + b + c;
double average = sum / 3.0;
System.out.println("평균값: " + average);
int x = 15;
int y = 25;
int z = 35;
sum = x + y + x;
average = sum / 3.0;
System.out.println("평균값: " + average);
}
(1-2) 리펙토링
- 평균을 구하는 기능을 메서드로 정의
- main인 메서드는 메서드호출로 구해진 평균에 대한 기능을 출력만하는 코드로 실제 기능과 출력을 분리
public static void main(String[] args) {
System.out.println("평균값: " + average(1, 2, 3));
System.out.println("평균값: " + average(15, 25, 35));
}
private static double average(int a, int b, int c) {
int sum = a + b + c;
return sum / 3.0;
}
(2-1) 입출금 리펙토링
- 입금(deposit)과, 출금(withdraw)을 메서드로 만들어서 리펙토링
public static void main(String[] args) {
int balance = 10000;
// 입금 1000
int depositAmount = 1000;
balance += depositAmount;
System.out.println(depositAmount + "원을 입금하였습니다. 현재 잔액: " + balance + "원");
// 출금 2000
int withdrawAmount = 2000;
if (balance >= withdrawAmount) {
balance -= withdrawAmount;
System.out.println(withdrawAmount + "원을 출금하였습니다. 현재 잔액: " + balance + "원");
} else {
System.out.println(withdrawAmount + "원을 출금하려 했으나 잔액이 부족합니다.");
}
System.out.println("최종 잔액: " + balance + "원");
}
(2-2) 리펙토링
- 입금을처리하는 기능과 입금 시 출력하는 멘트를 묶어서 메서드로 정의
- 출금을 처리하는 기능 과 출금 시 출력하는 멘트를 묶어서 메서드로 정의
- main메소드에서는 입, 출금 메서드를 호출하여 최종 잔액을 출력하는 코드만 남겨둠
public static void main(String[] args) {
int balance = 10000;
balance = deposit(1000, balance); // 입금 1000원
balance = withdraw(2000, balance); // 출급 2000원
System.out.println("최종 잔액: " + balance + "원");
}
public static int withdraw(int amount, int balance) {
if (balance >= amount) {
balance -= amount;
System.out.println(amount + "원을 출금하였습니다. 현재 잔액: " + balance + "원");
} else {
System.out.println(amount + "원을 출금하려 했으나 잔액이 부족합니다.");
}
return balance;
}
public static int deposit(int amount, int balance) {
balance += amount;
System.out.println(amount + "원을 입금하였습니다. 현재 잔액: " + balance + "원");
return balance;
}
(3-1) 은행 계좌 입출금 프로그램
- 사용자로부터 계속 입력을 받아 입금과 출금을 반복 수행하는 프로그램을 작성
- 간단한 메뉴를 표시하여 어떤 동작을 수행해야 할지 선택할 수 있도록 작성
- 출금 시 잔액이 부족하다면 "x원을 출금하려 했으나 잔액이 부족합니다"라고 출력
(3-2) 실행 예시
---------------------------------
1.입금 | 2.출금 | 3.잔액 확인 | 4.종료
---------------------------------
선택: 1
입금액을 입력하세요: 10000
10000원을 입금하였습니다. 현재 잔액: 10000원
---------------------------------
1.입금 | 2.출금 | 3.잔액 확인 | 4.종료
---------------------------------
선택: 2
출금액을 입력하세요: 8000
8000원을 출금하였습니다. 현재 잔액: 2000원
---------------------------------
1.입금 | 2.출금 | 3.잔액 확인 | 4.종료
---------------------------------
선택: 2
출금액을 입력하세요: 3000
3000원을 출금하려 했으나 잔액이 부족합니다.
---------------------------------
1.입금 | 2.출금 | 3.잔액 확인 | 4.종료
---------------------------------
선택: 3
현재 잔액: 2000원
---------------------------------
1.입금 | 2.출금 | 3.잔액 확인 | 4.종료
---------------------------------
선택: 4
시스템을 종료합니다.
(3-3) 코드 작성
- 입금과 출금을 메서드로 분리해서 1번과 2번이 입력이 되었을 때 각 메서드가 동작하도록 호출
- while문의 무한반복문으로 프로그램을 구동하고 단순히 입력값에 따라 동작하는 방식이 바뀌므로 switch문을 사용해 기능을 수행하도록 작성
- switch문을 수행 후 다음 코드를 수행하지 않기 위해서는 break;문으로 빠져나가야 하기에 각 case마다 break;를 입력해주고 4번은 프로그램을 종료해야하므로 return문을 입력하여 main 메서드가 종료되며 프로그램이 종료됨
public static void main(String[] args) {
int balance = 0;
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("---------------------------------");
System.out.println("1.입금 | 2.출금 | 3.잔액 확인 | 4.종료");
System.out.println("---------------------------------");
System.out.print("선택: ");
int option = scanner.nextInt();
int amount;
switch (option) {
case 1:
System.out.print("입금액을 입력하세요: ");
amount = scanner.nextInt();
balance = deposit(amount, balance);
break;
case 2:
System.out.print("출금액을 입력하세요: ");
amount = scanner.nextInt();
balance = withdraw(amount, balance);
break;
case 3:
System.out.println("현재 잔액: " + balance);
break;
case 4:
System.out.println("시스템을 종료합니다");
return;
default:
System.out.println("잘못된 옵션을 선택하셨습니다. 1,2,3,4 중 선택해주세요");
}
}
}
public static int withdraw(int amount, int balance) {
if (balance >= amount) {
balance -= amount;
System.out.println(amount + "원을 출금하였습니다. 현재 잔액: " + balance + "원");
} else {
System.out.println(amount + "원을 출금하려 했으나 잔액이 부족합니다.");
}
return balance;
}
public static int deposit(int amount, int balance) {
balance += amount;
System.out.println(amount + "원을 입금하였습니다. 현재 잔액: " + balance + "원");
return balance;
}
9) 정리
(1) 변수명 vs 메서드명
- 변수 이름은 일반적으로 명사를 사용함
- 메서드는 무언가 동작을 하는데 사용하기 때문에 일반적으로 동사로 시작함
- 변수 이름과 메서드 이름을 명명할 때의 규칙은 동일함
(2) 메서드 사용의 장점
- 특정 기능을 캡슐화하기 때문에 필요할 때마다 그 기능을 다시 작성할 필요 없이 해당 메서드를 호출함으로써 코드를 재사용할 수 있음
- 이름이 부여된 메서드는 코드가 수행하는 작업을 명확하게 나타내기 때문에 코드를 읽는 사람에게 추가적인 문맥을 제공하여 코드의 가독성을 높일 수 있고, 변수나 메서드의 이름이 중요한 이유중의 하나임
- 큰 프로그램을 작은, 관리 가능한 부분으로 나누어 모듈화 할 수 있기 때문에 코드의 가독성을 향상시키고 디버깅을 쉽게 만듦
- 코드의 특정 부분에서 문제가 발생하거나 업데이트가 필요한 경우 해당 메서드만 수정하면 되기 때문에 전체 코드 베이스에 영향을 주지않고 변경사항을 적용할 수 있어 코드 유지 관리에 장점이 있음
- 잘 설계된 메서드는 다른 프로그램이나 프로젝트에서도 재사용할 수 있고 새로운 기능을 추가하거나 기존 기능을 확장하는 데 유용하기에 재사용성과 확장성이 좋음
- 메서드를 사용하는 곳에서는 메서드가 어떻게 구현었고 어떻게 동작하는지 몰라도 되기 때문에 프로그램의 다른 부분에서는 복잡한 내부 작업에 대해 알 필요가 없이 추상화된 메서드를 사용할 수 있어 분업이 가능해짐
- 개별 메서드는 독립적으로 테스트하고 디버그할 수 있기 때문에 코드의 문제를 신속하게 찾고 수정하는데 도움이되어 테스트와 디버깅을 할때 매우 용이함
** 메서드는 효율적이고 유지 보수가 가능한 코드를 작성하는데 매우 중요한 도구임
'인프런 - 실전 자바 로드맵 > 자바 입문 - 자바 첫걸음(무료)' 카테고리의 다른 글
코드로 시작하는 자바 첫걸음3, 스코프 및 형변환, 훈련 (0) | 2024.11.01 |
---|---|
코드로 시작하는 자바 첫걸음2, 조건문, 반복문 (1) | 2024.11.01 |
코드로 시작하는 자바 첫걸음1, 자바란, 변수, 연산자 (9) | 2024.10.31 |