본문 바로가기

Java 기본 문법 - 참조 서적 [이것이 자바다 - 한빛미디어]/4. 객체지향 프로그래밍

5. Java 자바 - 클래스의 구성 멤버 [ 메소드 ]

메소드

 

객체의 동작에 해당하는 중괄호 { } 블록을 말한다.

메소드를 호출하게 되면 중괄호 블록에 있는 코드들이 일괄적으로 수행된다.

 

메소드는 필드를 읽고 수행하는 역할도 하지만, 다른 객체를 생성해서 다양한 기능을 수행하기도 한다.

 

메소드는 객체 간의 데이터 전달 수단으로 사용된다.

 

외부로부터 매개값을 받을 수 있고, 실행 후 어떤 값을 반환할 수도 있다.

 


 

메소드 선언

 

선언부(반환타입, 메소드 이름, 매개 변수 선언) 와 실행 블록으로 구성된다.

 

메소드 선언부를 "메소드 시그니처"라고도 한다.

 

메소드 선언 형태

 

메소드 형태

 

- 반환 타입 (return)

 

메소드가 실행 후 반환하는 값의 타입을 의미한다. (반환값이 있을 수도, 없을 수도 있다.)

 

메소드가 실행 후 결과를 호출한 곳에 넘겨줄 경우, 반환값이 있어야한다.

 

반환값이 없는 메소드는 반환 타입에 void가 와야 하며,

반환값이 있는 메소드는 반환값의 타입이 와야한다.

 

ex)

void powerOn() { . . . }  // 전원을 켜는 메소드는 반환값이 없어도 된다.
double divide(int x, int y) { . . . }   // 나눗셈 후 결과값을 반환해야 한다.

 

위의 메소드들은 아래와 같이 호출할 수 있다.

 

powerOn();  // 반환값, 매개변수 없이 메소드만 호출됨
double result = divide(10, 20);   // 매개변수 10을 20으로 나눈 나머지 0.5 가 반환되어 저장됨

 

divide 메소드는 double 타입으로 반환하기 때문에 아래와 같이 int 타입으로 result 변수를 선언하게 되면

컴파일 에러가 발생한다.

 

int result = divide(10, 20);    // 컴파일 에러

 

반환값을 반드시 변수에 저장할 필요는 없다.

메소드 실행이 중요한 경우 아래와 같이 변수 선언 없이 메소드를 호출할 수 있다.

 

divide(10, 20);

 

 

- 메소드 이름

 

메소드 이름은 자바 식별자 규칙에 맞게 작성해야 한다.

 

    - 숫자로 시작할 수 없고, $, _ 를 제외한 특수 문자는 사용할 수 없다.

    - 관례적으로 메소드 명은 소문자로 시작한다.

    - 서로 다른 단어가 혼합된 이름일 때, 단어의 첫 머리 글자는 대문자로 작성한다.

 

ex) void run( ) { . . . }, String getName( ) { . . . }

 

어떤 기능을 수행하는지 알 수 있도록 이름을 지어준다.

(이름 길이 제한 없음, 너무 짧게 짓지 않도록 한다.)

 

 

- 매개 변수 선언

 

메소드가 실행할 때 필요한 데이터를 외부로부터 받기 위해 사용된다.

 

매개 변수도 필요할 수도, 필요 없을 수도 있다.

(powerOn( ) 메소드는 매개 변수가 필요없지만,

  divide( ) 메소드는 나누어야 하는 수가 필요해서 매개 변수가 필요하다.)

 

double result = divide(10, 20);   // 매개변수가 반드시 필요한 메소드

 

만약 타입이 잘못된 매개 변수 값을 넘겨주게 되면 컴파일 에러가 발생한다.

 

double result = divide (10.5, 20.0);   // 매개 변수가 int 가 아닌 double 이므로 에러!!

 

하지만, 자동 타입 변환이 되는 경우는 예외이다.

