본문으로 바로가기

클래스(class)와 객체(object, instance)

category JAVA 2019. 12. 1. 23:19

객체지향 프로그래밍


클래스와 객체란?

클래스(class) : 제품을 만드는 도면에 해당하는 것

객체(object, instance) : 도면을 이용해서 찍어내는 제품

 

객체지향 프로그래밍(OOP : Object Oriented Programming)이란?

프로그램에서 필요한 요소들을 객체로 만들고 이것들을 이용해 프로그래밍하는 것을 말한다.

객체를 모듈화해서 작업하는 프로그램

 

객체지향과 모듈화

비행기를 만들때 머리부터 꼬리까지 순서대로 차근차근만들 수도 있지만, 머리, 몸통, 꼬리, 날개를 나누어 만든 후 합칠 수도 있다.

모듈이란 머리, 몸통, 꼬리, 날개 각각의 부분을 말한다. 각 부분은 재사용이 가능하도록 조립된 블록이고,

모듈화모듈형식으로 작업을 진행하는 것을 말한다.

 

[모듈화된 프로그램은 특징]은 다음과 같다.

  • 추가/수정/삭제가 쉽다
  • 재사용이 가능하다
  • 이미 검증된 모듈을 사용할 경우 신뢰도가 높다

 

좋은 객체와 올바른 사용법

좋은 객체란?

자신이 가진 정보를 활용하는 기능이 다양한 객체. 기능이 많을 수록 그 객체의 활용도가 높아진다.

 

올바른 사용법

정보를 가져오지 말고 정보를 가진 객체에게 일을 시키는 것이 올바른 객체의 사용법인다.

 

 

 

클래스와 객체


클래스와 객체

객체지향 프로그램은 현실을 프로그램으로 변경하는 방식으로 진행된다.

 

[클래스의 구성과 객체와의 관계]

  • 현실의 객체가 갖는 속성과 기능은 추상화(abstraton)과정을 거쳐 클래스로 정의 된다.
  • 클래는 구체화 과정을 거쳐 프로그램의 객체(object,instance)가 된다.

클래스는 하나의 데이터 타입이 되며, 제품의 틀로써 동작한다.

객체는 클래스를 통해서 나온 제품이고, 클래스의 정의대로 생성된다.

 

클래스와 객체가 만들어지는 과정

현실세계(클래스)에는 사람들(객체)이 살아간다. 

객체(사람들)을 분석하면 속성과 기능으로 이루어진것을 알 수 있다.

 

속성은 객체가 가지는 정보를 말한다. 프로그래밍에서는 데이터로 변수(variable), 상태(status), 필드(field)와 같은 말로 사용된다.

기능은 메서드(method), 행위(behavior), 함수(function)로 불리며 객체가 할 수 있는 작업을 나타낸다.

 

 

1. 클래스를 만드는 과정

추상화 과정에서는 객체 타입을 나타낼 이름을 정하고 객체의 속성을 변수로, 

객체의 기능을 메서드로 하여 하나의 클래스를 작성한다.

메서드 역시 기능을 나타내는 적절한 이름을 정의하고 반환값(return type)과 파라미터(parameter)를 작성한다.

 

클래스는 설계도 역할만 수행하기 때문에 실제로 사용할 수는 없다.

단지, 객체를 정의하고 생성하기 위한 틀이 될 뿐이다.

[클래스의 역할]

- static 필드나 메소드를 감싸주는 역할
- 객체 설계도의 역할
-Data Type 역할

 

 

2. 객체를 만드는 과정

현실세계에서 사람들이 활동하는 것처럼 프로그래밍 영역에서도 클래스를 바탕으로 활동할 객체가 필요하다.

객체를 만드는 과정을 구체화 또는 객체화라고 한다.

 

구체화는 메모리에 객체(인스턴스, 오브젝트)를 생성하는 과정으로 new키워드를 사용한다.

이제 클래스를 바탕으로 메모리에 생성된 객체의 변수에 실제 값을 할당할 수 있고, 메서드도 호출할 수 있다.

 

 

 

