[Java의 정석] 변수와 메서드

younghyun·2022년 7월 11일
0

Java의 정석

목록 보기
6/12
post-thumbnail

선언위치에 따른 변수의 종류

1. 클래스 변수: 멤버 변수 중 static이 붙은 것

  • 선언 위치: 클래스 영역
  • 생성 시기: 클래스가 메모리에 올라갔을 때
  • 특징
    • 모든 인스턴스가 공통된 저장공간(변수)을 공유한다.
    • 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우 사용한다.
    • 인스턴스를 생성하지 않고도 언제라도 바로 사용할 수 있다. -> '클래스이름.클래스변수'
    • public을 붙이면 같은 프로그램 내에서 어디서나 접근 가능한 '전역변수'이다.

2. 인스턴스 변수: 멤버 변수 중 static이 붙지 않은 것

  • 선언 위치: 클래스 영역
  • 생성 시기: 클래스의 인스턴스를 생성할 때
  • 특징
    • 독립적인 저장 공간을 가지며, 고유한 상태를 유지해야하는 속성의 경우 사용한다.

3. 지역 변수: 멤버 변수를 제외한 나머지 변수

  • 선언 위치: 메서드, 생성자, 초기화 블럭 내부
  • 생성 시기: 변수 선언문이 수행되었을 때
  • 특징
    • 메서드 내에 선언되어 메서드 내에서만 사용 가능

클래스변수와 인스턴스 변수

class Card {
    
    // 인스턴스 변수 (카드마다 다름)
    String kind; 
    int number;  
    
