[자바의 정석] 6장 객체지향 프로그래밍1
1. 객체지향언어
실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물간의 상호작용이다
주요 특징
- 코드의 재사용성이 높다.
- 코드의 관리가 용이하다.
- 신뢰성이 높은 프로그래밍을 가능하게 한다.
객체지향개념을 학습할 때 재사용성과 유지보수 그리고 중복된 코드의 제거 관점에서 볼 것
-> 일단 프로그램을 기능적으로 완성한 다음 어떻게 하면 보다 객체지향적으로 코드를 개선할 수 있을지를 고민하여 개선
2. 클래스와 객체
2.1 클래스와 객체의 정의와 용도
클래스의 정의 - 객체를 정의해 놓은 것
클래스의 용도 - 객체를 생성하는데 사용
객체의 정의 - 실제로 존재하는 것. 사물 또는 개념
클래스를 정의하고 클래스를 통해 객체를 생성하는 이유는 하나의 설계도만 잘 만들어 놓으면 제품을 만드는 일이 쉬워지기 때문이다.
2.2 객체와 인스턴스
인스턴스화
클래스 ————> 인스턴스(객체)
- 클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화라고 하며, 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스라고 한다.
2.3 객체의 구성요소 - 속성과 기능
- 객체는 속성과 기능의 집합이라고 할 수 있다.
- 객체가 가지고 있는 속성과 기능을 그 객체의 멤버라 한다.
-> 멤버변수와 메서드
2.4 인스턴스의 생성과 사용
클래스명 변수명; //객체를 참조하기 위한 참조변수 선언
변수명 = new 클래스명(); //객체를 생성 후, 객체의 주소를 참조변수에 저장
Tv t; //Tv클래스 타입의 참조변수 t를 선언
t = new Tv(); //Tv인스턴스를 생성한 후, 생성된 Tv인스턴스의 주소를 t에 저장
인스턴스 사용 예시
- Tv t; Tv클래스 타입의 참조변수 t를 선언한다. 아직 인스턴스가 생성되지 않았으므로 참조변수로 아무것도 할 수 없다.
- t = new Tv(); 연산자 new에 의해 Tv클래스의 인스턴스가 메모리의 빈 공간에 생성된다. 이 때, 멤버변수는 각 자료형에 해당하는 기본값으로 초기화 된다.
- t.channel = 7; 참조변수 t에 저장된 주소에 있는 인스턴스의 멤버변수 channel에 7을 저장한다.
- t.channelDown(); 참조변수 t가 참조하고 있는 Tv인스턴스의 channelDown메서드를 호출한다. 메서드는 멤버변수 channel에 저장되어 있는 값을 1 감소시킨다. -> 인스턴스는 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입은 인스턴스의 타입과 일치해야한다.
- 같은 클래스로부터 생성되었을지라도 각 인스턴스의 속성은 서로 다른 값을 유지할 수 있으며, 메서드의 내용은 모든 인스턴스에 대해 동일하다.
- 자신을 참조하고 있는 참조변수가 하나도 없는 인스턴스는 더 이상 사용되어질 수 없으므로 ‘가비지 컬렉터’에 의해서 자동적으로 메모리에서 제거된다.
- 참조변수에는 하나의 값만이 저장될 수 있으므로 둘 이상의 참조변수가 하나의 인스턴스를 가리키는 것은 가능하지만, 하나의 참조변수가 여러 인스턴스를 가리키는 것은 불가능하다.
2.5 객체 배열
- 많은 수의 객체를 다뤄야할 때, 객체 역시 배열로 다루는 것이 가능하며, 이를 ‘객체 배열’이라고 한다.
- 객체 배열은 참조변수들을 하나로 묶은 참조 변수 배열이다.
ex) Tv[] tvArr = new Tv[3];- 객체 배열을 생성하는 것은, 그저 객체를 다루기 위한 참조변수들이 만들어진 것일 뿐, 객체를 생성해서 각 요소에 저장하는 것을 잊으면 안된다.
ex) Tv[] tvArr = {newTv(), new Tv(), new Tv()};
2.6 클래스의 또 다른 정의
- 클래스 - 데이터와 함수의 결합
- 자바는 변수와 함수를 하나의 클래스에 정의하여 서로 관계가 깊은 변수와 함수들을 함께 다룰 수 있게 했다.
- 자바에서는 String이라는 클래스로 문자열을 다룬다. 그 이유는 문자열을 단순히 문자의 배열로 정의하지 않고 문자열과 문자열을 다루는데 필요한 함수들을 함께 묶기 위해서다.
- 클래스 - 사용자정의 타입
- 사용자가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하여 사용한다.
3. 변수와 메서드
3.1 선언위치에 따른 변수의 종류
변수는 클래스변수, 인스턴스변수, 지역변수 모두 세 종류가 있다. 변수의 종류를 결정짓는 중요한 요소는 ‘변수의 선언된 위치’이다.
class Variables { //클래스 영역
int iv; //인스턴스변수
static int cv; //클래스변수(static 변수)
void method()
{ //메서드 영역
int lv = 0; //지역변수
}
}
- 인스턴스 변수
- 클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어진다.
- 인스턴스마다 고유한 상태를 유지해야하는 속성의 경우, 인스턴스변수로 선언한다.
- 클래스 변수
- 클래스 변수를 선언하는 방법은 인스턴스변수 앞에 static을 붙이기만 하면 된다.
- 클래스변수는 모든 인스턴스가 공통된 저장공간을 공유하게 된다. 한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우, 클래스 변수로 선언해야 한다.
- 인스턴스를 생성하지 않고도 언제라도 바로 사용할 수 있다는 특징이 있으며, ‘클래스이름.클래스변수’와 같은 형식으로 사용한다.
- 클래스가 메모리에 로딩될 때 생성되어 프로그램이 종료될 때 까지 유지되며, public을 앞에 붙이면 ‘전역변수’의 성격을 갖는다.
- 지역변수
- 메서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸되어 사용할 수 없게 된다.
3.2 메서드
특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것
메서드를 사용하는 이유
- 높은 재사용성
- 중복된 코드의 제거
- 프로그램의 구조화
3.3 메서드의 선언과 구현
메서드 선언부
‘메서드의 이름’과 ‘매개변수 선언’, 그리고 ‘반환타입’으로 구성되어 있다.
//반환타입 메서드이름 매개변수
int add (int x, int y) {
int result = x + y;
return result; //결과를 반환
}
return문
- 반환타입이 ‘void’가 아닌 경우, 구현부 안에 ‘return 반환값;’이 반드시 포함되어 있어야 한다.
- 이 값의 타입은 반환타입과 일치하거나 적어도 자동 형변환이 가능한 것
3.4 메서드의 호출
메서드이름(값1, 값2, ...);
- 메서드를 호출할 때 괄호 안에 지정해준 값들을 ‘인자’ 또는 ‘인수’라고 하는데, 인자의 개수와 순서는 호출된 메서드에 선언된 매개변수와 일치해야 한다.
- 같은 클래스 내의 메서드끼리는 참조변수를 사용하지 않고도 서로 호출이 가능하지만 static메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없다.
매개변수의 유효성 검사
메서드를 작성할 때는 매개변수의 유효성 검사하는 코드를 반드시 넣어야 한다.
3.5 JVM의 메모리 구조
- 메서드 영역 (method area)
프로그램 실행 중 어떤 클래스가 사용되면, 클래스에 대한 정보를 이곳에 저장한다. 클래스변수도 이 영역에 함께 생성 - 힙 (heap)
인스턴스가 생성되고, 인스턴스변수들이 생성되는 공간 - 호출 스택 (call stack)
메서드의 작업에 필요한 메모리 공간을 제공. 이 메모리는 메서드가 작업을 수행하는 동안 지역변수 (매개변수 포함)들과 연산의 중간결과 등을 저장하는데 사용된다. 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 비워진다.
- 메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당받는다.
- 메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다.
- 호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드이다.
- 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드이다.
3.8 기본형 매개변수와 참조형 매개변수
기본형 매개변수 변수의 값을 읽기만 할 수 있다.(read only)
참조형 매개변수 변수의 값을 읽고 변경할 수 있다.(read & write)
기본형 매개변수
- change메서드가 호출되면서 ‘d.x’가 change메서드의 매개변수 x에 복사됨
- change메서드에서 x의 값을 1000으로 변경
- change메서드가 종료되면서 매개변수 x는 스택에서 제거됨
원본이 아니라 복사본이 변경된 것이라 원본에는 아무런 영향을 미치지 못한다.
이처럼 기본형 매개변수는 변수에 저장된 값만 읽을 수만 있을 뿐 변경할 수는 없다.
참조형 매개변수
- change메서드가 호출되면서 참조변수 d의 값(주소)이 매개변수 d에 복사됨 이제 매개변수 d에 저장된 주소값으로 x에 접근이 가능
- change메서드에서 매개변수 d로 x의 값을 1000으로 변경
- change메서드가 종료되면서 매개변수 d는 스택에서 제거됨
change메서드의 매개변수를 참조형으로 선언했기 때문에, 주소가 매개변수 d에 복사되었다. 그래서 매개변수 d로 x의 값을 읽는 것과 변경하는 것이 모두 가능한 것이다.
참조형 반환타입
- copy메서드를 호출하면서 참조변수 d의 값이 매개변수 d에 복사된다.
- 새로운 객체를 생성한 다음, d.x에 저장된 값을 tmp.x에 복사한다.
- copy메서드가 종료되면서 반환한 tmp의 값은 참조변수 d2에 저장된다.
- copy메서드가 종료되어 tmp가 사라졌지만, d2로 새로운 객체를 다룰 수 있다.
“반환타입이 ‘참조형’이라는 것은 메서드가 ‘객체의 주소’를 반환한다는 것을 의미한다.”
3.9 재귀호출
메서드의 내부에서 메서드 자신을 다시 호출하는 것을 ‘재귀호출’이라 하고, 재귀호출을 하는 메서드를 ‘재귀 메서드’라 한다.
- 조건문이 필수적으로 따라다닌다.
- 반복문 대신 재귀호출을 사용하는 이유?
-> 재귀호출이 주는 논리적 간결함 때문
3.10 클래스 메서드(static method)와 인스턴스 메서드
클래스 메서드
- 인스턴스와 관계없는(인스턴스 변수나 인스턴스 메서드를 사용하지 않는) 메서드를 클래스 메서드라고 한다.
- 클래스변수처럼, 객체를 생성하지 않고도 ‘클래스이름.메서드이름(매개변수)’와 같은 식으로 호출이 가능하다.
인스턴스 메서드
- 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는 데 인스턴스 변수를 필요로 하는 메서드이다.
- 인스턴스 변수는 인스턴스를 생성해야만 만들어지므로 인스턴스 메서드 역시 인스턴스를 생성해야만 호출할 수 있다.
규칙
- 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
- 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있다.
- 클래스 메서드는 인스턴스 변수를 사용할 수 없다.
-> 클래스메서드는 인스턴스 생성 없이 호출가능하므로 클래스 메서드가 호출되었을 때 인스턴스가 존재하지 않을 수도 있기 때문에 - 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.
3.11 클래스 멤버와 인스턴스 멤버간의 참조와 호출
- 같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다.
- 단, 클래스멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야한다.
-> 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문이다. - 그래서 static메서드는 인스턴스변수와 인스턴스 메서드를 사용하거나 호출할 수 없다.
4. 오버로딩
오버로딩 : 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것
4.1 오버로딩의 조건
- 메서드의 이름이 같아야 한다.
- 매개변수의 개수 또는 타입이 달라야 한다.
- 반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.
4.2 오버로딩의 장점
- 하나의 이름만 기억하면 되므로 기억하기도 쉽고 이름도 짧게 할 수 있어서 오류의 가능성을 줄인다.
- 메서드의 이름을 절약할 수 있다.
-> 같은 기능을 하는 메서드들이 다른 이름을 가진다면 작성하는 쪽에서나 사용하는 쪽에서나 부담스럽다.
4.3 가변인자와 오버로딩
메서드의 매개변수 개수를 동적으로 지정할 수 있는 기능
ex) 타입... 변수명
- 가변인자 외에도 매개변수가 더 있다면, 가변인자를 매개변수 중에서 제일 마지막에 선언해야 한다.
- 가변인자인지 아닌지를 구별할 방법이 없기 때문이다.
- 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성된다.
-> 편리하지만, 이러한 비효율이 숨어있으므로 꼭 필요한 경우에만 가변인자를 사용한다. - 매개변수의 타입을 배열로 하면, 반드시 인자를 지정해줘야 하기 때문에 인자를 생략할 수 없다.
-> null이나 길이가 0인 배열을 인자로 지정해줘야 하는 불편함 존재 - 가변인자를 선언한 메서드를 오버로딩하면, 메서드를 호출했을 때 이와 같이 구별되지 못하는 경우가 발생하기 쉽기 때문에 주의해야 한다.
-> 가변인자는 가변 길이의 배열을 이용하기 때문에 구분하기에 모호하기 쉽다. 가능하면 가변인자를 사용한 메서드는 오버로딩하지 않는 것이 좋다.
5. 생성자
5.1 생성자란?
인스턴스가 생성될 때 호출되는 ‘인스턴스 초기화 메서드’
- 인스턴스 변수의 초기화 작업에 주로 사용
- 인스턴스 생성 시에 실행되어야 할 작업을 위해서도 사용
생성자의 조건
- 생성자의 이름은 클래스의 이름과 같아야 한다.
- 생성자는 리턴 값이 없다.
클래스 이름 (타입 변수명, 타입 변수명, ...) {
//인스턴스 생성 시 수행될 코드,
//주로 인스턴스 변수의 초기화 코드를 적는다.
}
Class Card {
Card() { //매개변수가 없는 생성자
...
}
Card(String k, int num) { //매개변수가 있는 생성자
...
}
}
- 생성자도 오버로딩이 가능하므로 하나의 클래스에 여러 개의 생성자가 존재할 수 있다.
주의할 점
연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다
ex) Card c = new Card();
- 연산자 new에 의해서 메모리(heap)에 Card클래스의 인스턴스가 생성된다.
- 생성자 Card()가 호출되어 수행된다.
- 연산자 new의 결과로, 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장된다.
5.2 기본 생성자
- 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.
- 클래스에 생성자를 정의하지 않고도 인스턴스를 생성할 수 있었던 이유는 컴파일러가 제공하는 ‘기본 생성자’ 덕분이다.
ex)클래스이름() {} - 기본 생성자는 매개변수도 없고 아무런 내용도 없는 아주 간단한 것
기본 생성자가 컴파일러에 의해서 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐이다
5.3 매개변수가 있는 생성자
- 인스턴스마다 각기 다른 값으로 초기화되어야하는 경우가 많기 때문에 매개변수를 사용한 초기화는 매우 유용하다.
- 인스턴스를 생성한 다음에 인스턴스변수의 값을 변경하는 것보다 매개변수를 갖는 생성자를 사용하는 것이 코드를 보다 간결하고 직관적으로 만든다.
-> 클래스를 작성할 때 다양한 생성자를 제공하여 인스턴스 생성 후에 별도로 초기화를 하지 않도록 하는 것이 바람직함
5.4 생성자에서 다른 생성자 호출하기 - this(), this
this()
- 생성자 간에도 서로 호출이 가능하다. 단, 다음의 두 조건을 만족시켜야 한다.
- 생성자의 이름으로 클래스이름 대신 this를 사용한다.
- 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.
-> 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.
this
- 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장되어 있다.
- 모든 인스턴스메서드에 지역변수로 숨겨진 채로 존재한다.
-> 인스턴스 변수와 생성자의 매개변수로 선언된 지역변수를 서로 구별하기 위해 사용한다.
5.5 생성자를 이용한 인스턴스의 복사
- 하나의 클래스로부터 생성된 모든 인스턴스의 메서드와 클래스변수는 서로 동일하기 때문에 인스턴스간의 차이는, 인스턴스마다 각기 다른 값을 가질 수 있는 인스턴스변수 뿐이다.
Car(Car c) {
this(c.color, c.gearType, c.door);
}
-> Car클래스의 참조변수를 매개변수로 선언한 생성자이다.
- c를 복사하여 생성하므로 서로 같은 상태를 갖지만, 서로 독립적으로 메모리공간에 존재하는 별도의 인스턴스이므로 값들이 변경되어도 영향을 받지 않는다.
- 무작정 새로 코드를 작성하는 것보다 기존의 코드를 활용할 수 없는지 고민해야한다.
인스턴스를 생성할 때는 다음의 2가지 사항을 결정해야한다
- 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
- 생성자 - 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가?
6. 변수의 초기화
6.1 변수의 초기화
- 변수를 선언하고 처음으로 값을 저장하는 것
- 가능하면 선언과 동시에 적절한 값으로 초기화 하는 것이 바람직
- 멤버변수(클래스변수와 인스턴스변수)와 배열의 초기화는 선택적이지만, 지역변수의 초기화는 필수적이다.
-> 지역변수는 자동적으로 초기화되지 않기 때문에
멤버변수의 초기화 방법
- 명시적 초기화
- 생성자
- 초기화 블럭
명시적 초기화
- 변수를 선언과 동시에 초기화하여 가장 기본적이면서도 간단한 초기화 방법이므로 가장 우선적으로 고려되어야 한다.
class {
int door = 4; //기본형 변수의 초기화
Engine e = new Engine(); //참조형 변수의 초기화
}
초기화 블럭
초기화 작업이 복잡하여 명시적 초기화만으로는 부족한 경우 초기화 블럭을 사용한다.
class InitBlock {
static { /* static 초기화 블럭입니다. */ }
{ /* 인스턴스 초기화 블럭입니다. */ }
}
- 클래스 초기화 블럭 – 클래스변수의 복잡한 초기화에 사용
- 인스턴스 초기화 앞에 단순히 static을 덧붙이기만 하면 된다.
- 클래스가 메모리에 처음 로딩될 때 한번만 수행된다.
- 인스턴스 초기화 블럭 – 인스턴스변수의 복잡한 초기화에 사용
_ 단순히 클래스 내에 블럭{} 만들고 그 안에 코드를 작성한다.
_ 클래스의 모든 생성자에 공통으로 수행되어야 하는 문장들이 있을 때, 이 문장들을 각 생성자마다 써주기 보다는 인스턴스 블럭에 넣어주면 코드가 간결해진다.
-> 코드 중복을 제거하여 코드 신뢰성을 높여 주고, 오류의 발생 가능성을 줄여준다.
멤버변수의 초기화 시기와 순서
클래스변수의 초기화시점 – 클래스가 처음 로딩될 때 단 한번 초기화
인스턴스변수의 초기화시점 – 인스턴스가 생성될 때마다 각 인스턴스별로 초기화가 이루어진다.
클래스변수의 초기화순서 – 기본값 -> 명시적초기화 -> 클래스 초기화 블럭
인스턴스변수의 초기화순서 – 기본값 -> 명시적초기화 -> 인스턴스 초기화 블럭 -> 생성자