관리 메뉴

나구리의 개발공부기록

코드로 시작하는 자바 첫걸음4, 배열, 메서드 본문

인프런 - 실전 자바 로드맵/자바 입문 - 자바 첫걸음(무료)

코드로 시작하는 자바 첫걸음4, 배열, 메서드

소소한나구리 2024. 11. 2. 17:56

출처 : 인프런 - 자바 입문 - 코드로시작하는 자바 첫걸음 (무료) / 김영한님 


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) 메서드 사용의 장점

  • 특정 기능을 캡슐화하기 때문에 필요할 때마다 그 기능을 다시 작성할 필요 없이 해당 메서드를 호출함으로써 코드를 재사용할 수 있음
  • 이름이 부여된 메서드는 코드가 수행하는 작업을 명확하게 나타내기 때문에 코드를 읽는 사람에게 추가적인 문맥을 제공하여 코드의 가독성을 높일 수 있고, 변수나 메서드의 이름이 중요한 이유중의 하나임
  • 큰 프로그램을 작은, 관리 가능한 부분으로 나누어 모듈화 할 수 있기 때문에 코드의 가독성을 향상시키고 디버깅을 쉽게 만듦
  • 코드의 특정 부분에서 문제가 발생하거나 업데이트가 필요한 경우 해당 메서드만 수정하면 되기 때문에 전체 코드 베이스에 영향을 주지않고 변경사항을 적용할 수 있어 코드 유지 관리에 장점이 있음
  • 잘 설계된 메서드는 다른 프로그램이나 프로젝트에서도 재사용할 수 있고 새로운 기능을 추가하거나 기존 기능을 확장하는 데 유용하기에 재사용성과 확장성이 좋음
  • 메서드를 사용하는 곳에서는 메서드가 어떻게 구현었고 어떻게 동작하는지 몰라도 되기 때문에 프로그램의 다른 부분에서는 복잡한 내부 작업에 대해 알 필요가 없이 추상화된 메서드를 사용할 수 있어 분업이 가능해짐
  • 개별 메서드는 독립적으로 테스트하고 디버그할 수 있기 때문에 코드의 문제를 신속하게 찾고 수정하는데 도움이되어 테스트와 디버깅을 할때 매우 용이

** 메서드는 효율적이고 유지 보수가 가능한 코드를 작성하는데 매우 중요한 도구임