[JAVA] 객체 지향 프로그래밍 Ⅰ 정리 - (2)

DongGyu Jung·2022년 1월 18일
0

자바(JAVA)

목록 보기
9/60
post-thumbnail

🏃‍♂️ 들어가기 앞서..

본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 Page : 〔투 비 마스터 : 자바〕

*해당 교재의 목차 순서와 구성을 참고하여 작성하며
각 내용마다 부족할 수 있는 내용이나 개인적으로 궁금한 점은
추가적인 검색을 통해 채워나갈 예정입니다.


이번에는 저번 정리(객체 지향 프로그래밍 Ⅰ 정리 - (1))에 이어서

객체의 구성요소이자
클래스에서 정의하는 변수메서드에 대해 자세히 알아보도록 하자.


🧱 변수

변수에는 " 클래스변수 "/ " 인스턴스변수 " / " 지역변수 " 총 3가지가 있는데

이렇게 분류하게 된 기준은 바로
변수의 선언위치 이다.

그렇기 때문에 종류를 파악하기 위해선
변수가 어느 영역에서 선언되어 있는지 살펴봐야하는데

일단
멤버 변수를 제외한 나머지 변수들은 " 지역변수 "

(멤버 변수 中) static이 붙은 변수는 " 클래스 변수 "
(멤버 변수 中) static이 붙지 않은 변수는 " 인스턴스 변수 "이다.

  • 인스턴스변수
    : 인스턴스를 생성할 때 만들어지는 변수로
    각 인스턴스마다 별도의 저장공간을 가져 서로 다른 값을 가질 수 있다.
    (" 인스턴스마다 고유한 상태 유지가 필요할 경우 " )

  • 클래스변수
    : 인스턴스 변수 앞에 static을 붙이면 클래스 변수가 되며
    인스턴스를 생성치 않고 언제라도 바로 사용 가능하다 ▶ (클래스 이름).(클래스 변수)
    모든 인스턴스가 공통된 저장공간을 가져서 같은 클래스 내 모든 인스턴스들이 공통의 값을 가진다.
    (" 인스턴스들의 공통적인 값을 유지가 필요할 경우 " )

  • 지역변수
    : 메서드 내에 선언되어서
    메서드 안에서만 사용 가능한 변수로
    종료되거나 선언된 블럭을 벗어날 경우 자동 소멸된다.

◎ 클래스 변수 vs 인스턴스 변수

같은 멤버 변수이기도 하고 말로만 보면 조금 이해가 안될 수도 있다.
우리 말로 해석해보자면 " [개별 속성] " 과 " [공통 속성] "이다.

시리즈물 도서》를 예로 들어보자면

같은 시리즈 도서일 경우,
모든 책의 폭과 높이는 같을 것이다.
만약 폭과 높이를 수정해야하면 모든 시리즈 도서의 사이즈도 모두 바뀌어야 할 것이다.

여기서 이렇게 바뀌면 같이 바뀌고 공통으로 가진
폭과 높이같은 값들을 담은 변수를
【클래스 변수】라고 생각하면 되고

같은 시리즈물이더라도
각 책마다의 페이지 수는 다를 것이다.
이렇게 각 각의 책마다의 페이지 수 같이 고유한 값을 가진 것을
【인스턴스 변수】 라고 생각하면 된다.


위에서 설명했듯이
클래스 변수는 공통된 저장공간을 가지기 때문에
중간에 수정하고
다른 인스턴스에서 클래스 변수를 출력하면 수정된 값이 나온다.

그렇기 때문에
물론 인스턴스.클래스변수로도 출력이 되지만
클래스 변수를 사용할 때는
클래스.클래스변수의 형식으로 출력하는 것이 좋다.


📃 메서드

특정 작업을 수행하는 일련의 코드 모음

대부분의 메서드는 입력값(input)에 대해 특정 작업을 수행한 후 결과값(output) 출력/반환한다.
( 물론 입력값이나 결과값이 없는 메서드도 생성할 수 있다. )

일단 구조를 먼저 짚고 넘어가자면
" 선언부(header) " 와 " 구현부(body) "로 이루어져 있고
" 메서드를 정의하다."라는 표현은
" 선언부와 구현부를 작성한다."라는 표현과 같다.

//선언부
(반환 Type) Method Name ( [(Type)] [Variable Name], ...)
//구현부
{
	// 해당 메서드 호출시 실행될 코드
}

int Add(int a, int b) // 선언부
{   //구현부
    int result = a + b;
    return result;
}

※ 선언부 _ header

메서드 수행에 있어
" 어떤 값들이 필요한지 "
" 어떤 타입으로 결과를 반환하는지 " 에 대한 정보를 입력하는 부분

