JAVA 변수와 메서드

Dev.Shinny·2022년 9월 22일
0

자바의 정석

목록 보기
3/18

변수의 종류

변수(variable)는 클래스 변수, 인스턴스 변수, 지역 변수 총 세 종류가 있다.

변수는 종류를 결정 짓는 중요한 요소는 '변수의 선언된 위치'이다. 멤버 변수를 제외한 나머지 변수들은 모두 지역변수이며, 멤버변수 중 static이 붙은 것은 클래스 변수, 붙지 않은 것은 인스턴스 변수이다.

class Varivables{
	int instanceV ; // 인스턴스 변수
    staic int classV ; // 클래스 변수
    
    void method(){
		int localV = 0 ; //지역 변수
    }
}

1. 인스턴스 변수 (instance variable)

클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어진다. 인스턴스는 독립적인 저장공간을 가지므로, 서로 다른 값을 가질 수 있다. 인스턴스마다 고유한 상태로 유지해야 하는 속성의 경우 인스턴스 변수로 선언한다.

2. 클래스 변수 (class variable)

클래스 변수를 선언하는 방법은 인스턴스 변수 앞에 static을 붙이기만 하면 된다. 클래스 변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유한다. 클래스 변수는 인스턴스를 생성하지 않고도 바로 사용할 수 있다. 클래스가 메모리에 '로딩'될 때 생성되어 프로그램이 종료될 때까지 유지된다. public을 붙이면 같은 프로그램 내에서 어디서나 접근할 수 있는 '전역 변수(gloval variable)'의 성격을 갖는다.

3. 지역 변수 (local variable)

메서드 내에 선언되어 메서드 내에서만 사용가능하며, 메서드가 종료되면 소멸한다.

메서드

메서드(method)는 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것이다.

메서드는 수학의 함수와 비슷하지만, 입력값 또는 출력값이 없을 수도 있으며, 심지어 모두 없을 수도 있다.

메서드를 사용하는 이유

  1. 높은 재사용성
  2. 중복된 코드제거
  3. 프로그램의 구조화

메서드 선언과 구현

메서드는 크게 '선언부(header)'와 '구현부(body)'로 이루어져 있다.

반환타입 메서드명 ( 타입 변수명, 타입 변수명, ...)
{
	// 메서드 호출시 수행될 코드
}

int add ( int a, int b )
{
	int result = a + b ;
    return result; 
}

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

반환타입이 'void'가 아닌 경우, 구현부{}안에 'return 반환값;'이 반드시 포함되어야 한다. return문은 단 하나의 값만 반환할 수 있다. 매개변수는 여러개 일 수 있어도 출력은 최대 하나이다.

메서드 내에 선언된 변수들은 그 메서드 내에서만 사용할 수 있는 '지역 변수'이므로 서로 다른 메서드라면 같은 이름의 변수를 선언해도 된다.

	int add(int x, int y){
    	int result = x+y;
        return result;
    }
    int multiply(int x, int y){
    	int result = x*y;
        return result;
    }
        

위에 정의된 두 개의 메서드에서 선언된 변수 x, y, result는 이름만 같을 뿐 서로 다른 변수이다.

메서드의 호출

메서드를 정의하고 사용할 때는 원하는 메서드를 호출해야만 구현부의 문장들이 수행된다.

// 메서드 호출 방법
메서드명 (1,2, ...);
printAll(); //void printAll() 메서드 호출
int result = add(3, 5); 
// int add(int x, int y)를 호출하고, 결과를 result 변수에 저장

메서드를 호출할 때 괄호()안에 지정해준 값들을 '인자(argument)' 또는 '인수'라고 하는데, 인자의 개수와 순서는 호출된 메서드에 선언된 매개변수와 일치해야 한다.

메서드의 실행흐름

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

return문

반환값이 있을 때만 return문을 썼지만, 원래는 반환값 유무에 관계없이 모든 메서드에는 적어도 하나의 return문이 있어야 한다. 문제가 없었던 이유는 반환타입이 void인 경우, 컴파일러가 메서드 마지막에 'return'을 자동적으로 추가해주었기 때문이다.

리턴문에는 변수가 아닌 수식이 올 수도 있다. 자동적으로 수식의 결과값을 반환해준다.

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

매개변수 유효성 검사

메서드 구현부 작성시, 가장 먼저 해야 하는 일이 매개변수의 값이 적절한 것인지 확인하는 것이다. 호출하는 쪽에서 적절한 값을 넘겨주겠다는 생각을 가져서는 안 된다. 타입만 맞으면 어떠 값도 매개변수를 통해 넘어올 수 있기 때문에, 가능한 모든 경우의 수에 대해 고민하고 그에 대비한 코드를 작성해야 한다.

float divide(int x, int y){
// 작업을 하기 전 나누는 수 y가 0인지 확인해야 한다.
	if(y == 0){
		System.out.println("0으로 나눌 수 없습니다.");
	return 0;
    }
 return x/(float)y;
}