아래 byte 타입 변수는 int 타입 매개 변수로 전달 시 int 타입으로 자동 변환 된다.

 

byte b1 = 10;
byte b2 = 20;
double result = divide(b1, b2);   // byte -> int 자동 변환 (컴파일 에러 발생 X)

 

 

ex) Calculator.java 클래스

public class Calculator {

    //메소드
    void powerOn() {
        System.out.println("전원을 켭니다.");
    }
    
    int plus(int x, int y) {
        int result = x + y;
        return result;
    }
    
    double divide(int x, int y) {
        double result = (double)x / (double)y;
        return result;
    }
    
    void powerOff() {
        System.out.println("전원을 끕니다.");
    }
}

 

Calculator 클래스의 메소드를 호출하기 위해서는 아래와 같이 Calculator 객체를 생성하고,

참조 변수인 myCalc를 이용해야 한다.

 

CalculatorExample.java : 메소드 호출

public class CalculatorExample {
    public static void main(String[] args) {
        Calculator myCalc = new Calculator();  // 객체 생성
        myCalc.powerOn();   // 전원을 켭니다.
        
        int result1 = myCalc.plus(5, 6);
        System.out.println("result1 : "+ result1);   //11
        
        byte x = 10;
        byte y = 4;
        double result2 = myCalc.divide(x, y);
        System.out.println("result2 : "+ result2);    //2.5
        myCalc.powerOff();   // 전원을 끕니다.
    }
}

 


 

매개 변수의 수를 모를 경우

 

메소드의 매개 변수는 개수가 이미 정해져 있는 것이 일반적이지만,

경우에 따라 메소드를 선언할 때 매개 변수의 개수를 알 수 없는 경우가 있다.

 

ex) 여러 개의 수를 모두 합산하는 메소드 일 때 얼만큼의 매개 변수가 입력되는 지 알 수 없다.

 

해결책! : 매개 변수를 배열 타입으로 선언한다. (main 메소드의 String[] args 처럼!!)

 

int sum1(int[] values) { . . . }

 

sum1 메소드를 호출할 때 배열을 넘겨줌으로써 배열의 항목 값들을 모두 전달할 수 있다.

 

배열의 항목 수는 호출할 때 결정된다.

 

int[] values = { 1, 2, 3 };
int result = sum1(values);                      // 이미 생성되어있는 values 배열 전달
int result = sum1(new int[] { 1, 2, 3, 4, 5 }); // 새로운 배열 생성해서 전달 (불편함)

 

매개 변수를 배열 타입으로 선언하면, 메소드 호출 전, 배열을 생성해야하는 불편함이 있다.

 

배열을 생성하지 않고, 값의 리스트만 넘기는 방법도 있다.

 

아래와 같이 sum2( ) 메소드의 매개 변수를 " . . . " 을 사용해서 선언하면, 

 

메소드 호출 시 넘겨준 값의 수에 따라 자동으로 배열이 생성되고 매개값으로 사용된다.

 

int sum2(int ... values) {  }

 

"..." 으로 선언된 매개 변수 값은 아래와 같이 메소드 호출 시 리스트로 나열만 해주면 된다.

 

int result = sum2(1, 2, 3);
int result = sum2(1, 2, 3, 4, 5);

 

"..." 으로 선언된 매개 변수는 배열 타입이므로

아래와 같이 이미 생성된 배열을 직접 매개값으로 전달하여 사용할 수 있다.

 

int sum2 (int ... values) {  }    // "..." 은 배열 생성에 영향을 미치지 않음

int[] values = { 1, 2, 3 };   // 배열 생성됨
int result = sum2(values);    // "..."으로 선언된 매개 형태는 이미 생성된 배열도 받을 수 있다.
int result = sum2(new int[] { 1, 2, 3, 4, 5 };

 

즉, 매개 변수의 수를 모를 경우 배열로 매개 변수를 전달하는 2 가지 방법

 

- 미리 배열을 생성하여 전달한다.

- 미리 배열을 생성하지 않고, 리스트로 명시한다.

 

ex) 매개 변수를 배열로 선언한 sum1, 매개 변수를 "..." 으로 선언한 sum2  (동일한 실행 결과)