메서드의 선언부는
이후에 변경사항이 발생하지 않도록 주의하고 신중히 작성해야 한다.

변경을 하기 위해선
해당 메서드가 호출되는 모든 곳의 코드들이 메서드에 맞춰 함께 변경되야 하기 때문이다.

◎ 매개변수 (Parameter) 선언

: " 메서드의 작업 수행에 필요한 입력값들을 정해주는 작업 "

필요한 값의 개수만큼 쉼표로 구분하여 선언해주어야하고

일반적인 변수 선언과는 다르게
각 각의 변수에 타입 지정이 필수이다.

  • 필요 입력값이 많은 경우 : 배열 / 참조변수 사용
  • 필요 입력값이 없는 경우 : 빈 괄호 가능

◎ 반환타입 (return type)

: " 작업 수행 결과값/반환값(return value)의 타입을 작성하는 작업 "

반환값이 없는 경우 : 반환 타입으로 void를 적어야한다.
(여태까지 다루었던 예제들의 경우가 이에 해당한다. 구현부 안에서 북치고 장구치고 변수 선언부터 출력까지 모두 하는 경우엔 입출력값 지정이 필요없다.)



※ 구현부 _ body

선언부 다음에 오는 " 괄호(블럭){} 부분 "
메서드를 호출했을 때 " 수행될 문장들 "

◎ 지역변수 (local variable)

* 선언부에서 선언했던 메서드 작업을 위한 매개변수도 지역변수이다.

메서드 내부에서만 사용하고 메서드가 종료되면 자동으로 소멸된다.

return

< 반환타입이 void가 아닐 경우>
구현부에는 return 반환값 무조건 작성되어야 한다.
또한,
메서드에는 하나의 출력값만을 허용한다.

이 메서드 수행 결과값의 타입
앞서 선언부에서 지정했던 반환값 타입일치하거나 자동 형변환이 가능해야한다.



※ 호출

★ 【main메서드】는 프로그램 실행 시 자동 호출

일반적인 메서드 호출방식은 메서드명()이다.

◎ "인수 (argument)" | "매개변수(parameter)"

메서드를 공부하다보면 인수와 매개변수의 차이가 궁금해지는데
차이점은 바로
" 메서드를 선언하는 경우이냐 "와 " 메서드를 사용하는 경우이냐 "의 차이인 것 같다.

선언할 때 메서드에 입력될 값의 타입과 값 개수를 정하는데
이 때,
해당 메서드에 필요한 입력값
즉, 인수가 들어갈 그 자리를 매개변수라 부른다.

예를 들자면
회의를 할 때,
진짜 앉는 사람은 인수

회의 시작 전에
이 자리에 앉는 사람의 기준, 누구 자리인지 붙여놓은 이름표를
매개변수라고 보면 비슷할 것이다.

신입이 사장 자리에 앉으면 짤리는 것처럼
회의가 진행될 때,
" 각자의 자리에 맞게 앉아야되는 것 "이고
" 준비한 자릿수보다 들어온 사람이 많으면 안되는 것 "처럼

이걸 다시 프로그래밍 입장에서 보았을 때
*회의 = 호출
*회의 전 준비한 각자의 자리 = 매개변수
*그 자리에 착석한 자리 주인 = 인수

《메서드를 호출할 때》
괄호() 안에 지정해준 값들을 인수/인자(argument)라 하는데
이 인수는 선언된 매개변수와 일치해야하고

인수는 호출되면서 매개변수에 대입되기 때문에
Argument Type == Parameter Type 이 이루어져야 한다.
( 최소 형 변환 가능 )

또한,
< 매개변수의 개수보다 많은 값이 인수로 입력 > 되었거나
< 인수의 타입이 매개변수의 타입과 다른 값 > 일 경우
에러가 발생한다.

◎ 흐름

이제 메서드 클래스를 선언하는 것부터
어떻게 대입되고 실행되는지

위 설명들과 곁들여서 정리해보자.

// 클래스 = 회사
// 메서드 = 회의실
// 매개변수 = 지정석
// 실행문 = 회의 진행 내용

// 호출 = 회의 시작
// 인수 = 지정석 착석자


/* class & method 선언 */
class Culculate {
	long add(long a, long b) {
    		long result = a + b ;
        	return result ;
    	}
        long subtract(long a, long b) {
        	long result = a - b ;
        	return result ;
        }
        // return 문으로 간단한 연산하는 것도 가능
        long multiply(long a, long b) { return a * b; }
        double divide(double a, double b) { return a / b; }    
}