적절하지 않은 값이 매개변수를 통해 넘어온다면 매개변수의 값을 보정하던가, 보정하는 것이 불가능하다면 return문을 사용해 작업을 중단하고 호출한 메서드로 되돌아가야 한다. 유효성 검사는 간과하기 쉬운 중요한 부분이다.

JVM 메모리 구조

응용 프로그램이 실행되면, JVM은 시스템으로부터 프로그램을 수행하는데 필요한 메모리를 할당받고 JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.

그 중 3가지 주요 영역(method area, call stack, heap)이 있다.

1. 메서드 영역(Method area)

프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일(.class)을 읽어서 분석하여, 클래스에 대한 정보(클래스 데이터)를 이곳에 저장한다. 이 때, 그 클래스의 클래스변수도 함께 생성된다.

2. 힙(Heap)

인스턴스가 생성되는 공간이다. 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성된다. 즉, 인스턴스 변수들이 생성되는 공간이다.

3. 호출스택(call stack)

호출 스택은 메서드의 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출되면, 호출스택에 호출된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간결과 등을 저장하는데 사용된다. 메서드가 작업을 마치면 할당되었던 메모리 공간은 반환되어 비워진다.

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

반환타입(return type)이 있는 메서드는 종료되면서 결과값을 자신을 호출한 메서드에게 반환한다. 대기상태에 있던 호출한 메서드는 넘겨받은 반환값으로 수행을 계속 진행하게 된다.

기본형과 참조형 매개변수

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

메서드의 매개변수를 기본형으로 선언하면 단순히 저장된 값을 얻지만, 참조형으로 선언하면 값이 저장된 곳의 주소를 알 수 있기 때문에 값을 읽어 오는 것은 물론 값을 변경하는 것도 가능하다.

참조형 반환타입

반환타입도 참조형이 될 수 있다. 즉 반환하는 값의 타입이 참조형이라는 말로, 참조형 타입의 값은 '객체의 주소'이므로 그저 정수값이 반환되는 것이다.

class Data {int x};

class ReferenceReturn {
	public static void main(String[] args){
    Data d = new Data();
    d.x = 100;
    
    Data d2 = copy(d);
    System.out.println("d.x="+d.x);
    System.out.println("d2.x"+d2.x);
}

static Data copy(Data d){
	Data tmp = new Data();
    tmp.x =d.x;
    
    return tmp;
  }
}

실행결과
d.x = 10
d2.x =10

재귀호출

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

void method(){
	mthod(); // 메서드 자신을 호출.
}

위 코드는 무한히 자기 자신을 호출하기 때문에 무한 반복에 빠지게 된다. 따라서 재귀호출은 조건문이 필수다. 메서드는 호출하는 것은 반복문보다 더 많은 과정을 거치므로 재귀호출이 수행시간이 더 오래 걸린다. 그럼에도 재귀호출을 사용하는 이유는 논리적 간결함 때문이다.


< 팩토리얼! 코드(1) >
static int factorial (int n){
	int result = 0;
    if( n == 1 ) 
    	ruslt = 1;
    else
    	result = n*factorial(n-1);
    reutrn result;
}

< 팩토리얼! 코드(2) >

public class Factorial {

	static long factorial (int n) {
		if(n<=0 || n>10) return -1;
		if(n<=1) return 1;
		return n*factorial(n-1);
	}
	
	public static void main(String[] args) {
		int n = 11;
		long result = 0;
		
		for(int i=1; i<=n;i++) {
			result = factorial(i);
			
			if(result == -1) {
				System.out.println("유효하지 않은 값입니다.");
				break;
			}
			System.out.printf("%2d!=%20d\n",i,result);
		}
	}
}

결과값


< X의 1~N제곱의 합 구하기 >

public class ThePower_X {

	public static void main(String[] args) {
		int x =2;
		int n =5;
		long result = 0;
		
		for(int i=1; i<=n;i++) {
			result +=power(x,i); 
		}
		System.out.println(result);
	}
	
	static long power(int x, int n) {
		if(n==1) return x;
		return x*power(x,n-1);
	}

}

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

클래스 메서드는 객체를 생성하지 않고도 '클래스명.메서드이름(매개변수)'와 같은 식으로 호출 가능하다. 반면에 인스턴스 메서드는 반드시 객체를 생성해야 호출할 수 있다.

클래스는 '데이터(변수)와 데이터에 간련된 메서드의 집합'이므로, 같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있다.

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

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

  • 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
  • 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있다.
  • 클래스 메서드는 인스턴스 변수를 사용할 수 없다.
  • 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.

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

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

인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수 있기 때문이다.

인스턴스 멤버간의 호출에는 아무런 문제가 없는데, 하나의 인스턴스 멤버가 존재한다는 것은 인스턴스가 이미 생성되었다는 것을 의믜하며 즉 다른 인스턴스 멤버들도 모두 존재하기 때문이다.