(1) 클래스 생성 해보기

new 키워드로 생성자를 호출해서 객체를 만들어줘야 설계도(클래스)에 있는 내용이 실체화 된다.

 

1-3행 z package에 person 클래스 생성 

4-6행 field 영역(저장소)에 변수 선언. class의 필드영역에서는 기본값을 주지 않아도 자동으로 주어진다.

8-9행 메서드 eat과 work 선언 및 구현. eat 메서드를 호출하면 콘솔창에 "냠냠"이 출력되고 isHungry 값이 false가 된다.

work 메서드를 호출하면 콘솔창에 "열심히"가 출력되고 isHungry 값이 true가 된다.

 

(2) 객체 생성 및 활용

생성자를 호출하여 설계도(클래스)를 실체화 시키기

클래스를 이용해 객체를 만들고 사용할 주체 만들기 

  • 객체를 생성할때는 new 키워드를 이용해 생성자를 호출한다.
    (생성자=클래스명)
  • new키워들
  • 생성자를 호출하면 메모리에 객체가 생성되고, 그 객체를 사용하기 위해서는 참조할 수 있는 참조값을 해당 클래스 타입의 변수에 할당해야한다. 
클래스명 변수명=new 클래스명();

   6행 new키워드를 이용해 생성자를 불러와 객체를 생성하고, MyPerson 타입의 a변수에 참조값을 담아주었다.

7-9행 a변수에 있는 참조값을 이용해 name, age, isHungry field에 각각 값을 할당한다.

 10행 현재 필드에 담겨있는 값 출력

 11행 a변수에 있는 참조값을 이용해 객체에 있는 eat()메서드를 실행시킴

 13행 eat()메서드 실행 후 필드에 담겨있는 값 출력

 14행 new키워드를 이용해 생성자를 불러와 객체를 생성하고, MyPerson 타입의 b변수에 참조값을 담아주었다.

15-14행 b변수에 있는 참조값을 이용해 name, age, isHungry field에 각각 값을 할당한다.

18행 현재 필드에 담겨있는 값 출력

19행 b변수에 있는 참조값을 이용해 객체에 있는 work()메서드를 실행시킴

20행 work()메서드 실행 후 필드에 담겨있는 값 출력

 

 

객체와 메모리

클래스를 이용해 객체를 만들면 객체는 참조값을 할당받는다. 이때 참조변수를 선언해서 객체의 참조값을 사용할 수 있는데 이부분을 조금 더 자세히 살펴보자

 

먼저, JVM(Java Virtual Machine:자바 가상 머신)의 메모리 구조에 대해 살펴볼 필요가 있다.

JVM의 메모리는 크게 3부분으로 나눌 수 있다.

  • 클래스 영역(class area | method area | code area | static area) : 
    클래스를 사용하려면 원판 클래스가 메모리에 올라가야한다.
    이 원판이 등록되는 곳이 클래스 영역이다.
    클래스 영역에서는 Field 정보, Method 정보, 타입에 대한 정보, 상수풀이 저장된다.


    - Field 정보 : 변수의 이름, 데이터 타입 접금 제어자에 대한 정보


    - Method 정보 : 메서드 이름, 리턴타입, 파라미터, 접근 제어자에 대한 정보

    - Type 정보 : 타입의 속성이 클래스인지, 인터페이스 인지, 타입전체이름, 상위 클래스이름, 접근 제어자 정보 등

    - 상수풀(Constant Pool) : 타입(클래스 또는 인터페이스)에서 사용된 상수가 저장되는 곳으로 문자 상수도 상수 풀에 저장
  • 스택 영역(Stack Area)
    메서드를 호출 할때마다 로컬 변수들이 쌓이는 영역으로 스레드(thread)별로 별도의 공간을 가진다.
    이 영역은 LIFO(Last In First Out)구조로 나중에 메모리에 생성된 데이터가 먼저 소멸된다.
    로컬 변수란 호출된 메서드의 매개변수나 메서드 내부에 선언된 변수 등으로 객체의 멤버 변수가 아닌 변수를 말한다.
  • 힙 영역(Heap Area)
    힙은 new 키워드로 생성된 객체가 저장되는 공간으로 여러 스레드(thread)가 공유한다.
    이 영역에서 생성된 객체들은 자동으로 G.C(Garbage Collection)가 실행될때 삭제되어 사용하던 메모리 공간이반환된다.
    프로그램 코드로 임의로 객체를 삭제할 수도 있다. 

 