    // 클래스 변수 (모든 카드가 같음)
    static int width = 100;
    static int height = 250;
}
class CardTest {
    public static void main(String args[]) {
        
        // 클래스 변수는 인스턴스(객체) 생성없이 사용 가능하다.
        System.out.println("카드의 폭:" + Card.width);
        System.out.println("카드의 높이:" + Card.height);
        
        // 인스턴스 변수를 사용하기 위해 클래스의 인스턴스(c1, c2)를 생성한다.
        Card c1 = new Card();
        c1.kind = "Heart";
        c1.number = 7;
        
        Card c2 = new Card();
        c2.kind = "Spade";
        c2.number = 4;
        
        // c1과 c2는 클래스 변수인 width와 height를 공유하기 때문에 항상 같은 값을 갖는다. 
        c1.width = 50   
        c2.height = 80
        System.out.println("c1 카드의 폭:"+ c1.width, ", c1 카드의 높이"+ c1.height);
        System.out.println("c2 카드의 폭:"+ c2.width, ", c2 카드의 높이"+ c2.height);
        
        /* 
        실행 결과
        카드의 폭: 100
        카드의 넓이: 250
        c1 카드의 폭: 50, c1 카드의 높이: 80
        c2 카드의 폭: 50, c2 카드의 높이: 80
        */
  • 클래스 변수는 클래스이름.클래스변수의 형태로 하는 것이 좋다.

인스턴스변수는 인스턴스가 생성될 때마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.

메서드

'메서드'는 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것이다.
기본적으로 수학의 함수와 유사하며, 메서드의 내부 과정은 몰라도 되고, 메서드에 넣을 값과 반환 결과만 알면 된다.

메서드를 사용하는 이유

1. 높은 재사용성

한 번 만들어 놓은 메서드는 몇 번이고 호출할 수 있으며, 다른 프로그램에서도 사용 가능하다.

2. 중복된 코드의 제거

반복되는 문장들을 메서드로 만들어서 사용하면 코드의 중복이 제거되고, 변경 사항이 발생했을 때, 이 메서드만 수정하면 되므로 관리도 쉽고 오류의 발생 가능성도 낮아진다.

3. 프로그램의 구조화

  • main메서드는 프로그램의 전체 흐름이 한눈에 들어올 정도로 단순하게 구조화하는 것이 좋다.
public static void main(String args[]){

    int[] numArr = new int[10];
    
    // 프로그램의 전체 흐름이 한눈에 들어오도록
    initArr(numArr);   // 1. 배열을 초기화
    printArr(numArr);  // 2. 배열을 출력
    sortArr(numArr);   // 3. 배열을 정렬
    printArr(numArr);  // 4. 배열을 출력
  • 프로그램 설계시, 내용이 없는 메서드를 작업단위로 만들어놓고 하나씩 완성해가는 것도 좋은 방법이다.
public static void main(String args[]){
    
    // 구조를 먼저 잡아 메서드를 작업단위로 만들어 놓고
    switch(showMenu()) {
        case1: inputRecord(); break;
        case2: changeRecord(); break;
        case3: deleteRecord(); break;
        case4: searchRecord(); break;
        default: showRecordList(); 
    
    // showMenu(), inputRecord(), ... ,showRecordList() 까지 하나씩 구현해 나가기

메서드의 선언과 구현

1. 메서드 선언부

int(=반환타입) add(=메서드이름) (int x, int y)(=타입 변수명, 타입 변수명, ...) {  // 선언부

    // 구현부: 메서드 호출시 수행될 코드
    int result = a + b;
    return result;   // return 문: 반환타입과 일치하거나 자동형변환이 가능한 것이여야 함
}

매개변수 선언 (int x, int y)

  • 메서드가 작업을 수행하는데 필요한 값들(입력)을 제공받기 위한 것으로 '지역변수'이다.

메서드 이름

  • 이름만으로도 메서드의 기능을 쉽게 알 수 있는 의미있는 이름을 지어야 한다.

반환 타입

  • 메서드의 작업 수행 결과인 '반환값'의 타입을 적는다. 반환 값이 없는 경우는 'void'를 적는다.

2. 메서드 구현부

  • 메서드 선언부 다음에 오는 괄호 안에 메서드를 호출했을 때 수행될 문장들을 넣는다.

메서드의 호출

같은 클래스 내의 메서드끼리는 참조변수를 사용하지 않고도 호출이 가능하지만, static메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없다.

class MyMathTest {
    public static void main(String args[]){
        
        // MyMath 클래스의 메서드를 호출하기 위해서 MyMath 클래스의 인스턴스를 생성한다.
        MyMath mm = new MyMath();    
        
        long result1 = mm.add(5L, 3L);
        double result2 = mm.divide(5L, 3L);
        
        System.out.println("add(5L,3L):" + result1);
        System.out.println("divide(5L,3L):" + result2);
    }
}
class MyMath {
    long add(long a, long b) { return a + b;}
    
    // a, b가 5L, 3L인 long형의 값으로 호출이 가능한 이유
    // long형인 5L가 double형인 5.0으로 자동 형변환되어 a, b에 저장되므로 연산결과가 double형이 된다.
    double divide(double a, double b) { return a / b; }
    
}

return 문

매개변수의 유효성 검사

메서드의 구현부를 작성할 때, 제일 먼저 해야 하는 일이 매개변수의 값이 적절한 것인지 확인하는 것이다. 가능한 모든 경우의 수에 대해 고민하고 그에 대비한 코드를 작성해야 한다.

float divide(int x, int y) {
    
    // 작업을 하기 전에 y가 0인지 확인한다.
    if (y == 0) { 
        return 0;   // 매개변수가 유효하지 않으므로 메서드를 종료한다.
    }
    
    return x / (float) y;

JVM의 메모리 구조

1. 메서드 영역(method area)

클래스가 사용되면, 클래스에 대한 정보를 이 곳에 저장한다. 이 때, 그 클래스의 클래스 변수도 이 영역에 함께 생성된다.

2. 힙(heap)

인스턴스가 생성되는 공간이다.

3. 호출 스택 (call stack 또는 execution stack)

메서드의 작업에 필요한 메모리 공간을 제공한다. 이 메모리는 메서드가 작업을 수행하는 동안 지역변수들과 연산의 중간결과 등을 저장하는데 사용되고, 작업을 마치면 할당되었던 메모리 공간은 반환되어 비워진다.

  • 메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당받는다.
  • 메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다.
  • 호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드이다.
  • 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드이다.
  • 반환타입이 있는 메서드는 종료되면서 결과값을 자신을 호출한 메서드에게 반환한다.

기본형 매개변수와 참조형 매개변수

  • 기본형 매개변수: 변수의 값을 읽기만 할 수 있다.
  • 참조형 매개변수: 변수의 값을 읽고 변경할 수 있다.
class Data { int x; }

class PrimitivieParamEx {

    public static void main(String[] args) {
    
        Data d = new Data();
        d.x = 10;
        Syste.out.println("x =  + d.x);   // 실행결과: x = 10
        
        change(d.x);  
        System.out.println("x = " + d.x);  // 실행결과: x = 10
    }
    
    static void change(int x) {  // 기본형 매개변수
        x = 1000;
        System.out.println("x = " + d.x);  // 실행결과: x = 1000
    }  
}
  • 실행 과정
    • 1) change메서드 호출: 'd.x'가 change 메서드의 매개변수 x에 복사됨
    • 2) change메서드: x값을 1000으로 변경
    • 3) change메서드 종료: x는 스택에서 제거됨
  • 주의: d.x의 값이 변경된 것이 아니라 메서드의 매개변수 x값이 변경된 것이다.
class Data { int x; }

class ReferenceParamEx {

    public static void main(String[] args) {
    
        Data d = new Data();
        d.x = 10;
        System.out.println("x = " + d.x);
        
        change(d);
        System.out.println("x = " + d);
    }
    
    static void change(Data d) {
        x = 1000;
        System.out.println("x = " + d.x);
    }  
}
  • 실행 과정
    • 1) change메서드 호출: 참조변수 d의 값(주소)이 매개변수 d에 복사됨
    • 2) change메서드: 매개변수 d로 x의 값을 1000으로 변경
    • 3) change메서드 종료: 매개변수 d는 스택에서 제거됨

참조형 반환타입

반환 타입이 '참조형'이라는 것은 메서드가 '객체의 주소'를 반환한다는 것을 의미한다.

class Data { int x; }

class ReferenceReturnEx {