/* 호출 */
Culculate culc = new Culculate() ;

// 타입 맞춰주기 (long)
long value1 = culc.add(1L, 2L);
double value2 = culc.divide(5L, 3L); // 일치하진 않지만 형변환도 가능 _ long -> double

System.out.println("1L + 2L = " + value1);
System.out.println("5L + 3L = " + value2); // long 타입으로 인수를 입력했지만 double로 형변환 가능

return

return문은 뜻 그대로
실행 중인 메서드를 종료하고 호출한 메서드로 되돌아가게 해준다.

모든 메서드는 최소한 하나의 return문이 있어야 하는데
겉으로 봤을 때 return문이 없는 메서드가 있다.

그 경우는 바로
" 반환타입이 void인 메서드 "의 경우이다.

사실 없는 것이 아니라
컴파일러가 자동으로 마지막에 return ;을 추가하기 때문에
문제가 발생하지 않는 것이다.
( 그렇게 때문에 void가 아닌 경우에 return이 없는 경우 에러가 발생한다. )

또한
" 조건식을 사용하는 메서드 "의 경우,
작성한 모든 조건식 결과에 대해
return문을 작성해야 한다.

➰ 반환값

앞서 "흐름"에 대해 알아봤을 때
divide메서드의 return문을 살펴보면
return문에 직접 실행문을 적어 놓은 것을 볼 수 있다.

이처럼
수식이 반환되는 것이 아니고 수식의 결과가 반환되는데

이런 기능을 통해
간단한 메서드의 경우,
" 조건 연산자를 이용한 식 "을 바로 입력할 수 있다.

int abs(int x) {
	if (x >= 0) {
    		return x ;
    	}else {
        	return -x ;
        }
}

// 위 메서드를 간단하게
// 조건연산자 식으로 바로 return문에 입력
int abs(int x) {
	return x>=0 ? x : -x ;
}

💡 호출 스택 (call stack)

메서드의 작업에 할당받은 필요 메모리 저장 공간

  • 호출 ▶ 수행에 필요한 만큼 메모리 ▶ 스택 (할당)
  • 수행 완료 ▶ 메모리 반환 ▶ 스택 제거
  • 스택 최상단(top) 메서드 == 현재 실행 중인 메서드
  • 아래 메서드 == 바로 위의 메서드를 호출한 메서드
    : 메서드가 종료되면서 결과값을 자신을 호출한 아래 메서드에 반환

※ 매개변수 _ parameter

앞서 설명했듯
메서드를 호출할 때
매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨주는데

매개변수 타입에 따라
" 복사되는 값 "이 달라진다.

" 기본형 "과 " 참조형 "
두 가지로 나뉠 수 있다.

◎ 기본형(primitive type) 매개변수

읽기(Read Only)만 가능

메서드의 매개변수를 기본형으로 선언하게 되면
단순히 값만 얻어온다.

◎ 참조형(reference type) 매개변수

읽기 & 변경 (Read & Write) 가능

메서드의 매개변수를 참조형으로 선언하게 되면
값이 저장된 주소를 얻어온다

예제를 통해 자세히 살펴보자.

/* 기본형 매개변수 */
class Data {
	int x ;
    }
    
class TypeCheck {
	public static void main(String[] args) {
    		Data d = new Data();
            	d.x = 10 ; // Data클래스에 속해 선언했던 x 사용
            	System.out.println("해당 메서드 main() :  x (d.x) = " + d.x);
                
                change(d.x) ; // "change() : x = 1000" 출력  : 실행되어서 호출스택에 단독 수행 후 사라짐.
                // d.x가 인수로 들어가더라도 값만 복사해서 chage메서드에서는 int 기본형 타입으로 들어감.
                // change메서드에서 " int "타입으로 독립적인 x는 출력만 가능
                // "값을 받아오기만 하지" 어떠한 클래스의 x를 참조해서 가져오는 형태가 아님
                
                System.out.println("chage() 이 후 : x (d.x) = " + d.x); // 변함없음 10 출력        
        }
        static void change(int x) { // "기본형" 매개변수
        	x = 1000 ; // 매개변수 int x 값이 변경된 것
            	System.out.println("change() : x = " + x) ;
        }
}

/* 참조형 매개변수 */
class Data2 {
	int x ;
    }
    