위에서 만들었던 PersonTest의 실행 결과를 대입해보면 다음과 같은 메모리 상태를 예상해볼 수 있다.

 

소스 코드를 보면서 메모리가 어떻게 동작하는지 해석해보기

 

1행 프로그램이 시작되면서 Person, PersonTest 클래스가 클래스 영역에 로딩된다.

5행 main메소드를 실행하면 MyPerson 타입의 a변수가 stack 영역에 생성되고,

     new 연산자에 의해 MyPerson 객체가 heap 영역에 생성된다.

     이때 객체의 참조값을 0*300이라고 가정하고, 참조값을 a에 할당해서 객체를 참조하게 된다.

6행 a의 name이 참조하는 값을 "짱아"로 변경한다. Sting도 참조형이므로 별도의 메모리 공간을 잡고, 0*400이라는 

     참조값을 name이라는 변수에 할당한다.

8행 a의 age가 참조하는 값을 5로 변경한다.

9행 a의 isHungry가 참조하는 값을 true로 변경한다.

11행 a의 eat()메서드를 실행해서 메서드의 내용에서 객체의 isHungry를 다시 false로 할당한다.

이후 내용은 위의 내용과 동일 하다.

 

 

 

변수의 종류


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

  • 멤버 변수(Member Variable)
    클래스의 구성요소로 클래스 영역에 선언된 변수를 말한다.
  • 로컬(Local Variable)
    메서드, 생성자, 초기화 블록처럼 클래스 영역내에 있는 또 다른 블록 내부에 선언된 변수를 말한다.

(1) 인스턴스 멤버 변수의 특징

  • 클래스 영역에 static 키워드 없이 선언된 변수
  • 객체가 만들어질 때 객체별로 생성된다.
    생성되는 위치는 객체 내부이기 때문에 heap영역에 생성된다.
  • 인스턴스 멤버 변수는 생성 시점에 타입별로 초기화가 이루어진다.
  • 이 변수를 사용하기 위해서는 먼저 객체를 생성해서 메모리에 올린 후 객체 이름을 통해서 접근해야한다.
  • 인스턴스 멤버 변수의 값은 객체마다 생성되기 때문에 객체별로 고유한 값. 즉, 상태를 가질 수 있다.
  • 인스턴스 멤버 변수가 메모리에서 삭제되는 시점은 객체가 없어지는 가비지 컬렉션이 일어나는 시점이다.

 

(2)클래스 멤버 변수의 특징

  • 클래스 영역에 static 키워드가 선언된 변수를 말한다.
  • 클래스 영역에 클래스가 로딩되는 시점에 메모리에 생성되므로 개별 객체의 생성과는 무관하다.
    (클래스 멤버 변수는 소속이 클래스이다.)
  • 같은 클래스를 통해 만들어진 모든 객체가 공유하게 되므로 공유 변수라고도 불린다.
  • 클래스 멤버 변수도 생성시 타입별로 기본 초기화가 이루어진다.
  • 개별 객체와 무관하기 때문에 객체가 아닌 클래스 이름으로 접근 해야한다.

 

(3)지역변수(Local Variable)의 특징

  • 메서드, 생성자의 내부, 파라미터, 초기화 블록의 내부에서 선언된 변수들
  • 로컬 변수는 변수가 선언된 행이 실행될때 스레드별로 생성된 메모리의 stack 영역에 생성되고, 해당영역({})이 끝나면 자동으로 삭제된다.
  • 로컬변수는 멤버변수와 달리 자동으로 초기화 되지 않는다. 따라서 사용하기 전에 반드시 명시적인 초기화가 필요하다.

 

 

메서드(Method)