Computer.java

public class Computer {
    int sum1(int[] values) {   // 매개 변수가 배열로 선언됨
        int sum = 0;
        for(int i = 0; i < values.length; i++) {
            sum += values[i];
        }
        
        return sum;
    }
    
    int sum2(int ... values) {
        int sum = 0;
        for(int i = 0; i < values.length; i++) {
            sum += values[i];
        }
        
        return sum;
    }
}

 

ComputerExample.java : 실행 클래스

public class ComputerExample {
    public static void main(String[] args) {
        Computer myCom = new Computer();   // 객체 생성
        
        int[] values1 = { 1, 2, 3 };
        int result1 = myCom.sum1(values1);    // 미리 생성된 배열 전달
        System.out.println("result1 : "+ result1);   // 6
        
        int result2 = myCom.sum1(new int[] { 1, 2, 3, 4, 5 });  // 배열 생성후 전달
        System.out.println("result2 : "+ result2);    // 15
        
        int result3 = myCom.sum2(1, 2, 3);  // 리스트로 전달
        System.out.println("result3 : "+ result3);    // 6
        
        int result4 = myCom.sum2(1, 2, 3, 4, 5);
        System.out.println("result4 : "+ result4);    // 15
    }
}

 


 

리턴 (return) 문

 

1. 반환값이 있는 메소드

 

메소드 선언에 리턴 타입이 있는 메소드는 반드시 리턴(return) 문을 사용해서 리턴값을 지정해야 한다.

return 문이 없다면 컴파일 에러가 발생한다.

 

return 문이 실행되면 메소드는 이후 즉시 종료된다.

 

return 리턴값;

 

return 문의 리턴값은 리턴 타입이거나, 리턴 타입으로 변환될 수 있어야 한다.

(ex : 리턴 타입이 int인 plus( ) 메소드에서 byte, short, int 타입의 값이 리턴되어도 상관 없다.

byte, short는 int로 자동 타입 변환되기 때문!!)

 

int plus(int x, int y) {
    int result = x + y;
    return result;
}

 

int plus(int x, int y) {
    byte result = (byte)(x + y);
    return result;   // int 타입으로 자동 변환된다.
}

 

return 문 사용 시 주의할 점!

 

- return 문 이후에 실행문이 오면 "Unreachable code" 컴파일 오류 발생

(리턴문 이후 실행문은 결코 실행되지 않음)

int plus(int x, int y) {
    int result = x + y;
    return result;
    System.out.println(result);   // return 문 이후 실행문은 컴파일 에러!!
}

 

- 하지만, if 문으로 실행문이 분기되는 경우는 컴파일 에러가 발생하지 않는다.

 

boolean isLeftGas() {
    if(gas == 0) {
        System.out.println("gas가 없습니다.");
        return false;
    }
    System.out.println("gas가 있습니다.");
    return true;
}

 

 

2. 리턴값이 없는 메소드 (void)

 

void 로 선언된 리턴값이 없는 메소드에서도 return 문을 사용할 수 있다.

 

return 문을 사용하게 되면 메소드 실행을 강제 종료시킨다.

 

return;

 

ex) gas 값이 0보다 클 경우, while문 계속 실행, 0일 경우 run() 메소드 즉시 종료

void run() {
    while(true) {
        if(gas > 0) {
            System.out.println("달립니다. (gas 잔량 : "+ gas +")");
            gas -= 1;
        } else {
            System.out.println("멈춥니다. (gas 잔량 : "+ gas +")");
            return;   // run() 메소드 실행 종료 (break; 사용 가능)
        }
    }
}

 

while 문 1번 돌 때마다 gas 값 1 감소, gas 값이 0 이 되면 run( ) 메소드가 종료된다.