참고

ClassA a = new ClassA();
int test = a.exampleMethod();
(ClassA라는 클래스가 있고 클래스 안에 exampleMethod()라는 메소드가 있다고 가정하자)

위 두 줄을 int result = new ClassA().exampleMethod();로 작성할 수 있다. 대신 참조변수를 선언하지 않았기 때문에 생성된 ClassA 인스턴스는 더 이상 사용 할 수 없다.

변수의 초기화

변수의 초기화는 경우에 따라서 필수적이기도 하고 선택적이기도 하지만, 가능하면 선언과 동시에 적절한 값으로 초기화 하는 것이 바람직하다.

멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지므로 초기화하지 않고 사용해도 되지만, 지역변수는 사용하기 전에 반드시 초기화해야한다.

멤버변수(클래스 변수와 인스턴스 변수)와 배열의 초기화는 선택적이지만, 지역변수의 초기화는 필수적이다.

각 타입의 기본값(default value)

int x = 10, long y=0; // error _ 타입이 다른 변수는 함께 선언하거나 초기화 할 수 없다.

멤버변수의 초기화 방법

  1. 명시적 초기화(explicit initialization)
  2. 생성자(constructor)
  3. 초기화 블럭(initialization block)
    -인스턴스 초기화 블럭
    -클래스 초기화 블럭

명시적 초기화

변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다. 가장 기본적이기 때문에 초기화 방법 중 가장 우선적으로 고려되어야 한다.

명시적 초기화가 간단하고 명료하지만, 보다 복잡한 초기화 작업이 필요할 때는 '초기화 블럭(initialization block)'또는 생성자를 사용해야 한다.

초기화 블럭

클래스 초기화 블럭 : 클래스 변수를 초기화 하는데 사용.
인스턴스 초기화 블럭 : 인스턴스 변수를 초기화 하는데 사용

인스턴스 초기화 블럭은 단순히 클래스 내에 블럭{}만들고 그 안에 코드를 작성하기만 하면 된다. 클래스 초기화 블럭은 인스턴스 초기화 블럭에 static을 덧붙이기만 하면 된다.

class InitBlock{
	static { /* 클래스 초기화 블럭 */}
    
    { /* 인스턴스 초기화 블럭 */}
	//....
}

클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한번만 수행되며, 인스턴스 초기화 블럭은 생성자와 같이 인스턴스가 생성할 때마다 수행된다. 그리고 생성자보다 인스턴스 초기화 블럭이 먼저 수행된다.

인스턴스 변수의 초기화는 주로 생성자를 사용하고, 인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행돼야 하는 코드를 넣는데 사용한다.

class BlockTest{
	static {
    	System.out.println("static {}");
    }
    {
    	System.out.println("{}");
    }
    public BlockTest(){
    	System.out.println("생성자");
    }
    public static void main(String args[]) {
    	System.out.println("BlockTest bt = new BlockTest();");
        BlockTest bt = new BlockTest();
        
        System.out.println("BlockTest bt2 = new BlockTest()");
        BlockTest bt2 = new BlockTest();
    }
}

결과


멤버변수의 초기화 시기와 순서

클래스 변수의 초기화 시점 : 클래스가 처음 로딩될 때 단 한번 초기화.
인스턴스 변수의 초기화 시점 : 인스턴스가 생성될 때마다 각 인스턴스별로 초기화.

클래스 변수의 초기화 순서 : 기본값 → 명시적초기화→ 클래스 초기화 블럭
인스턴스 변수의 초기화 순서 : 기본값 → 명시적초기화 → 인스턴스 초기화 블럭 → 생성자

class Product{
	static int count = 0;
	int serialNo;
	
	{ //Product인스턴스가 생성될 때마다 count의 값을 1씩 증가시켜서 serialNo에 저장한다. 
		++count;  
		serialNo = count;
	}
	public Product() {} //기본생성자, 생략 가능
}

class ProductTest {
	public static void main(String[] args) {
		Product p1 = new Product();
		Product p2 = new Product();
		Product p3 = new Product();
		
		System.out.println("p1의 제품번호(serial no)는 "+p1.serialNo);
		System.out.println("p2의 제품번호(serial no)는 "+p2.serialNo);
		System.out.println("p3의 제품번호(serial no)는 "+p3.serialNo);
		System.out.println("생산된 모든 제품의 수: "+Product.count);		
	}
}

count를 인스턴스 변수로 선언했다면, 인스턴스가 생성될 때마다 0으로 초기화 될 것이므로 모든 Product 인스턴스의 SerialNo값은 항상 1이 될 것이다.







남궁성 선생님의 '자바의 정석'을 요약한 글입니다.

profile
Hello I'm Shinny. A developer who try to enjoy the challenge.

0개의 댓글