메서드 정의와 필요성

  • 현실세계에서 객체가 하는 동작을 메서드(method)라고 한다.
  • 어떤 작업을 수행하는 명령문(코드)의 집합.
  • 메서드를 작성하는 이유는 유지 보수성을 향상시키기 위함이다.

 

메서드 작성

학교에서 배웠던 함수의 형태는 위와 같다.

 

프로그램에서 Method도 이와 비슷하다.

제한자 리턴타입 메서드이름(타입 변수명,...){ //선언부
	//code 작성. 구현부
}

 

(1) 선언부 : 리턴타입

  • 메서드 호출의 결과로 메서드를 호출한 곳으로 돌려주는 값의 타입을 지정
  • 결과를 받는 곳에서는 묵시적 형변환을 적용할 수 있다.
  • 메서드를 호출한 곳에서는 동일한 타입으로 받거나, 보다 큰 타입으로 받을 수도 있다.
public int add(int a, int b){ //add();메서드가 호출되면 int 타입의 값이 리턴된다.
	return a+b;
}

int result1=add(100,200); //add();호출 결과를 int 타입의 변수 result1에 할당
double result2=add(100,200); /add();호출 결과를 double 타입의 변수 result2에 할당
System.out.println(result1+" : "+result2); //300 : 300.0
  • void : 메서드의 호출결과 아무것도 리턴하지 않을 경우 리턴 타입에 작성한다.
public void sayHello(){  //sayHello에서는 아무 값을 반환하지 않음
	System.out.println("Hello");
}

System.out.println(sayHello()); //메서드 호출 결과가 없으므로 출력할 수 없음

 

  • 메서드 호출결과로 리턴타입에 해당하는 하나의 데이터 만을 리턴할 수 있다.

 

(2)선언부 메서드 이름

  • 작업의 내용을 한눈에 파악하기 쉽게 의미 있는 단어를 이용하는 것이 좋다

 

(3)선언부 : 파라미터 목록

  • 메서드를 실행하면서 사용할 값이 있다면 메서드에 파라미터로 선언한다.
  • 필요한 값이 없다면 파라미터 선언을 생략할 수 있다.
  • 변수의 타입과 변수명을 나열해서 파라미터를 설정한다.
    단, 값을 할당할 수는 없다.
  • 메서드가 호출되기 위해서는 선언된 파라미터와 동일한 개수의 파라미터가 제공되어야한다.
    타입은 묵시적 형변환이 가능하다.
    예) 파라미터에 int를 받겠다고 선언한 경우, int 보다 작은 byte, short, int, char를 전달할 수 있다.
//long 타입의 변수 두개가 선언된 add( ) 메서드를 호출하는 방식을 보여주는 예시
public long add(long a, longb){
	return a+b;
}

add(10L,20L);  //적절
add(100,200);  //적절:int->long으로 묵시적 형변환 진행
add(1.1,2.2);  //부적절 : long타입에 double 할당 불가(값의 손실이 일어남)
add(100,200,300);  //부적절 : 파라미터의 개수가 다름
add(100);  //부적절 : 파라미터의 개수가 다름

 

(4)선언부 : 가변인자(Variable arguments)

  • 더해야하는 숫자의 개수가 다양한 경우 사용할 수 있다.
  • 가변인자는 내부저긍로 배열을 사용하는데, 별도의 초기화 과정이 필요 없고 값을 할당할때 단순히 나열해주기만 하면된다.
  • 가변인자는 파라미터의 타입과 변수명 사이에 ...으로 표시한다.
    (...는 선언한 데이터 타입의 변수가 0개 이상 올 수 있음을 의미)

16행 add()메서드의 파라미터로 int 타입 가변인수인 params가 사용되었다.

18-21행 params는 내부저그올 배열이므로 반복문을 이용해서 내부 요소들에 접근할 수 있다.

25행 add()를 호출하기 위해 VariableArgsTest타입의 객체 vt를 생성한다.

26-28행 객체를 통해 add메서드를 호출할때 0개 이상의 int를 전달할 수 있다.

 