    public static void main(String[] args) {
    
        Data d = new Data();
        d.x = 10;
        
        Data d2 = copy(d);
        System.out.println("d.x=" + d.x);
        System.out.println("d2.x=" + d2.x);
    }
    
    static Data copy(Data d) {   // 반환 값: Data 객체의 주소
   
        Data tmp = new Data();
        tmp.x = d.x;
        
        return tmp;
    }
}
  • 실행 과정
    • 1) copy메서드 호출: 참조변수 d의 값(주소)이 매개변수 d에 복사됨
    • 2) copy메서드: 새로운 객체 tmp를 생성한 다음, d.x에 저장된 값을 tmp.x에 복사함
    • 3) copy메서드 종료: 종료되면서 반환한 tmp의 값은 참조변수 d2에 저장됨
    • 4) copy메서드 종료 이후: tmp는 사라졌지만 d2로 새로운 객체를 다룰 수 있음

재귀호출(recursive call)

메서드의 내부에서 메서드 자신을 다시 호출하는 것을 '재귀호출'이라 하고, 제귀호출을 하는 메서드를 '재귀 메서드'라 한다.

  • 조건문이 필수적이다.
  • '매개변수 유효성검사'가 중요하다.
  • 호출된 메서드는 'call by value'를 통해 원래의 값이 아닌 복사된 값으로 작업하기 때문에 호출된 메서드와 관계없이 독립적인 작업수행이 가능하다.
class FactorialTest {
    static long factorial(int n) {
    
        if (n <= 0 || n > 20) return -1;  // 매개변수의 유효성 검사
        if (n ==1) return 1;
        
        return n * factorial(n-1);
    }
}  

클래스 메서드(static 메서드)와 인스턴스 메서드

인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다.

클래스 메서드는 메서드 중에서 인스턴스와 관계없는 메서드로 정의한다.

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

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

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

  • 인스턴스 변수나 인스턴스 메서드에서는 static이 붙은 멤버들을 사용하는 것이 언제나 가능하다.

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

  • 메서드 호출시간이 짧아지므로 성능이 향상된다.
class MyMath2 {

    long a, b;  // 인스턴스 변수
    
    // 인스턴스변수만을 이용해서 작업하므로 매개변수가 필요없다.
    long add() { return a + b; }
    
    // 인스턴스변수와 관계없이 매개변수로만 작업한다.
    static long add(long a, long b) { return a + b; }
}

class MyMathTest2 {
    public static void main(String args[]) {
    
        // 클래스메서드 호출: 인스턴스 생성없이 호출 가능
        System.out.println(MyMath2.add(200L, 100L));
       
        // 인스턴스메서드 호출을 위한 인스턴스 생성
        MyMath2 mm = new MyMath2();  
        mm.a = 200L;
        mm.b = 100L;
        // 인스턴스메서드는 객체 생성 후에만 호출이 가능
        System.out.println(mm.add());
    }
}

클래스 멤버와 인스턴스 멤버 간의 참조와 호출

class MemberCall {
    
    int iv = 10;
    static int cv = 20;
    
    int iv2 = cv;   // 인스턴스 변수는 클래스 변수 사용 가능
    static int cv2 = new MemberCall().iv  // 클래스 변수는 객체를 생성해야 인스턴스 변수 사용 가능
    
    // 클래스 메서드
    static void staticMethod1() {  
        
        System.out.println(cv);  // 클래스 변수 사용 가능
        
        MemberCall c = new MemberCall();  // 객체를 생성해야 인스턴스 변수 참조 가능
        System.out.println(c.iv);
    }
    
    // 인스턴스 메서드
    void instanaceMethod1() {  // 모두 바로 호출 가능
    
        System.out.println(cv);
        System.out.println(iv); 
    }
    
    // 클래스 메서드
    static void staticMethod2() {
    
        staticMethod1();  
        
        MemberCall c = new MemberCall();  // 객체를 생성해야 인스턴스 메서드 호출 가능
        c.instanceMethod1();
    }
    
    // 인스턴스 메서드
    void instanceMethod2() {  // 모두 바로 호출 가능
    
        staticMethod1();
        instanceMethod1();
    }
}
  • 클래스 멤버(클래스변수와 클래스메서드): 언제나 참조 또는 호출이 가능
  • 인스턴스 멤버: 반드시 객체를 생성한 후에만 참조 또는 호출이 가능
    • 인스턴스 멤버간의 호출에는 문제가 없다. why?
      : 하나의 인스턴스멤버가 존재한다는 것은 인스턴스가 이미 생성되어 있다는 것을 의미하기 때문이다.
profile
🌱 주니어 백엔드 개발자입니당

0개의 댓글