숑숑이의 개발일기

본글은 'Do it 자바 완전 정복' 책을 통한 공부내용을 정리한 글입니다.

 

필드

필드와 지역 변수의 구분

필드(field) 클래스에 포함된 변수로, 객체의 속성값을 지정할 수 있다. 지역 변수메서드에 포함된 변수를 말한다. 즉, 클래스의 중괄호 안에 선언된 변수를 필드, 메서드의 중괄호 안에 선언된 변수는 지역변수라고 한다. 필드는 힙 메모리에, 지역 변수는 스택 메모리에 생성된다. 스택 메모리의 변수(지역변수)는 자신이 선언된 메서드의 중괄호가 닫혀 메서드가 종료되면, 그 메서드 안에 선언된 모든 지역 변수가 메모리에서 삭제된다.

 

필드와 지역 변수의 초깃값

둘의 또 다른 차이점은 초깃값이다. 필드는 직접 초기화 하지 않아도 강제로 초기화된다. 반면, 지역 변수는 직접 초기화 하지 않으면 빈 공간 그대로 있는다. 이는 각각 위치하는 메모리 영역의 특징이다.

class A {
	int m;
    int n;
    void work1() {
    	int k;
        System.out.println(k);	// 초기값이 없어 오류 발생
    }
}

 

메서드

클래스의 내부 구성 요소 두번째. 메서드를 알아보자

 

메서드 정의

메서드는 클래스의 기능에 해당하는 요소로 예를 들어 사람 클래스에서 자기, 먹기, 공부하기 등이 메서드로 구성되는 것이다.

자바 제어자 리턴(반환) 타입 메서드명(입력매개변수) {
	메서드 내용
}

public static int sum(int a, int b) {
	// 메서드 내용
}

리턴 타입은 메서드 종료 이후 반환되는 값의 자료형을 의미한다. 소괄호 안의 입력매개변수는 메서드 호출 시 전달되는 값의 자료형과 전달받은 값을 저장할 지역변수명을 정의한다.

 

여러 리턴 타입의 메서드를 살펴보자

// 리턴 타입이 void, 입력매개변수가 없는 메서드
void print () {
	System.out.print("안녕");
}

// 리턴 타입이 int, 입력매개변수가 없는 메서드
int sum() {
	return 3;
}

// 리턴 타입이 double, 입력매개변수가 2개인 메서드
double sum(int a, double b) {
	return a + b;
}
메서드의 리턴타입이 void일때 return 키워드는 해당 메서드를 종료하라는 의미다.

 

메서드 호출

메서드의 호출은 크게 클래스 외부, 내부 호출로 나뉜다.

 

클래스 외부에서의 메서드 호출

클래스 외부에서 메서드를 사용하려면 먼저 객체를 생성하고 호출한다. 리턴 타입이 void인 메서드의 경우 그대로 메서드만 호출하면 된다. a.print() 리턴값이 있는 메서드는 리턴되는 값을 저장할 수 있는 변수를 선언해 대입한다. 입력매개변수가 있는 함수를 호출할 때는 해당 자료형의 값을 넘겨 줘야 한다. a.sum(5, 2.5)

 

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

클래스 내부에 있는 메서드끼리는 객체를 생성하지 않고 서로를 호출할 수 있다. 

 

기본 자료형, 참조 자료형 입력매개변수의 차이

twice()메서드의 실행을 완료하고 main() 함수로 돌아온 시점에는 메모리에서 twice() 프레임 자체가 날아간 이후이므로 twice() 메서드의 지역 변수 a는 메모리상에 존재하지 않는다.

=> 기본 자료형을 입력매개변수로 전달하면 전달받은 메서드는 값을 복사해 사용한다

public static void main(String[] ar) {
	int a = 3;
    int k1 = twice(3);
    int k2 = twice(a);
    
    System.out.println(k1);	// 6    
    System.out.println(k2);	// 6   
    System.out.println(a);	// 3
}

public static int twice(int a) {
    a*=2;
    return a;
}

 