(5)구현부

  • 메서드 구현부는 중괄호({ }) 내에서 처리해야할 내용인 비즈니스 로직을 작성한다.
  • 맨 
  • 값을 반환할때는 묵시적 형변환이 적용된다.
    예 : int를 리턴하기로 한경우 보다 작은 byte, short, char 타입의 값도 리턴 가능하다.
//int를 리턴하도록 작성된 getNumber()메소드에서 char 타입을 리턴하는 예
public int getNumber(){
	return'A';
}
  • 메서드 종료시점 : 
     1. return 문장을 만나거나
     2. 메서드의 마지막 문장을 실행한 경우
  • 조건문 내에서 return을 사용하는 경우 모든 조건에서 return 될 수 있도록 주의 해야한다.

 

메서드 호출  (어려움!!!!!!!)

언제나 가장 중요한 것은 '호출하려는 멤버가 메모리에 있는가?'이다.

멤버 변수를 호출할때오 마찬가지로 호출하려는 메서드가 메모리에 구성돼 있을 경우만 호출 가능하다.

 

static 메서드 | 클래스 메서드

  • static 키워드가 붙은 메서드는 개별 객체와 관련 없이, class가 로딩되는 시점에 메모리에 생성되어 언제나 메모리에 있다.
  • 특별히 객체에 대한 레퍼런스 필요없이 바로 클래스 이름을 이용해서 호출할 수 있다.
    (같은 클래스 내에서 호출 하는 경우, 클래스 이름조차 필요없다.)

 

non-static 메서드 | 인스턴스 메서드

  • static이 붙지 않은 메서드는 반드시 객체를 먼저 생성한 후 객체를 통해서 접근해야한다.
    (같은 클래스 내에서 호출하는 경우, 객체 없이 바로 호출 할 수 있다.)

위의 두개의 클래스에서 서로의 멤버를 호출하는 다양한 상황을 고민해보자

  • 3의 구현부에서 1과4를 호출하려면?
    First.cv=100;        | cv=100;
    First.cMethodB();  | cMethodB();
    1,2,4는 static 이므로 메모리에 생성되어 있다. 
    static메서드와 클래스 멤버 변수 이므로 class 로딩시 이미 static 영역에 실체가 형성되어 있다. 따라서 클래스명에 .(점)을 찍어 변수에 값을 넣어주고, 메서드를 호출하면 된다.
    하지만 같은 클래스 내에 있으므로 굳이 클래스명을 선언하지 않아도 변수와 메서드를 호출 할 수 있다.
  • 4의 구현부에서 2와5를 호출 하려면?
    First a=new First();  //객체생성
    a.iv=100;       | iv=100;
    a.iMethodA(); | iMethodA();
    4는 static이므로 메모리에 생성되어 있고, 2와 5는 non-static이므로 아직 메모리에 생성되어 있지 않다.
    따라서 new를 이용해 생성자를 불러와서 객체를 생성한 뒤 호출해야한다.
    한편, 4,2,5는 모두 동일한 클래스 내에 있기때문에 객체를 생성하지 않고도 바로 호출할 수도 있다.
  • 5의 구현부에서 1과3을 호출하려면?
    First.cv=100;        | cv=100;
    First.cMethodA();  | cMethodA();
    5는 non-static이다. 1,3은 static 이므로 클래스명에 .(점)을찍어 호출한다.
    한편, 같은 클래스 내에 있으므로 굳이 클래스명을 선언하지 않아도 변수와 메서드를 호출 할 수 있다.
  • 7의 구현부에서 9와 10을 호출하려면 ?
    Second.cMethod();
    Second b=new Second(); //객체생성
    b.iMethod();
    7은 non-static이다. 9와 10은 다른 클래스에 있는 static 메서드와 non-static 메서드이다.
    다른 클래스에 있으므로 호출하기 위해서는 반드시 참조할 클래스명을 명시 해주어야한다.
    또한 10의 경우 non-static 메서드 이므로 아직 메모리에 생성되어 있지 않다. 때문에 객체를 생성해서 메모리에 생성 한뒤 7에서 호출할 수 있다.
  • 8의 구현부에서 9와10을 호출하려면?  어려웠음.
    Second.cMethod();
    s.iMethod();
    8에서 파라미터 값으로 설정된 (Second s)는 Second 타입의 어떤 값이 오면 파라미터에 넣어 전달 하겠다는 말이다. 따라서 9 메소드는 static이므로 클래스가 로딩되는 시점에 이미 static 영역(클래스 영역)에 메소드가 생성되어 있다. 따라서 클래스명에 점(.)을 찍어 메소드를 호출하면 된다.
    10의 경우,  non-static 이므로 객체를 생성해야 메소드를 호출할 수 있다. 객체에 대한 참조값은 8에서 파라미터로 받고 있으므로 참조값을 받는 파라미터에 점(.)을 찍어 메서드를 호출해야한다.

