ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ch6 자바 객체] 메서드의 정의와 매개변수, staic
    프로그래밍 언어/JAVA 2022. 6. 14. 22:11
    메서드란?

    '메서드(method)'는 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것이다. 기본적으로 수학의 함수와 유사하며, 어떤 값을 입력하면 이 값으로 작업을 수행해서 결과를 반환한다. 예를 들어 제곱근을 구하는 메서드 'Math.sqrt()'는 4.0을 입력하면, 2.0으로 결과를 반환한다. 메서드는 넣을 값(입력)과 반환하는 결과(출력)만 알면 되는 것이다. 그래서 보이지 않는 '블랙박스(black box)'라고도 한다. sqrt()외에도 지금까지 빈번히 사용해온 println()이나 random()과 같은 메서드들 역시 내부적으로 어떻게 동작하는지 몰라도 아무런 어려움은 없다.

     

    메서드는 크게 두 부분, '선언부(header,머리)'와 '구현부(body, 몸통)'로 이루어져 있다. 메서드를 정의한다는 것은 선언부와 구현부를 작성하는 것을 뜻하며 다음과 같은 형식으로서 메서드를 정의한다.

     

     

     

    메서드의 선언부

    메서드의 선언부는 '메서드의 이름'과 '매개변수 선언', 그리고 반환타입으로 구성되어 있으며, 메서드가 작업을 수행하기 위해 어떤 값들을 필요로 하고 작업의 결과로 어떤 타입의 값을 반환하는지에 대한 정보를 제공한다. 예를 들어 아래에 정의된 메서드 add는 두 개의 정수를 입력받아서, 두 값을 더한 결과(int 타입의 값)를 반환한다.

    매개변수 선언(parameter declaration)

    :매개변수는 메서드가 작업을 수행하는데 필요한 값들(입력)을 제공받기 위한 것이며, 필요한 값의 개수만큼 변수를 선언하며 각 변수 간의 구분은 쉼표' , ' 를 사용한다. 한 가지 주의할 점은 일반적인 변수 선언과 달리 변수의 타입이 같아도 변수 타입을 생략할 수 없다는 것이다. 

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

    선언할 수 있는 매개변수의 개수는 거의 제한이 없지만, 만ㅇ리 입력해야할 값의 개수가 많은 경우는 배열이나 참조변수를 사용하면 된다. 

     

     

    반환타입(return type)

    메서드의 작업수행 결과(출력)인 '반환값(return value)'의 타입을 적는다. 단 반환값이 없는 경우 반환 타입을 void로 적어야한다. 

    void print99danAll(){
      for(int i=1; i<=9; i++){
         for(int j=2; j<=9; j++){
                  System.out.print(j+”*”+i+”=“+(j*i)+” “);
                }
           }
    }

    위의 메서드는 구구단 전체를 출력하는데, 작업을 수행하는데 필요한 값(입력)도, 작업수행 결과인 반환값(출력)도 없다. 그래서 반환 타입이 'void'이다.

     

     

    메서드의 구현부

    메서드의 선언부 다음와 오는 괄호 { } 를 메서드의 구현부라고 하는데, 여기에 메서드를 호출했을 때 수행될 문장들을 넣는다. 

     

    return 문

    메서드의 반환타입이 'void'가 아닌 경우, 구현부{ } 안에 'return 반환값;'이 반드시 포함되어 있어야 한다. 이 문장은 작업을 수행한 결과인 반환값을 호출한 메서드로 전달하는데, 이 값의 타입은 반환타입과 일치하거나 적어도 자동 형변환이  가능한 것 이어야한다. 여러개의 변수를 선언할 수 있는 매개변수와 달리 return문은 단 하나의 값만 반환할 수 있는데, 메서드로의 입력(매개변수)은 여러 개일 수 있어도 출력(반환값)은 최대 하나만 허용하는 것이다. 

    위의 코드에서 'return result;'는 변수 result에 저장된 값을 호출한 메서드로 반환한다. 변수 reult 타입이 int이므로 메서드 add의 반환타입과 일치하는 것을 알 수 있다. 

     

     

     

    메서드의 호출

    메서드를 정의했어도 호출되지 않으면 아무 일도 일어나지 않는다. 메서드를 호출해야만 구현부{ } 의 문장들이 수행된다. 

    메서드 이름(값1, 값2, ,,,, ); 
    print99danAll();     //void print99danAll()을 호출
    int result = add(3, 5);   //int add(int x, int y)를 호출하고, 결과를 result에 저장

     

    인수(argument)와 매개변수(parameter)

    : 메서드를 호출할 때 괄호() 안에 지정해준 값들을 '인수(argument)' 또는 '인자'라고 하는데, 인자의 개수와 순서는 호출된 메서드에 선언된 매개변수와 일치해야한다. 그리고 인수는 메서드가 호출되면서 매개변수에 대입되므로, 인자의 타입은 매개변수의 타입과 일치하거나 자동 형변환이 가능한 것이어야 한다. 

    만일 메서드에 선언된 매개변수의 개수보다 많은 값을 괄호( )에 넣거나 타입이 다른 값을 넣으면 컴파일러가 에러를 발생시킨다. 

    int rsult = add(1, 2, 3); //에러. 개수가 다름
    int result = add(1.0, 2.0); //에러. 타입이 다름

     

     

     

    메서드의 실행 흐름

    MyMath 클래스의 'add(long a, long b)'를 호출하기 위해서는 먼저 'MyMath mm = new MyMath();'와 같이 해서, MyMath 클래스의 인스턴스를 생성한 다음 참조변수 mm을 통해야한다.

     

    1.  main메서드에서 add 메서드를 호출한다. 인수 1L과 2L이 메서드 add의 매개변수 a,b에 각각 복사(대입)된다.
    2. 메서드 add의 괄호{ } 안에 있는 문장들이 순서대로 수행된다.
    3. 메서드 add의 모든 문장이 실행되거나 return문을 만나면, 호출한 메서드(main 메서드)로 되돌아와서 이후의 문장들을 실행한다.

    메서드가 호출되면 지금까지 실행중이던 메서드는 실행을 잠시 멈추고 호출된 메서드의 문장들이 실행된다. 호출된 메서드의 작업이 모두 끝나면, 다시 호출한 메서드로 돌아와 이후의 문장들을 실행한다.

     

     

    ※ 예제 1번 

    package ch6;
    
    public class Ex6_4 {
    
    	public static void main(String[] args) {
    		MyMath mm = new MyMath();
    		long result1 = mm.add(5L, 3L);
    		long result2 = mm.substract(5L, 3L);
    		long result3 = mm.multiply(5L, 3L);
    		double result4 = mm.divide(5L, 3L);
    		
    		System.out.println("add(5L, 3L) = " + result1 );
    		System.out.println("substract(5L, 3L) = " + result2 );
    		System.out.println("multiply(5L, 3L) = " + result3 );
    		System.out.println("divide(5L, 3L) = " + result4 );
    		
    
    	}
    
    }
    
    class MyMath{
    	long add(long a, long b) {
    		long result = a+b;
    		return result;
    	}
    	long substract(long a, long b) {
    		return a-b;
    	}
    	long multiply(long a, long b) {
    		return a*b;
    	}
    	double divide(double a, double b) {
    		return a/b;
    	}
    }

    예제 1번 출력 값

     

    사칙연산을 위해 4개의 메서드가 정의되어 있는 MyMath 클래스를 이용한 예제이다. 이 예제를 통해서 클래스에 선언된 메서드를 어떻게 호출하는지 알 수 있다. 여기서 눈여겨 볼 곳은 double divide(double a, double b) 메서드를 호출하는 부분이다. divide메서드에서 선언된 매개변수 타입은 doube형인데, 이와 다른 long형의 값인 5L과 3L을 사용해서 호출하는 것이 가능하다. 호출 시에 입력된 값은 메서드의 매개변수에 대입되는 값이므로, long형의 값을 double형 변수에 저장하는 것과 같아서 'double형 값인 5.0으로 자동 형변환되어 divide 매개변수 a에 저장된다.

     

     

     

    return 문

    return문은 현재 실행중인 메서드를 종료하고 호출한 메서드로 돌아간다. 지금까지 반환값이 있을 때만 return문을 썼지만, 원래는 반환값의 유무에 관계없이 모든 메서드는 적어도 하나의 return문이 있어야 한다. 그런데 반환타입이 void인 경우, return문이 없어도 아무런 문제가 없었던 이유는 컴파일러가 메서드의 마지막에 'return;'을 자동적으로 추가해주었기 때문이다. 그러나 반환타입이 void가 아닌 경우, 즉 반환값이 있는 경우, 반드시 return문이 있어야 한다. return문이 없으면 컴파일 에러 (error: missing return statement)가 발생한다. 

     

    int max(int a, int b){
    if(a > b)
    return a; // 조건식이 참일때만 실행된다.
    }

    위 코드는 두 값 중에서 큰 값을 반환하는 메서드인데, 이 메서드의 리턴타입이 int이고 int타입의 값을 반환하는 return문이 있지만 return문이 없다는 에러가 발생한다. why? if문 조건식의 결과에 따라 return문이 실행되지 않을 수도 있기 때문이다

     

    int max(int a, int b){
    if(a > b)
          return a; // 조건식이 참일때만 실행된다.
    else
         return b; //조건식이 거짓일 때 실행된다.
    }

    그래서 이런 경우 위와 같이 if문의 else블럭에 return문을 추가해서, 항상 결과값이 반환되도록 해야 한다.

     

     

    반환값

    return문의 반환값으로 주로 변수가 오긴 하지만 항상 그런 것은 아니다. 아래 1번 코드는 2번과 같이 간략히 할 수 있는데, 2번 코드는 return문의 반환값으로 'x+y'라는 수식이 적혀있다. 그렇다고 수식이 반환되는 것은 아니고, 이 수식을 계산한 결과가 반환된다. 

    1번. 
    int add(int x, int y){
     int result = x+y;
     return result;
    }

    2번.
    int add(int x, int y) {
     return x+y;
    }

     

    간단한 메서드의 경우 if문 대신 조건연산자를 사용하기도 한다. 메서드 abs는 입력받은 정수의 부호를 판단해서 음수일 경우 부호연산자(-)를 사용해 양수로 반환한다.

    int abs(int x) {
    return x>=0 ? x: -x;
    }

     


     

    기본형 매개변수

    자바에서는 메서드를 호출할 때 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨준다. 매개변수 타입이 기본형(primitive type)일 때는 기본형 값이 복사되겠지만, 참조형(reference type)이면 인스턴스의 주소가 복사된다. 메서드의 매개변수를 기본형으로 선언하면 단순히 저장된 값을 얻지만, 참조형으로 선언하면 값이 저장된 곳의 주소를 알 수 있기 때문에 값을 읽어오는 것은 물론 값을 변경하는 것도 가능하다.

    기본형 매개변수 : 변수의 값을 읽기만 할 수 있다. (read only)
    참조형 매개변수 : 변수의 값을 읽고 변경할 수 있다. (read & write)

     

     

    ※ 예제 2번 ※

    package ch6;
    
    
    class Data{int x;}
    
    public class Ex6_6 {
    
    	public static void main(String[] args) {
    		Data d = new Data();
    		d.x = 10;
    		System.out.println("main() : x = " + d.x);
    		
    		change(d.x);
    		System.out.println("After change(d.x)");
    		System.out.println("main() : x = " + d.x);
    
    	}
    
    	 static void change(int x) { // 기본형 매개변수
    		x = 1000;
    		System.out.println("change() : x = "+ x );
    		
    	}
    
    }

     

    예제 2번 출력 값

    1. change 메서드가 호출되면서 'd.x'가 change메서드의 매개변수 x에 복사된다. 
    2. change메서드에서 x의 값을 1000으로 변경
    3. change메서드가 종료되면 매개변수 x는 스택에서 제거된다.

    'd.x'의 값이 변경된 것이 아니라, change메서드의 매개변수 x값이 변경된 것이다. 즉) 원본이 아닌 복사본이 변경된 것이라 원본에는 아무런영향을 미치지 못한다. 이처럼 기본형 매개변수는 변수에 저장된 값만 읽을 수만 있을 뿐 변경할 수는 없다

     

     

    참조형 매개변수

    ※ 예제 3번 ※

    package ch6;
    
    
    class Data2{int x;}
    
    public class Ex6_7 {
    
    	public static void main(String[] args) {
    		Data2 d = new Data2();
    		d.x = 10;
    		System.out.println("main() : x = " + d.x);
    		
    		change(d);
    		System.out.println("After change(d)");
    		System.out.println("main() : x = " + d.x);
    
    	}
    
    	 static void change(Data2 d) { //참조형 매개변수
    		d.x = 1000;
    		System.out.println("change() : x = "+ d.x );
    		
    	}
    
    }

    예제 3번 출력 값

    예제 2번과 달리 change 메서드를 호출한 후에 d.x의 값이 변경되었다. change 메서드의 매개변수가 참조형이라서 값이 아니라 '값이 저장된 주소'를 change메서드에게 넘겨주었기 떄문에 값을 읽어오는 것뿐만 아니라 변경하는 것도 가능하다.

     

    1. change메서드가 호출되면서 참조변수 d의 값(주소)이 매개변수 d에 복사된다. 이제 매개변수 d에 저장된 주소값으로 x에 접근이 가능하다.
    2. chagne 메서드에서 매개변수 d로 x의 값을 1000으로 변경
    3. chagne메서드가 종료되면서 매개변수 d는 스택에서 제거된다.

    2번 예제와 달리, change 메서드의 매개변수를 참조형으로 선언했기 때문에, x의 값이 아닌 변수 d의 주소가 매개변수 d에 복사되었다. 이제 main 메서드의 참조변수 d와 chagne메서드의 참조변수 d는 같은 값을 가리키게 된다. 그래서 매개변수 d로 x의 값을 읽는 것과 변경이 모두 가능한 것이다.

     

     

    static 메서드와 인스턴스 메서드

    변수에서 그랬던 것과 같이, 메서드 앞에 static이 붙어 있으면 클래스메서드이고 붙어 있지 않으면 인스턴스 메서드이다. 클래스 메서드도 클래스 변수처럼, 객체를 생성하지 않고도 '클래스이름.메서드이름(매개변수)'와 같은 식으로 호출이 가능하다. 반면에 인스턴스 메서드는 반드시 객체를 생성해야만 호출할 수 있다. 그렇다면 클래스를 정의할 때, 어느 경우에 static을 사용해서 클래스 메서드로 정의해야하는 것일까? 클래스는 '데이터(변수)와 데이터에 관련된 메서드의 집합'이므로, 같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있다.

     

    인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다. 그런데 인스턴스 변수는 인스턴스(객체)를 생성해야만 만들어지므로 인스턴스 메서드 역시 인스턴스를 생성해야만 호출할 수 있는 것이다. 반면에 메서드 중에서 인스턴스와 관계없는(인스턴스 변수나 인스턴스 메서드를 사용하지 않는) 메서드들을 클래스 메서드(static 메서드)로 정의한다. 물론 인스턴스 변수를 사용하지 않는다고 해서 반드시 클래스 메서드로 정의해야 하는 것은 아니지만 특별한 이유가 없는 한 그렇게 하는 것이 일방적이다.

     

    클래스 영역에 선언된 변수를 멤버변수라 한다. 멤버변수 중에 static이 붙은 것은 클래스변수(static변수), static이 붙지 않은 것을 인스턴스변수라 한다. 멤버변수는 인스턴스변수와 static변수를 모두 통칭하는 말이다. 

     

     

    ※ 예제 4번 ※

    package ch6;
    
    public class Ex6_9 {
    
    	public static void main(String[] args) {
    		
            //클래스 메서드 호출, 인스턴스 생성없이 호출 가능
    		System.out.println(MyMath2.add(200L, 100L));
    		System.out.println(MyMath2.substract(200L, 100L));
    		System.out.println(MyMath2.multiply(200L, 100L));
    		System.out.println(MyMath2.divide(200L, 100L));
    		
    		System.out.println("========================");
    		
    		MyMath2 mm = new MyMath2(); //인스턴스를 생성
    		mm.a = 200L;
    		mm.b = 100L;
            //인스턴스 메서드는 객체생성 후에만 호출이 가능함
    		System.out.println(mm.add());
    		System.out.println(mm.substract());
    		System.out.println(mm.multiply());
    		System.out.println(mm.divide());
    	}
    
    }
    
    
    
    class MyMath2{
    	long a,b;
    	
    	//인스턴스 변수 a,b만을 이용해서 작업하므로 매개변수가 필요 없다.
    	long add() {return a+b;} //a,b는 인스턴스 변수
    	long substract() {return a-b;}
    	long multiply() {return a*b;}
    	double divide() {return a/b;}
    	
    	//인스턴스 변수와 관계없이 매개변수만으로 작업이 가능하다.
    	static long add(long a,long b) {return a+b;} //a,b는 지역변수
    	static long substract(long a, long b) {return a-b;}
    	static long multiply(long a, long b) {return a*b;}
    	static double divide(long a, long b) {return a/(double)b;}
    
    	
    }

    예제 4번 출력 값

     

    인스턴스 메서드인 add(), substract(), multifly(), divide()는 인스턴스변수인 a와 b만으로도 충분히 작업이 가능하기 때문에, 매개변수를 필요로 하지 않으므로 괄호()에 매개변수를 선언하지 않았다. 

    반면에 add(long a, long b) ,, 등은 인스턴스변수 없이 매개변수 만으로 작업을 수행하기 때문에 static을 붙여서 클래스메서드로 선언했다. 그래서 위의 예제 4번을 보면, 클래스 메서드는 객체생성 없이 바로 호출이 가능했고, 인스턴스메서드는 MyMath2 클래스의 인스턴스를 생성한 후에야 호출이 가능했다. 

     

     

    static을 언제 붙여야 할까?

    1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.

    : 생성된 각 인스턴스는 서로 독립적이기 때문에 각 인스턴스의 변수(iv)는 서로 다른 값을 유지한다. 그러나 모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스변수로 정의해야 한다.

     

    2. 클래스 변수(static변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.

    : static이 붙은 변수(클래스변수)는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다. 

     

    3. 클래스 메서드(static 메서드)는 인스턴스 변수를 사용할 수 없다.

    : 인스턴스변수는 인스턴스가 반드시 존재해야만 사용할 수 있는데, 클래스메서드(static이 붙은 메서드)는 인스턴스 생성 없이 호출하므로 클래스 메서드가 호출되었을 때 인스턴스가 존재하지 않을수도 있다. 그래서 클래스 메서드에서 인스턴스 변수의 사용을 금지한다. 

    반면에 인스턴스변수나 인스턴스메서드에서는 static이 붙은 멤버들을 사용하는 것이 언제나 가능하다. 인스턴스 변수가 존재한다는 것은 static 변수가 이미 메모리에 존재한다는 것을 의미하기 때문이다. 

     

    4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.

    : 메서드의 작업내용 중에서 인스턴스변수를 필요로 한다면, static을 붙일 수 없다. 반대로 인스턴스변수를 필요로 하지 않는다면 static을 붙인다. 메서드 호출시간이 짧아지므로 성능이 향상된다. static을 안 붙인 메서드(인스턴스메스드)는 실행 시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다. 

    멤버변수 : 클래스의 멤버변수 중 모든 인스턴스에 공통된 값을 유지해야하는 것이 있는지 살펴보고 있으면,static을 붙여준다. 
    메서드 : 작성한 메서드 중에서 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드에 static을 붙일 것을 고려한다.

     

     

    메서드 간의 호출과 참조

    같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다. 단, 클래스멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다. 

    그 이유는 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문이다.  (인스턴스 멤버란 인스턴스 변수와 인스턴스 메서드를 의미)

     

    위의 코드는 같은 클래스 내의 인스턴스 메서드와 static메서드 간의 호출에 대해 설명하고 있는데, 같은 클래스 내의 메서드는 서로 객체의 생성이나 참조변수 없이 직접 호출이 가능하지만 static메서드는 인스턴스 메서드를 호출할 수 없다.

     

     

    이번엔 변수와 메서드간의 호출에 대해 살펴보면, 메서드간의 호출에서처럼 인스턴스메서드는 인스턴스 변수를 사용할 수 있지만, static메서드는 인스턴스 변수를 사용할 수 없다. 

     

    반응형

    댓글

Designed by Tistory.