그런데 자바에서는 프로그래밍을 할 때 반드시 메소드를 사용할 수밖에 없다. 어떤 프로그램을 만들더라도, 클래스 안의 메소드 안쪽에다가 하고 싶은 것을 적어야만 함.
메서드는 다음과 같은 형식을 따른다.
예시) public static void main(String[] args) {
}
↑(메서드를 정의하려면 메서드이름() 이후에 중괄호를 적고 중괄호 안에 메서드가 실행할 내용을 적으면 된다. )
아무튼 저 위의 코드에서는 main이 메소드의 이름인데, main이라는 것은 자바와 개발자 사이에 맺어진 약속이다. (약속: 하고 싶은 작업이 있으면 main이라는 이름을 가진 메소드의 본문에다가 그 작업에 해당하는 코드를 위치시켜라.)
--->자바가 실행될 때 바로 main이라는 메소드가 호출되도록 만들어져 있다.
# 다른 특징들:
하나의 클래스에 메소드가 두 개 이상 있을 수 있다.
메소드 안쪽의 내용 부분에서, 다른 메소드를 호출할 수도 있다.
메소드이름(); 이렇게 적으면 그 메소드를 호출하는 것이고, 중괄호 안쪽 본문에 해당하는 내용이 실행된다.
메소드는 반복해서 여러 번 호출할 수 있다.
메소드의 장점: "재활용"을 잘 할 수 있게 해 줌. 유지 보수가 용이함. 코드 양이 줄어듦.
코드를 읽는 요령:
메소드의 정의 부분을 먼저 보지 말고, 안의 내용을 먼저 봐라.
그리고 입력값, 출력값을 확인하는 것이 메소드를 이해하는 데 많은 도움이 된다.
<메소드의 입력과 출력>
메서드의 입력/ 출력값은 있을 수도 없을 수도 있다.
그런데 이 입력값, 출력값을 잘 설정하면 메서드의 존재 이유와도 같은 재사용성을 높일 수 있기 때문에 아주 중요하다.
내용은 거의 동일한데 실행할 때마다 살짝 다른 결과를 도출해야 하는 경우라고 하자.
이런 약간의 변경사항 때문에 우리는 메서드의 동작 방식을 살짝 바꾼 새로운 메서드를 만어야 할 수 있다.
그런데 이런 유사한 메서드를 100개, 200개 만들어야 한다면 그건 메서드가 좋은 부품으로서의 기능을 하지 못하는 것이다. 한마디로 재활용성이 떨어지는 메서드가 된다.
하지만 만약,
어떤 값을 입력했을 때 그 입력값에 따라서 출력값이 달라진다면 ,
비슷한 메소드를 굳이 만들지 않아도 입력값을 바꿈으로서 출력값에 변화를 줄 수 있다.
입출력값을 잘 설정하는 것은 메서드의 재활용성을 높이는 좋은 방법이다.
1) 입력값
매개변수, 인수를 통해서 입력을 한다.
ex)
매개변수가 int limit인 메서드
의 내용이 { limit 값보다 작을 때까지 반복되는 while문 }인 경우:
거기에 어떤 인자를 넣느냐에 따라서 출력값이 달라진다. 인자가 4면 3번만 돌아가고 인자가 10이면 9번 돌아가는 것. +메소드의 이름(Numbering) 뒤에, 괄호 안에 limit이라는 변수를 저장했고, int를 앞에 적어줌으로써 정수만 받겠다는 의미를 담음.
다른 메서드에서 Numbering 메소드를 호출하기 위해서 Numbering(9)이런 식으로 쓰면
이 괄호 안에 담긴 9가 int litmit에 담기게 되는 것이다.
Numbering메서드는 변수 안에 괄호 안의 숫자를 넣고 실행된다. limit이 숫자로 치환돼서 그거에 맞게 로직이 돌아가는 것이다.
만약에 메서드 정의에서 괄호 안에 아무것도 없으면? 그럼 메소드의 내용(중괄호 안)에다가 이제 int limit=5이런 식으로 쓰면 된다.
이렇게 했을 때의 문제는 메소드의 내용에 가변성이 늘어난다는 것이다. 지속적인 수정이 요구되어서 번거롭다.
반면 Numbering (int limit)이라고 정의했을 때는 고정적인 영역에 둘 수 있음. 그렇게 하고 메소드를 호출할 때마다 넘버링() 괄호 안에다가 전달할 값을 다르게 주면 된다.
가변성이 있는 영역을 아주아주 좁게 만든 것이다.
*참고
int limit이나 인수 5 나 둘 다, 입력값을 위한 도구이다. 그런데 int limit은 매개변수(parameter)라고 하고, 5 등은 인자(argument)라고 한다.
- 매개변수는 입력값을 저장하는 공간, 변수를 의미한다 (int a, int b 등) - 인수는 메서드를 호출할 때 전달하는 입력값 자체를 의미한다 (3, 4 등) (근데 흔하게 혼용되는 용어이긴 함)
암튼 메서드를 호출할 때마다 인자를 주고,
그 인자를 매개변수에다 대입해서 세팅할 수 있게 되면,
메소드의 재활용성이 높아진다.
→ 0부터 limit까지 출력하고 싶을 때는 그렇다. 그런데 만약 매가 지정한 값부터 ~ limit까지 출력하고 싶을 때는?
매개변수를 하나 더 늘리면 된다. numbering(int init, int limit); 처럼. 그리고 이 메소드를 호출할 때 numbering(3, 5)이런 식으로 인자를 넣어주면 된다. 3은 init에, 5는 limit에 들어감.
2) 출력값
return: 메소드가 동작된 결과를 메소드 밖으로 돌려준다는 뜻이다.
public static String numbering(int init, int limit);
numbering이라는 메소드는 입력값으로 1, 5를 받고 있다.
메소드 이름 앞에 붙은 String은, 그 메소드가 리턴할 값이 반드시 문자열이라는 것을 의미한다.
그래서 이 메소드를 호출할 때 result 앞에도 String을 붙여줘야 함.
# void는 무엇인가?
빈 공간. 공허. 이런 뜻을 가진 void가 메소드 이름 앞에 붙으면, 이 메소드는 return 값이 없다는 것을 의미한다.
{ 메서드 내용 중
String result = numbering (1, 5); 이런 게 있다.
result는 문자열을 담을 수 있는 변수이고
여기에다가 numbering이라는 메소드가 1, 5를 인자로 받았을 때 나오는 결과를 담으라는 뜻이다.
1과 5라는 값은, 메소드에서 각각 init과 limit에 담긴다. 그리고 String output = "" 이라는 빈 문자열을 정의한다.
while문이 실행되는 동안 i의 값이 아웃풋에 계속 더해진다. 1, 2, 3, 4 가 아웃풋에 순차적으로 붙음. 아웃풋은 숫자가 아니라 "문자열" 이니까 10이렇게 더해지는 게 아니라, 그냥 1234가 됨.
그리고 이 아웃풋을 return을 통해 메소드 바깥으로 내보내짐. }
이렇게 하면 바깥 main 메소드의 result 값에 1234가 담기게 되는 것이다.
이전에 살펴본 예제는 result라는 변수나 출력하는 코드 없이 끝났는데,
여기서는 result 값을 만들고, 화면에 출력(print)하는 내용도 바깥쪽으로 나와 있다.
대신에 메소드 안에는 return만 있음.
" 위와 같이 매번 입출력의 형식을 지정해야 한다는 점은 귀찮고 어려운 일일 수 있다. 특히 고급 프로그래밍 언어인 파이썬 등을 사용하던 사람들은 이렇게 고정되어있는 형태가 억압적이라고 생각할 수도 있다.
그러나 모든 일에는 장단이 있는 법이다. Java에서는 메소드가 리턴하는 값이 고정되어 있기 때문에, 메소드가 리턴하는 값이 스트링일지 아닐지 고민하지 않아도 된다는 장점이 있다. 다른 언어와 달리, 문자열을 리턴할지, 숫자를 리턴할지 체크하는 조건문 같은 것을 생략할 수 있다. 유연성을 버린 대신 예측가능성을 얻은 것. 이런 다면성을 인지하고 있어야 좋은 프로그래밍을 할 수 있다. "
3) 굳이 return값을 써야 하는 이유?
메소드의 부품으로서의 가치를 높이기 위해서이다.
메소드의 결과는 화면에 출력할 수도 있지만 이메일로 보내야 할 때도 있고 파일로 기록해야할 수도 있다.
메소드가 해야 할 일은 결과를 내는 것까지. return을 통해 결과를 밖으로 내보내고 나면, 내용이 같은데도 불구하고 출력 방법이 다르다는 이유로 각기 다른 여러 개의 메소드를 따로 만들 필요가 없다.
결국 메서드의 재사용성. 부품으로서의 가치를 극대화하기 위해서 return 기능을 사용하는 것이다.
그렇게 해서 만들어진 코드가 바로.....
public static void main(String[] args) {
String result = numbering(1, 5);
System.out.println(result);
}
여기서 보면 프린트하는 부분을 이메일로 보내는 부분으로, 또는 파일에 저장하는 부분으로 바꾸기가 참 쉽다.
메소드는 하나도 건드리지 않고도!
4) return의 특징
1. 리턴을 만나면 리턴값을 반환한 뒤 메소드가 종료된다.
2. 로직의 흐름에 따라서 리턴값은 한 메소드에 여러 개 있을 수 있다.
ex)
if - else if - else 문에서는 서로 다른 리턴값 중 어떤 한 개가 실행되는 시점에 메소드가 종료된다.
3. 리턴값 여러 개를 리턴하고 싶을 때는 배열을 써먹으면 된다.
배열은 메소드와 밀접한 관계를 갖는다.
입력값이나 출력값을 배열로 만들 수 있으므로 배열에 복수의 값을 담은 뒤에 한 번에 입력, 리턴하면 된다.
이럴 때는 메소드 정의를 이렇게 한다. public static String[ ] get members( ) {
클래스 내의 메서드는 필요할 때마다 쓰면 되는 일회용 기능 같은 것. 근데 인스턴스는 뭔가 일시적인 게 아니라, 새로운 복제본 그 자체의 맥락을 계속 이어갈 수 있다. 일회용으로 작업을 끝내면 되는 것들은 메서드나 변수를 클래스에 있는 그대로 이용하지만, 긴 맥락으로 작업해야 하는 경우에는 클래스를 직접 사용하지 않고, 복제본을 만들어서 이용하는 것이다.
선언하는 방법:
" 클래스 이름" "인스턴스 이름"=new "클래스 이름"();
예시:
Printp1=newPrint();
classPrint{
publicStringdelimiter="";
publicvoidA() {
System.out.println(delimiter);
System.out.println("A");
System.out.println("A");
}
publicvoidB() {
System.out.println(delimiter);
System.out.println("B");
System.out.println("B");
}
}
publicclassSample8 {
publicstaticvoidmain(String[] args) {
Printp1=newPrint();
p1.delimiter="----";
p1.A();
p1.A();
p1.B();
p1.B();
Printp2=newPrint();
p2.delimiter="****";
p2.A();
p2.A();
p2.B();
p2.B();
p1.A();
p2.A();
p1.A();
p2.A();
}
}
2. static
스태틱은 클래스 소속, 스태틱이 없는 것은 인스턴스 소속이라는 것을 기억하자.
1. 인스턴스들은 classVar랑 연결되어 있음. 한 인스턴스의 classVar를 변경하면 클래스의classVar, 그리고 같은 클래스에 있는 다른 instance의 값도 같이 변경된다.
2. 각각의 instance 변수들( static 없는 것 )은 완전히 독립적이고, 자기 instance안에서만 뭔가 변하게 할 수 있음.
classFoo{
publicstaticStringclassVar="I class var";
publicStringinstanceVar="I instance var";
publicstaticvoidclassMethod() {
System.out.println(classVar);
//System.out.println(instanceVar); //Error. 클래스 메소드 안에서 인스턴스 변수에 접근 불가.
}
publicvoidinstanceMethod() {
System.out.println(classVar);
System.out.println(instanceVar); //인스턴스 메소드 안에서는 인스턴스 변수에도 접근 가능함.
}
}
publicclassStaticApp {
publicstaticvoidmain(String[] args) {
System.out.println(Foo.classVar); //OK
//System.out.println(Foo,instanceVar); //Error
Foo.classMethod();
//Foo.instanceMethod(); //인스턴스 메소드이기 때문에 클래스를 통해 접근할 수 없음.
Foof1=newFoo();
Foof2=newFoo();
System.out.println(f1.classVar); // I class var
System.out.println(f1.instanceVar); // I instance var
f1.classVar="changed by f1"; // f1 인스턴스의 classVar 를 변경했을 때
System.out.println(Foo.classVar); // class Foo의 classVar 변수도 변경됨. changed by f1
System.out.println(f2.classVar); // f2 인스턴스의 classVar 변수도 변경됨. changed by f1
f1.instanceVar="changed by f1"; // f1 인스턴스의 instanceVar를 변경했을 때
System.out.println(f1.instanceVar); // f1의 instanceVar 변수 변경됨. changed by f1
System.out.println(f2.instanceVar); // f2의 instanceVar는 변경 안 됨. 그대로 있음 == I instance bar
//System.out.println(Foo.instanceVar); //이런 건 존재 X