(1) 메서드 호출 스택

  • 스택 : 순차적으로 데이터를 쌓는 구조. 스택은 입구와 출구가 같기 때문에 LIFO구조로 데이터를 저장한다.
    (LIFO : First In Last Out)
  • 메서드가 호출되는 과정에서도 메서드 호출 스택이라는 것을 구성하게 된다.
    즉, '메서드를 쌓는다'는 개념이 존재한다.
  • 모든 메서드는 호출할 때마다 메서드 동작을 위한 메모리 상자를 하나씩 할당 받는다.
    상자 내부에는 메서드에서 사용되는 파라미터 변수등 로컬 변수들이 저장된다.
    메서드 외부에서는 상자에 접근할 수 없기 때문에 로컬변수들은 메서드 내부에서만 접근할 수 있다.

[예시] A메서드가 최초에 호출되었다면 stack 메모리 영역에 A를 위한 상자가 생성된다.

A에서 새로운 B메서드 호출시 B 실행을 위한 메모리 상자를 A 위에 쌓는다.

이때! 언제나 맨위에 있는 상자만 동작한다.(*중요*)

즉, B의 상자가 A위에 올라가게되면 B가 동작하고 A는 완전히 동작이 끝나지 않고 잠시 정지된 상태가 된다.

만약 B메서드가 종료(return)하면 B메서드를 위한 메모리 상자가 제거되고 메모리를 반납하게된다. 

비로소 A가 다시 최상위 메서드가 되어서 나머지 동작을 수행하게 된다.

 

[실전]

메서드가 호출되면서 stack영역의 메모리가 어떻게 구성되는지 생각해보자 

4행 main Method가 호출되면서 메모리가 구성된다. main메서드를 위한 메모리 상자가 생성되고, 현재 최상단에 존재하는 실행 매서드가 된다.
6행 aMethod가 호출되면 aMethod 실행을 위한 메모리 상자가 생성되고, main메서드 메모리 상자 위에 쌓인다. 이제 main메모리 상

12행 aMethod 내부에서 bMethod를 호출한다. 이때 aMethod위에 bMethod 실행을 위한 메모리 상자가 생성되고 실행의 흐름을 bMethod가 가져 간다.

16-20행 bMethod의 실행이 끝나고 리턴하면 bMethod를 위한 메모리 상자가 제거되고 메모리를 반납한다. 이때 aMethod는 최상위 메소드가 되어 실행의 흐름을 가져간다.

12-14행 aMethod의 실행의 흐름이 끝나고 리턴 되면 aMethod를 위한 메모리 상자가 제거되고 메모리를 반납한다. 그럼 main메소드가 최상위 메소드가 되고 실행의 흐름을 가져간다.

6행 main메서드가 동작하고 리턴되면 모든 메모리가 깨끗이 초기화 된다.

 