반면 참조 자료형을 입력매개변수로 넘겼을 때에는 매개변수로 넘겨진 변수의 스택 메모리값이 복사되어 사용되는 것은 동일하나 참조 자료형은 스택 메모리에 객체의 참좃값(위칫값)을 저장하고 있으므로 실제 객체가 아닌 참좃값이 전달되어 복사된다.

=> 참조 자료형을 입력매개변수로 전달 후 호출한 메서드로 돌아오면 값이 바뀌게 됨

public static void main(String[] ar) {
	int[] array = new int[] {1, 2, 3};
    modifyData(array);
    printArray(array);
}

public static void modifyData(int[] a) {
    a[0] = 4;	a[1] = 5;	a[2] = 6;
}

public static void printArray(int[] a) {
	System.out.println(Arrays.toString(a));
    // 4, 5, 6
}

 

오버로딩 메소드

먼저 메서드 시그너처(method signature)의 의미를 알아보자. 메서드 시그너처는 메서드명과 입력매개변수의 자료형을 말한다. 메서드를 구분하는 기준 역할을 한다. 자바 가상 머신은 메서드 시그너처가 다르면 메서드명이 동일해도 다른 메서드로 인식한다. 메서드 오버로딩은 입력매개변수의 개수나 자료형이 다른 여러 개의 동일한 이름을 지닌 메서드를 같은 공간에 정의하는 것을 말한다. 시그너처에는 리턴 타입이 포함되어있지 않다. 호출 과정에서는 리턴 타입을 사용하지 않기 때문에 리턴 타입으로는 메서드를 구분할 수 없다. 실전 예시로, 3개의 동일한 이름을 가진 이미지 abc.jpg, abc.png, abc.bmp를 동일한 폴더에 저장할 수 있는 것과 같은 원리다.

public static void print() {
	System.out.println(1);
}

public static void print(int a) {
	System.out.println(a);
}

public static void print(double a) {
	System.out.println(a);
}

 

가변 길이 배열 입력매개변수 메서드

만일 어떤 메서드가 입력 매개변수로 어떠한 숫자 사이의 int 자료형 값을 받는다고 가정하자. 정확히 몇 개의 입력이 들어올지 모르므로 모든 메서드를 오버로딩해야한다. 이를 간단하게 할 수 있는 방법이 가변 길이 배열 입력매개변수다. 개수가 정해지지 않은 가변 길이의 입력을 받는 입력매개변수로 입력값들은 배열로 저장된다. 해당 배열의 크기는 함수가 호출될 떄 전달된 입력값의 개수로 정해진다.

=> 가변 길이 배열 입력매개변수를 사용해 1개의 메서드로 모든 메서드 호출에 대응할 수 있다

리턴 타입 메서드명 (자료형... 참조 변수명) {
	//	메서드 내용
}

public static void main(String[] args) {
	method1(1, 2);	// 길이 2
    method1(1, 2, 3);	// 길이 : 3
    method1();	// 길이 0
    
    method2("안녕", "방가");	// 길이 2
    method2("안녕", "방가", "떙큐");	// 길이 3
    method2();	// 길이 0
}

public static void method1(int... values) {
	System.out.println()("입력매개변수 길이 : " + values.length);
    for(int i = 0; i < values.length; i++) {
    	System.out.print(values[i]);
    }
}

public static void method2(String... values) {
	System.out.println()("입력매개변수 길이 : " + values.length);
    for(int i = 0; i < values.length; i++) {
    	System.out.print(values[i]);
    }
}

 

생성자

생성자(constructor)는 생성하는 역할을 지닌 클래스의 내부 구성요소다. 객체 내에 포함되는 필드의 초기화 또는 생성자 내에서 수행한다.

 

생성자의 특징

1) 반드시 클래스 명과 동일한 이름으로 지어야 한다.

클래스명과 다르게 지으면 JVM은 메서드로 인식하여 오류를 발생시킨다.

 

2) 리턴 타입이 없다.

 

컴파일러는 생성자가 없는 클래스를 만들면 입력 매개변수가 없는 기본 생성자를 추가해준다. 일반적으로 생성자의 실행문에서는 필드를 초기화 한다.

 

생성자와 객체의 생성 방법