위의 예제에서는 return 문 대신 break 문을 사용할 수 있다.

 

만약 while 문 뒤에 실행문이 추가적으로 더 있을 경우, break 문을 반드시 사용해야 한다.

return 문은 즉시 메소드를 종료시키기 때문이다.

 

ex) Car.java

public class Car {
    //필드
    int gas;
    
    //생성자
    
    //메소드
    void setGas(int gas) {
        this.gas = gas;  // 리턴값이 없는 메소드로 매개값을 받아서 gas 필드값을 변경
    }
    
    boolean isLeftGas() {  //리턴값 boolean인 메소드
        if(gas == 0) {
            System.out.println("gas가 없습니다.");
            return false;  // false 리턴
        }
        System.out.println("gas가 있습니다.");
        return true;        // true 리턴
    }
    void run() {   //리턴값 없는 메소드
        while(true) {
            if(gas > 0) {
                System.out.println("달립니다. (gas 잔량 : " + gas + ")");
                gas -= 1;
            } else {
                System.out.println("멈춥니다.(gas 잔량 : "+ gas + ")");
                return;    // 메소드 실행 종료
            }
        }
    }
}

 

CarExample.java

public class CarExample {
    public static void main(String[] args) {
        Car myCar = new Car();
        
        myCar.setGas(5);    // Car의 setGas() 메소드 호출
        
        boolean gasState = myCar.isLeftGas();   // Car의 isLeftGas() 메소드 호출
        if(gasState) {
            System.out.println("출발합니다.");
            myCar.run();   //Car의 run() 메소드 호출
        }
        
        if(myCar.isLeftGas()) {  // Car의 isLeftGas() 메소드 호출
            System.out.println("gas를 주입할 필요가 없습니다.");
        } else {
            System.out.println("gas를 주입해 주세요");
        }
    }
}

 

isLeftGas( ) 메소드가 boolean 값을 리턴하기 때문에 if 문의 조건식으로 들어갈 수 있다.

 

 


 

메소드 호출

 

메소드는 클래스 내부, 외부의 호출에 의해 실행된다.

 

클래스 내부의 다른 메소드에서 호출할 경우, 단순히 메소드 이름으로 호출하면 되지만

 

클래스 외부에서 메소드를 호출할 경우, 우선 클래스로부터 객체를 생성한 뒤,

참조 변수를 이용해서 메소드를 호출해야 한다.

(객체가 존재해야 메소드도 존재하기 때문!!)

 

메소드의 외부 / 내부 호출

 

1. 클래스 내부에서 메소드 호출 (객체 내부)

 

클래스 내부에서 메소드를 호출하려면 아래와 같이 작성한다.

메소드가 매개 변수를 가지고 있을 때, 매개 변수의 타입과 수에 맞게 매개값을 제공한다.

 

메소드( 매개값1, . . . );

 

ex) 객체 내부 method2( ) 메소드에서 method1( ) 메소드를 호출하려면 아래와 같다.

 

객체 내부에서 메소드 호출

 

① 호출에서 method1("홍길동", 100) 이 호출되면

매개값인 "홍길동" 은 p1 매개 변수에 대입되고, 100 은 p2 매개 변수에 대입된다.

 

② 실행에서 p1, p2 변수를 이용하게 된다.

 

메소드가 리턴값이 없거나, 있어도 받고 싶지 않을 경우 위와 같이 모두 호출이 가능하다.

 

리턴값이 있는 메소드를 호출하고 리턴값을 받고 싶다면, 아래와 같이 변수를 선언하고 리턴값을 저장한다.

 

타입 변수 = 메소드(매개값1, . . . );   // 메소드의 리턴값을 변수에 저장한다

 

주의!

변수 타입은 메소드 리턴 타입과 동일해야 하거나, 자동 타입 변환(큰 자료형으로)이 될 수 있어야 한다.