class TypeCheck {
	public static void main(String[] args) {
    		Data2 d = new Data2();
            	d.x = 10 ; // Data클래스에 속해 선언했던 x 사용
            	System.out.println("해당 메서드 main() :  x (d.x) = " + d.x);
                
                change(d) ; // "change() : x = 1000" 출력  : "d 객체" 를 인수로 집어넣음 => 값이 아닌 주소가 들어감
                // 완전 직접적으로 접근이 가능하게된 상태
                
                System.out.println("chage() 이 후 : x (d.x) = " + d.x); // 변경된 1000 출력        
        }
        static void change(Data2 d) { // "참조형" 매개변수 ( 위에서 사용한 "Data2 클래스의 타입" )
        	d.x = 1000 ; // 클래스 Data2에 접근해서 d.x 값을 직접 변경
            	System.out.println("change() : x = " + d.x) ;
        }
}

💡 참조형 return(반환)값 타입

위 매개변수 외에
반환값의 타입도 참조형이 될 수 있는데

모든 참조형 타입 값은 " 객체의 주소 "가 들어가기 때문에
반환되는 값의 타입 또한 해당 객체를 참조하여 따르게 된다.

class Data3 {
	int x ;
    }
    
class TypeCheck {
	public static void main(String[] args) {
    		Data3 d = new Data3();
            	d.x = 10 ;
                
                Data3 d2 = copy(d) // 위에 d 객체의 주소를 copy 메서드 인수로
                // 아래 tmp -> d2로 복사 => d2.x 출력하면 tmp.x의 값이 들어가게됨.
                
                System.out.println("d.x = " + d.x); // 위에서 대입한 d.x 값
                System.out.println("d2.x = " + d2.x); // copy메서드 수행하고 반환된 
                //Data3 클래스 타입으로 만들어진 tmp객체의 tmp.x 값이 복사된 d2.x 출력
        }
        static Data3 copy(Data3 d) { // Data3 타입 반환 _ 반환값을 참조
        	Data3 tmp = new Data3() ; // 새로운 객체 생성
            
            	tmp.x = d.x ; // 참조한 Data3 d에 접근해 x 값을 대입해서 복사
                
                return tmp // tmp객체 반환 -> 위 Data3 d2에는 tmp의 주소가 들어가게된다. 
        }
}

💡 "static" 메서드 vs "인스턴스" 메서드

변수의 경우와 마찬가지로

메서드 static이 붙어있으면
" 클래스 메서드 "이고
붙어있지 않으면
" 인스턴스 메서드 "이고

특징 또한 변수의 경우와 유사한데

  • 클래스 메서드」 : 객체를 생성하지 않고도 클래스 이름.클래스 메서드(매개변수)와 같이 호출 가능
  • 인스턴스 메서드」 : 반드시 객체를 생성해야만 호출 가능

인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는
즉, 메서드 수행에 인스턴스 변수가 필요하다.
하지만 인스턴스 변수는 인스턴스(객체)를 생성해야만 호출할 수 있기 때문에
메서드 역시 인스턴스가 생성되야만 호출할 수 있다는 것이다.

그 외 나머지,
인스턴스 변수가 필요없는 메서드
클래스 메서드(static 메서드)로 정의한다.

그래도
클래스를 정의할 때,
인스턴스 변수를 사용하지 않는다고 해서
무조건 / / 억지로라도 /꾸역꾸역
static 메서드로 정의할 필욘 없지만
특별한 이유가 없다면 그렇게 정의하는 것이 일반적이다.

static 을 붙여야할 경우

사실 인스턴스 변수 및 인스턴스 메서드의 상호간 사용여부를 확인하고 그 외의 공통적으로 사용하는 메서드나 변수에 static을 붙이는 것이 좋아보인다.

  • (클래스 설계 시) 멤버변수 중 " 모든 인스턴스에 공통으로 사용하는 것 "에 붙인다.
  • static 변수(클래스변수) : 인스턴스를 생성하지 않아도 사용 가능
  • static 메서드(클래스메서드) : 인스턴스 변수 사용 불가능
    ▶ 만약 메서드 내에서 " 인스턴스 변수를 사용하지 않으면 " static을 붙이는 것을 고려

◎ 메서드 간 호출 & 참조

같은 클래스에 속한 멤버들 간에는
별도 인스턴스 생성 없이 서로 참조/호출이 가능하다

하지만
클래스 멤버가 " 인스턴스 멤버를 참조/호출하고 싶다면 " 인스턴스를 생성해야만 한다.

클래스 멤버가 인스턴스 멤버보다
선행적으로 생성되기 때문에
클래스 멤버는 항상 존재하기에 " 인스턴스 멤버의 클래스 멤버 호출 "은 가능할 수 있어도
" 클래스 멤버가 인스턴스 멤버를 호출 "하는 것은 불가능할 수도 있다.
(불가능한 것이 아니라 인스턴스 멤버가 존재하지 않는 경우가 발생할 수 있음.)


0개의 댓글