평소에 프로젝트를 할 때나 코딩 테스트를 풀 때, 아직 자바 문법에 대해 공백이 있다는 것을 깨닫게 되었다.
따라서 비어있는 개념을 채우기 위해 기본서를 2회독을 할지, 강의를 새로 들어볼지 고민하다가 마침 영한님의 자바 로드맵이 오픈되었다는 소식을 듣고!
강의로 결정하여 다음과 같은 포맷으로 학습을 진행하게 되었다.
앗
싶었던 부분 위주로 강의로 1회독이번 주 글감은 메서드이다!
출처는 김영한의 자바 입문 - 코드로 시작하는 자바 첫걸음 (섹션 9. 메서드)이다.
자바에선 함수(function)를 메서드(method)라는 명칭으로 부른다.
메서드 선언은 다음과 같다. 간단하게 훑어보자.
public static int add(int a, int b)
1. 접근 제어자(Modifier), public
으로 호출할 경우 다른 클래스에서 호출 가능
2. static
, 객체를 생성하지 않고 호출할 수 있는 정적 메서드
3. 반환 타입(Return Type), 메서드가 실행된 후 반환하는 데이터의 타입이며 int
로 선언할 경우 반환되는 데이터의 타입은 int
형이다.
void
를 사용add(1, 2)
add()
반환 타입이 지정된 메서드의 경우, 반드시 return
이 필요하다.
return
을 작성하지 않아 종종 마주했던 오류(Missing return statement.
)가 이 개념에서 기인한 것자바는 항상 변수의 값을 복사해서 대입한다. 즉, Call by value임을 기억
그렇다면, 변수의 값을 복사해서 대입
한다는 개념을 기반으로 다음 코드를 풀어보면?
public class MethodValue1 {
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);
}
public static void changeNumber(int num2) {
System.out.println("2. changeNumber 변경 전, num2: " + num2);
num2 = num2 * 2;
System.out.println("3. changeNumber 변경 후, num2: " + num2);
}
}
답은 다음과 같다.
num1 = 5
num1 = 5
num1 = 10
num1 = 5
따라서 num2
의 값을 바꾸더라도 num1
에는 영향을 주지 않는다.
메서드를 호출할 때는 전달하는 인수의 타입
과 매개변수의 타입
이 맞아야 한다.
하지만, 타입이 달라도 자동 형변환이 가능한 경우에는 호출할 수 있다.
다음 코드를 보자.
public class MethodCasting1 {
public static void main(String[] args) {
double number = 1.5;
printNumber(number); // double을 int형에 대입하므로 컴파일 오류
}
public static void printNumber(int n) {
System.out.println("숫자: " + n);
}
}
변수의 데이터 타입은 double
타입이지만, 메서드의 매개변수가 int
타입인 경우 다음과 같은 오류가 발생한다.
java: incompatible types: possible lossy conversion from double to int
따라서 이럴 경우는 명시적 형변환
을 사용한다.
printNumber((int) number);
단, 반대의 경우(변수의 데이터 타입은 int
, 메서드의 매개변수 타입은 double
)는 자동 형변환이 이루어진다.
왜냐하면 int(4 byte)보다 큰 double(8 byte)에 int 값을 담는 것은 문제가 되지 않기 때문이다.
자바는 메서드의 이름
뿐만 아니라 매개변수 정보
를 함께 사용해서 메서드를 구분한다.
매개변수가 다른 메서드
를 정의할 수 있다.이러한 메서드를 여러 개 정의하는 개념을 메서드 오버로딩
이라고 한다.
과하게 물건을 담았다
라는 느낌?이름이 같아도 매개변수의 타입 및 순서가 다르면 오버로딩
은 할 수 있다. 하지만, 반환 타입
이 다른 메서드는 오버로딩을 할 수 없다.
다음 예시를 보자.
add(int a, int b)
add(int a, int b, int c)
add(double a, double b)
int add(int a, int b)
double add(int a, int b)
차례대로 오버로딩 성공
과 실패
의 예시이다.
첫 번째 케이스는 이름은 동일하지만, 매개변수의 타입과 개수
가 다르다.
하지만, 두 번째 케이스는 반환 타입
은 다르지만 매개변수의 정보
가 동일하므로 오버로딩이 될 수 없는 것이다.
메서드 시그니처 = 메서드 이름 + 매개변수 타입(순서)
메서드 시그니처 개념에서 엿볼 수 있듯이,
메서드 이름이 같아도, 메서드 시그니처가 다르면 다른 메서드로 간주한다. 그리고 이때, 반환 타입은 시그니처에 포함되지 않는다. 꼭 기억하자!
갯수
가 다른 오버로딩public static int add(int a, int b)
public static int add(int a, int b, int c)
2-1. 매개변수의 타입
이 다른 오버로딩
public static void myMethod(int a, double b)
public static void myMethod(double a, int b)
2-2. 매개변수의 타입이 다른 오버로딩의 추가 케이스
public static int add(int a, int b)
public static double add(double a, double b)
사실상 이 포스팅의 핵심!
스스로 코드를 작성해보면서 정말 코딩 경험이 적다는걸 체감할 수 있는 부분들이 있었다.
한 번 살펴보자.
package method;
// TODO 문제 1. 평균값 리팩토링 : "메서드를 사용하도록 리팩토링 해보자."
// TODO 1. a,b,c가 사용할 메서드를 만들자.
public class Method {
public static void main(String[] args) {
int a = 1; // 입력으로 값을 할당과 동시에 메서드를 호출하도록 리팩토링
int b = 2;
int c = 3;
double firstResult = average(a, b, c); // 메서드 값 반환과 동시에 값을 호출하도록 리팩토링
System.out.println("평균값: " + firstResult);
int x = 15;
int y = 25;
int z = 35;
double secondResult = average(x, y, z);
System.out.println("평균값: " + secondResult);
}
private static double average(int a, int b, int c) {
int sum = a + b + c;
return sum / 3.0;
}
}
키워드는 특정 숫자
, 같은 메시지
, 반복
package method;
// TODO 1. 각 for문 내부에 sout가 있음을 확인
// TODO 2. 따라서 for문에 공통적으로 넘겨줄 String을 메서드를 단 한 번만 반복함으로써 수행
public class Method {
public static void main(String[] args) {
repeat("Hello, world!");
}
private static void repeat(String message) {
for (int i = 0; i < 3; i ++) {
System.out.println(message);
}
for (int i = 0; i < 5; i ++ ){
System.out.println(message);
}
for (int i = 0; i < 7; i ++) {
System.out.println(message);
}
}
}
처음으로 내가 작성한 코드와 정답 코드가 일치해서 소소하게 기분이 좋았던 문제!
package method;
// TODO 1. 공통된 변수 balance를 main()에서 생성
// TODO 2. 입금 및 출금에 따른 메서드를 각각 생성
// TODO 3. 입금 및 출금에 따른 메서드 값을 공통 변수인 balance에 업데이트
// TODO 4. 각 로직에 따른 출력은 해당 메서드에서, 합 출력은 main()에서 진행
public class Method {
public static void main(String[] args) {
int balance = 10000;
balance = deposit(1000, balance);
balance = withdraw(2000, balance);
System.out.println("최종 잔액: " + balance + "원");
}
private static int deposit(int depositAmount, int balance) {
balance += depositAmount;
System.out.println(depositAmount + "원을 입금하였습니다. 현재 잔액: " + balance + "원");
return balance;
}
private static int withdraw(int withdrawAmount, int balance) {
if (balance >= withdrawAmount) {
balance -= withdrawAmount;
System.out.println(withdrawAmount + "원을 출금하였습니다. 현재 잔액: " + balance + "원");
} else {
System.out.println(withdrawAmount + "원을 출금하려 했으나 잔액이 부족합니다.");
}
return balance;
}
}
이 문제는 내가 요구 사항
을 제대로 파악하지 않고 풀어서 애매한 풀이를 작성했던 코드였다.
인프런 게시판에 질문을 올려서 아래와 같은 답변을 받고, 잠깐의 반성을 한 후 수정해서 다시 푼 문제였다.
package method;
// TODO 1. 공통 출력 부분을 start()라는 메서드로 따로 빼서 반복 호출하여 수행
// TODO 2. 입금 출금 등 요구 사항에 따라 구현이 다른 부분을 메서드로 추출
// TODO 3. 이때, 선택에 따른 숫자값과 입금액 및 출금액 등을 파라미터로 전달
// TODO 4. 잔액 확인 및 종료는 별다른 연산이 필요 없으므로 void로 설계
// TODO 5. 이때, 종료는 종료라는 책임이 해당 메서드에 배당이 되어야 하므로 메서드 내부에서 System.exit(0);을 수행
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Method {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str;
int balance = 0;
// TODO 1. "사용자의 입력이 계속 들어오는" + int choice = scanner.nextInt(); 및 switch(choice) { case 1: ..
// TODO -> 한 줄에 하나씩 값이 들어옴
// TODO 2. 어차피 "4"라는 값이 들어올 경우, 시스템은 반드시 종료됨 -> 따라서 별다른 조건 없이 무조건 while()을 실행하는 방향으로 구현
while (true) {
int choice = Integer.parseInt(br.readLine());
int money;
if (choice == 1) {
money = Integer.parseInt(br.readLine());
balance = deposit(choice, balance, money);
} else if (choice == 2) {
money = Integer.parseInt(br.readLine());
balance = withDraw(choice, balance, money);
} else if (choice == 3) {
check(choice, balance);
} else {
exit(choice);
}
}
}
private static int depositOrWithdraw(StringTokenizer st) {
return Integer.parseInt(st.nextToken());
}
private static void start() {
System.out.println("---------------------------------");
System.out.println("1.입금 | 2.출금 | 3.잔액 확인 | 4.종료");
System.out.println("---------------------------------");
}
private static int deposit(int index, int balance, int depositAmount) {
start();
System.out.println("선택: " + index);
balance += depositAmount;
System.out.println("입금액을 입력하세요: " + balance);
System.out.println(balance + "원을 입금하였습니다. 현재 잔액: " + balance + "원");
return balance;
}
private static int withDraw(int index, int balance, int withdramAmount) {
start();
System.out.println("선택: " + index);
if (balance >= withdramAmount) {
balance -= withdramAmount;
System.out.println("출금액을 입력하세요: " + withdramAmount);
System.out.println(balance + "원을 출금하였습니다. 현재 잔액: " + balance + "원");
} else {
System.out.println(withdramAmount + "원을 출금하려 했으나 잔액이 부족합니다.");
}
return balance;
}
private static void check(int index, int balance) {
start();
System.out.println("선택: " + index);
System.out.println("현재 잔액: " + balance + "원");
}
private static void exit(int index) {
start();
System.out.println("선택: " + index);
System.out.println("시스템을 종료합니다.");
System.exit(0);
}
}
특히, 마지막 문제는 같은 스터디원 분이 다음과 같은 피드백을 주셔서 생각해볼 거리들이 좀 있었다.
1. 각 메서드별로 존재하는 System.out.println("선택: " + index);는 main() 내부의 if-else if-else 분기에 따라 실행하도록 두는 건?
2. start() 또한 main() - if 분기점 이전에 실행하도록 두는건?
3. 고런데 System.out.println("선택: " + index);이 필요할까?
이번 주 블로그 원정대 포스팅도 끝~
캬캬 저는 항상 문제 슥 보고 바로 풀이 보는데 역시 성실의 아이콘 BB