(int 타입은 double 타입으로 자동 변환 될 수 있기 때문에 int 리턴값은 double 변수에 저장될 수 있다.)

 

public class ClassName {
    int method1(int x, int y) {
        int result = x + y;
        return result;
    }
    
    void method2() {
        int result1 = method1(10, 20);    // result1에 30 저장
        double result2 = method1(10, 20);  // result2에 30.0 저장
    }
}

 

ex) 클래스 내부에서 메소드 호출

Calculator.java

public class Calculator {
    int plus(int x, int y) {
        int result = x + y;
        return result;
    }
    
    double avg(int x, int y) {
        double sum = plus(x, y);   // 2. plus 메소드 호출
        double result = sum / 2;
        return result;
    }
    
    void execute() {                
        double result = avg(7, 10);         //1. avg 메소드 호출
        println("실행 결과 : "+ result);    //3. println 메소드 호출
    }
    
    void println(String message) {
        System.out.println(message);        
    }
}

 

CalculatorExample.java : 실행 클래스

public class CalculatorExample {
    public static void main(String[] args) {
        Calculator myCalc = new Calculator();
        myCalc.execute();     // Calculator 의 execute() 메소드 호출
    }
}

 

2. 클래스 외부에서 메소드 호출 (객체 외부)

 

외부 클래스에서 메소드를 호출하려면, 아래와 같이 우선 클래스로부터 객체를 생성해야 한다.

 

메소드는 객체에 소속된 멤버이므로, 객체가 존재하지 않으면, 메소드도 존재하지 않는다.

 

클래스 참조변수 = new 클래스(매개값1, 매개값2, . . . );   //객체 생성

 

객체가 생성되었다면, 참조 변수와 함께 도트 ( . ) 연산자로 메소드를 호출할 수 있다.

 

도트 ( . ) 연산자는 객체 접근 연산자로 객체의 필드, 메소드에 접근할 때 사용된다.

 

참조변수.메소드(매개값1, 매개값2, . . . );   // 리턴값이 없거나, 있어도 리턴값을 받지 않을 경우
타입 변수 = 참조변수.메소드(매개값1, 매개값2, . . . ) // 리턴값이 있고, 리턴값을 저장할 경우

 

ex) Car 객체의 keyTurnOn( ) 메소드, run( ) 메소드, getSpeed( ) 메소드를 호출하는 코드

 

Car myCar = new Car();   // 객체 생성
myCar.keyTurnOn();   // 리턴값X, 단순 호출
myCar.run();         // 리턴값X, 단순 호출
int speed = myCar.getSpeed();   // 리턴값을 받아 변수 speed 에 저장

 

ex) 클래스 외부에서 메소드 호출

Car.java

public class Car {
    // 필드
    int speed;
    
    //생성자
    
    //메소드
    int getSpeed() {
        return speed;
    }
    
    void keyTurnOn() {
        System.out.println("키를 돌립니다.");
    }
    
    void run() {
        for(int i = 10; i <= 50; i += 10) {  //시속 10씩 증가
            speed = i;
            System.out.println("달립니다. (시속 : "+ speed + "km/h)");
        }
    }
}

 

CarExample.java : 실행 클래스

public class CarExample {
    public static void main(String[] args) {
        Car myCar = new Car();  // 객체 생성
        myCar.keyTurnOn();
        myCar.run();
        int speed = myCar.getSpeed();
        System.out.println("현재 속도 : "+ speed + "km/h");
    }
}

 


 

메소드 오버로딩

 

클래스 내같은 이름의 메소드를 여러 개 선언하는 것.

하나의 메소드 이름으로 여러 기능을 담는다. (오버로딩 : 많이 싣는 것)

 

메소드 오버로딩 조건 : 매개 변수의 타입, 개수, 순서 중 하나가 달라야 한다. (구별 조건)

 

매개 변수의 타입, 개수, 순서로 메소드 오버로딩을 구별한다.

 