생성자의 모양에 따라 객체를 생성하는 방법이 결정된다. 생성자도 메서드처럼 오버로딩을 할 수 있다. 아래 코드와 같이 클래스 A에 서로 다른 내용의 생성자 3개가 오버로딩 되어있다면 클래스 A로 객체를 만드는 3가지 방법이 존재하는 것이다.

class A{
	A() {
    	// 디폴트 생성자
    }
    A(int a){
    	// 두번째 생성자
    }
    A(int a, int b) {
    	// 세번쨰 생성자
    }
}

public class constructorEx {
	public static void main(String[] args) {
    	A a1 = new A();	// 디폴트 생성자로 a1 생성
        A a2 = new A(2);	// 두번째 생성자로 a2 생성
        A a3 = new A(3, 5);	// 세번쨰 생성자로 a3 생성
    }
}

 

this 키워드와 this() 메서드

클래스 내부에서는 객체의 생성 없이 필드와 메서드를 바로 사용할 수 있다. 모든 사용할 수 있는 상태의 멤버는 항상 객체 속에만 존재한다고 했는데 어떻게 클래스 내부에서는 객체를 생성하지 않고 바로 필드와 메서드를 사용할 수 있을까?

 

this 키워드

모든 메서드에는 자신이 포함된 클래스의 객체를 가르키는 this라는 참조 변수가 있다. 예를 들어, int m = 3이라는 필드를 클래스 내부에서 출력하고자 할때는 System.out.println(this.m)과 같이 작성해야 한다. 그러나 this를 생략했을 때 컴파일러가 자동으로 this를 추가해주기 때문에 지금까지 객체를 생성하지 않고 그대로 사용할 수 있었던 것.

 

this.를 명시적으로 붙여줘야 할 때가 있다. 다음 코드를 봐보자.

class A {
	int m;
    int n;
    void init(int m, int n) {
    	m = m;
        n = n;
    }
}

메서드 안에서 작성한 값을 컴파일러는 모두를 지역 변수로 인식하므로 this.가 추가되지 않는다. 지역 변수에 지역 변숫값을 다시 대입하는 형태이므로 필드값의 변화도 없다. 따라서 아래와 같이 this.를 추가하여 사용한다.

 

class A {
	int m;
    int n;
    void init(int m, int n) {
    	this.m = m;
        this.n = n;
    }
}

이렇게 수정하면 this.m은 필드로, m은 지역 변수로인식하여 정상동작 된다.

 

this() 메서드

this() 메서드는 자신이 속한 클래스 내부의 다른 생성자를 호출하는 명령이다. this() 메서드를 구성할 때에는 반드시 아래의  2가지의 문법적 규칙을 지킨다. 

1) 생성자의 내부에서만 사용할 수 있다.

2) 생성자의 첫 줄에 위치해야 한다.

또한 메서드 내부에서는 this()메서드를 사용할 수 없다.

 

아래의 예시코드를 보자. 생성자가 2개이므로 객체를 생성하는 방법 또한 2가지다.

class A {
	A() {
    	System.out.println(1);
    }
    
    A(int a) {
    	this();	// 반드시 생성자의 첫 줄에 위치
    	System.out.println(2);
    }
}

public class ThisMethod1{
	public static void main(String[] args) {
    	A a1 = new A();	// 첫 번째 생성자 호출
        System.out.println();
        A a2 = new A(3);	// 두번째 생성자 호출
        
        // 결과 : 1 1 2
    }
}

 

이러한 문법 요소가 생긴 이유가 무엇일까? 위에서 생성자의 주요 역할은 필드를 초기화하는 것이라고 했다. 여러가지 생성자가 존재할 때에 각 생성자마자 중복되는 코드를 많이 포함할 수 있다. 이때 첫 번째 생성자를 호출하여 생성자의 중복을 제거할 수 있다.

class A {
	int m1, m2, m3, m4;
    A() {
    	m1 = 1;
        m2 = 2;
        m3 = 3;
        m4 = 4;
    }
    A(int a) {
    	this();
        m1 = a;
    }
    A(int a, int b) {
    	this(a);
        m2 = b;
    }
}
profile

숑숑이의 개발일기

@숑숑-

풀스택 개발자 준비중입니다