(2)기본형 변수와 참조형 변수의 전달 차이

  • 변수의 종류는 타입에 따라 기본형과 참조형으로 전달된다.
    - 기본형 data type : byte, short, int, long, float, double, char, boolean
    - 참조형 data type : 기본형 외의 모든 변수
  • 자바에서는 변수의 값이 파라미터로 전달될 때 기존의 값이 복사되어 전달된다. 여기서 기존의 값이 무엇인지 파악하는 것이 중요하다.
  • 기본형 : 변수에 저장되는 것이 값 자체이다.
  • 참조형 : 실제 객체가 아니라 객체에 대한 참조값이다.
               참조값을 전달 받은 곳에서 그 참조값을 통해 객체의 내용을 바꾸면 원래 있던 객체의 내용이 변경된다.
               (주소를 아무리 복사해도 집이 여러채 생기지는 않는다.)

[실습]

5-8행 기본형인 int type을 Var 파라미터로 받아서 10을 더하는 메서드

 

10-13행 참조형인 CallByTest type을 cbtl 파라미터로 받아서 10을 더하는 메서드

 

17행 CallByTest객체를 생성해 참조값을 CallByTest type의 cbt변수에 할당한다.

 

19-20행 처음 이 객체의 memberVar에는 10이 할당되어 있다. cbt에 할당되어 있는 참조값을 이용해 객체에 접근한 뒤

memberVar의 값을 5로 변경한다. 따라서 결과적으로 출력된 값은 5이다.

 

22-23행 int 타입의 자료를 Var파라미터로 받는 change1 메서드를 콜하면서 cbt.memberVar의 값을 복사해서 넘겨준다. memberVar는 자체적으로 5라는 값을 가지고 있다가 복사해서 넘겼기 때문에 change1에서 전달받은 값을 변경 하더라도 원본은 전혀 영향을 받지 않는다. 따라서 메서드를 호출 후 출력한 cbt.memberVar의 값은 여전히 5이다.

25-26행 CallByTest 타입의 자료를 파라미터로 받는 change2 메서드를 콜하면서 객체의 참조값이 담겨 있는 cbt 복사해서 넘겨주었다.  때문에 cbtl에는 cbt의 참조값이 할당되었고, 이로써 cbtl로도 생성된 객체에 접근할 수 있게 되었다. 따라서 change2에서 cbtl의 memberVar를 변경하면 cbt에서의 memberVar 값도 변경되어 결과로 105가 출력된다.

 

메서드 오버로딩(Overloading)

(1) 메서드 오버로딩이란?

  • 메서드가 같은 이름을 다중 정의 되어 있는 것을 말함.
  • 예시1 : 밥을 먹을 때 숟가락, 젓가락, 포크 등 다양한 방법으로 먹을 수 있다. 밥을 입으로 가져가는데 까지는 모두 다 다르지만 입에 들어간 이후 소화하는 동작은 모두 다 똑같다.
  • 예시2 : println의 경우 파라미터의 데이터 타입이 각각 모두 다 다르다.

(2) 메서드 오버로딩 방법

  • 동일한 이름의 메서드에 파라미터의 개수 또는 파라미터의 타입을 다르게 작성하면 된다.

  • 메서드의 리턴 타입이나 파라미터 이름이 다른 경우는 의미가 없다.

  • 숫자형 데이터 타입 overloading시 주의! 
    아래의 코드는 파라미터의 갯수가 동일하게 작성되었고, 파라미터의 타입이 다르게 작성되어 오버로딩에 성공한 것 처럼 보인다. add(3L, 4)와 add(3, 4L)로 메서드를 각각 호출해보면 잘 동작한다.
    그러나 add(3,4)로 메서드를 호출하면 어떤 메서드를 호출하는지 알 수 없어 오류가 발생한다.

(3) 메서드 오버로딩과 코드의 재사용  

  • 메서드이 Overloading은 동일한 기능을 수행하는 데서 출발했기 때문에 많은 로직이 겹치게 된다.
  • 로직이 겹치면 코드의 생산성을 떨어트리게 된다.
  • 이런 경우, 기존의 메서드를 호출함으로써 코드를 재사용할 수 있다.

걷는 기능을 구현한 코드이다. 