- 메소드 오버로딩이 필요한 이유

 

매개값을 다양하게 받아서 처리할 수 있도록 하기 위함

 

ex) plus( ) 메소드

int plus(int x, int y) {
    int result = x + y;
    return result;
}

 

만약 int 타입이 아닌, double 타입의 덧셈을 하기 위해서는 plus 메소드를 사용할 수 없다.

(매개 변수 타입이 다르다!)

 

따라서 매개 변수가 double인 타입으로 선언된 plus( ) 메소드를 하나 더 선언한다. (메소드 오버로딩)

 

int plus(int x, int y) {
    int result = x + y;
    return result;
}

double plus(double x, double y) {
    double result = x + y;
    return result;
}

 

오버로딩된 메소드를 호출할 경우 JVM은 매개값 타입을 보고 메소드를 선택한다.

 

plus(10, 20);   // plus(int x, int y) 가 호출된다.

plus(10.0, 20.0);   // plus(double x, double y) 가 호출된다.

 

매개 변수 타입이 섞여있다면??

 

int x = 10;
double y = 20.3;

plus(x, y);  // x는 int, y는 double

 

컴파일 에러는 발생하지 않는다. plus(double x, double y) 메소드가 호출된다.

 

가상 머신은 일차적으로 매개 변수 타입을 보지만, 매개 변수 타입이 일치하지 않는 경우,

자동 타입 변환이 가능한지 검사한다.

 

즉, 첫 번째 매개 변수 int 는 double 타입으로 자동 변환이 가능하기 때문에

plus(double x, double y) 가 선택된다.

 

 

메소드 오버로딩 시 주의할 점!

 

매개 변수 타입, 개수, 순서가 똑같을 경우 매개 변수 이름만 바꾸는 것은 메소드 오버로딩으로 볼 수 없다.

 

또한 리턴 타입만 다르고 매개 변수가 동일해도 역시 메소드 오버로딩이 아니다.

 

리턴 타입은 자바 가상 머신이 메소드를 선택할 때 아무 도움을 주지 못하기 때문이다.

 

아래와 같이 선언하면, 메소드 오버로딩이 아니기 때문에 컴파일 에러가 발생한다. (중복 메소드로 간주)

 

int divide(int x, int y) { . . . }

double divide(int boonja, int boonmo) { . . . }  // 매개 변수 이름만 다르다!!

 

메소드 오버로딩의 대표적인 예 : System.out.println( ) 메소드

 

println( ) 메소드는 호출할 때 주어진 매개값의 타입에 따라 오버로딩된 println( ) 메소드를 호출한다.

 

오버로딩된 println( ) 메소드들

void println() { . . }
void println(boolean x) { . . }
void println(char x) { . . }
void println(char[] x) { . . }
void println(double x) { . . }
void println(float x) { . . }
void println(int x) { . . }
void println(long x) { . . }
void println(Object x) { . . }
void println(String x) { . . }

 

ex) Calculator 클래스에 areaRectangle( ) 메소드를 오버로딩

매개 값이 하나면 정사각형 넓이, 두 개이면 직사각형 넓이 계산하여 리턴 (매개 변수 개수로 구별)

 

Calculator.java : 메소드 오버로딩

public class Calculator {
    //정사각형의 넓이
    double areaRectangle(double width) {
        return width * width;
    }
    
    //직사각형의 넓이
    double areaRectangle(double width, double height) {
        return width * height;
    }
}

 

CalculatorExample.java : 실행 클래스

public class CalculatorExample {
    public static void main(String[] args) {
        Calculator myCalcu = new Calculator();
        
        // 정사각형 넓이
        double result1 = myCalcu.areaRectangle(10);
        
        // 직사각형 넓이
        double result2 = myCalcu.areaRectangle(10, 20);
        
        // 결과 출력
        System.out.println("정사각형 넓이 : "+ result1);
        System.out.println("직사각형 넓이 : "+ result2);
    }
}