public class WalkTestBad {
	//100cm마다 이동
	public void walk() {
		System.out.println("100cm이동");
	}
	//입력한 거리 마다 이동
	public void walk(int distance) {
		System.out.println(distance+"cm이동");
	}
	//단위를 파라미터로 받는 거리이동
	public void walk(int distance, String unit) {
		switch(unit) {
		case "cm":
			break;
		case "inch":
			distance*=2.54;
			break;
		default:
			System.out.println("unknown");
			distance=0;
		}
		System.out.println(distance+"cm 이동");
	}
}

 

System.out.println(); 코드가 반복되고 있다. 

이와같은 경우 유지 보수하기가 힘들어지고, 코드의 생산성을 떨어트리게 된다. 

기존의 메소드를 호출함으로써 코드를 재사용할 수 있다.

 

public class WalkTestBad {
	public void walk() {
		walk(100,"cm");  //10행의 walk()메소드 호출
	}
	
	public void walk(int distance) {
		walk(distance,"cm");   //10행의 walk()메소드 호출
	}
	
	public void walk(int distance, String unit) {
		switch(unit) {
		case "cm":
			break;
		case "inch":
			distance*=2.54;
			break;
		default:
			System.out.println("unknown");
			distance=0;
		}
		System.out.println(distance+"cm 이동");
	}
}

 

 

생성자(Constructor)


생성자의 형태

  • 정의하지 않아도 클래스 생성시 default 생성자는 있다고 간주한다.
  • 생성자는 선어부에 return type이 없고, 생성자명은 클래스이름과 똑같이 써야한다.
  • 생성자도 overloading할 수 있다.

 

생성자의 종류

생성자는 파라미터의 유무에 따라 기본생성자와 파라미터 생성자로 나뉜다.

 

(1) 기본 생성자(default constructor)

  • 기본생성자는파라미터가 없고 구현부가 비어 있는 형태이다.
  • 별도의 생성자를 만들지 않았을 때 컴파일러에 의해 컴파일 타임에 자동으로 삽입된다.
    (별도의 생성자를 만들지 않았을 때만 생긴다.)
  • 별도의 생성자를 만들고 난 뒤 default 생성자를 사용하고 싶으면 명시적으로 작성해 주어야한다.

(2) 파라미터 생성자

  • 생성자가 파라미터를 받는 형태이다.
    생성자에 전달될 파라미터는 주로 멤버 변수의 초기화에 사용된다.

 

this의 용법

(1) 객체를 참조하는 this

  • 객체를 참조하기 위한 용도로 사용된다.
  • 로컬변수의 이름과 멤버 변수의 이름이 동일한 상황에서 둘을 구분해야할 필요가 있는데 이때 사용되는 키워드가 this이다.
  • this는 객체에 대한 레퍼런스(참조값)이기 때문에 객체가 생성되기 전에는 사용할 수 없다.
    (static 영역에서는 사용 불가)

 

 

(2) 다른 생성자를 호출하는 this

public class OverloadConstructorPerson {
	String name;
	int age;
	boolean isHungry;
	
	OverloadConstructorPerson(String name, int age, boolean isHungry){
		this.name=name;
		this.age=age;
		this.isHungry=isHungry;
	}
	
	OverloadConstructorPerson(String name, int age){
		this(name, age, false);
	}
	
	OverloadConstructorPerson(String name){
		this(name, 0, false);
	}
	
	OverloadConstructorPerson(){
		this("홍길동", 50, false);
	}
}
  • this의 두번째 용법은 생성자를 호출하는데 사용된다.
  • 파라미터를 다르게 해서 여러개의 생성자를 정의하는 것을 생성자 오버로딩이라고 한다.
  • 생성자에서도 메서드와 같이 다른생성자를 호출하여 중복 코드를 제거할 수 있다.
  • 주의!
    - 만약 this 대신 그냥 생성자 이름을 사용하면 선언되지 않은 메서드를 사용한다는 오류 메시지가 표시된다.
    - this를 통한 생성자 호출은 생성자의 맨처음 행에서만 가능하다.(컴파일 진행되지 않음)
OverloadConstructorPerson(){
  System.out.println("don't write on the first line");
  //Constructor call must be the first statement in a constructor
  this("홍길동", 50, false);
}