본문 바로가기
Book

자바의 정석 - 자바 기본기 정리하기 (5)객체지향프로그래밍1

by devLog by Ronnie's 2021. 11. 11.

들어가며


문제 구현에 있어서 자바에 대한 기본기의 부족함을 느껴서 오랜만에 자바의 기본 저서인 자바의 정석을 다시 피게 됐다. 그러면서 정말 신기한 경험을 하게 되었는데 바로 예전에 잘 이해가 안가서 읽고 넘어갔던 내용들이 이제는 내 머릿속에서 자연스럽게 그려지는 경험을 하게 되었다. 그동안에 시간들이 헛되지는 않았나보다.

 

어느 곳에서나 기본기는 중요하듯이 이번 기회를 통해 자바 기본기를 더 단단히 다지고자 챕터별로 글로 정리하면서 다시 한번 암기를 하고 좀 더 디테일하게 알아야 되는 곳은 챕터를 나눠서 자바의정석에 나온 내용 + 보강된 내용을 더해서 정리를 하고자 한다. 

 

정리



객체지향 언어
가장 큰 장점 -> 코드의 재사용성이 높고 유지보수가 용이하다.
상속과 다형성과 같은 객체지향개념을 학습할때 재사용성과 유지보수 그리고 중복된 코드의 제거, 이 세가지 관점에서 보면 보다 쉽게 이해할 수 있다.

클래스
객체를 정의해놓은 것 (객체의 설계도 또는 틀) -> 객체지향이론의 관점에서 내린 정의
데이터(변수)와 함수를 하나의 클래스에 결합한 것 (구조체+함수) -> 프로그래밍적인 관점에서 내린 정의

객체
속성과 기능, 두 종류의 구성요소로 이루어져 있으며, 일반적으로 다수의 속솽과 다수의 기능을 갖는다. 즉 객체는 속성과 기능의 집합이라고 할 수 있다.
객체의 속성과 기능을 그 객체의 멤버라 한다.

객체와 인스턴스
클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화 라고 하며 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스라고 한다.
책상은 인스턴스다 x -> 책상은 객체다 가 자연스럽고,
책상은 책상 클래스의 객체이다 x -> 보다는 책상은 책상 클래스의 인스턴스다 라고하는게 더 자연스럽다.

한파일에 여러 클래스 작성하기
보통은 하나의 소스파일에 하나의 클래스만을 정의한다. 하지만 한파일에 여러 클래스 정의도 가능하다.
이때 주의할 점은 소스파일의 이름은 public class의 이름과 일치해야 한다. 만일 소스파일 내의 public class가 없다면 소스파일의 이름은 소스파일 내의 어떤 클래스의 이름으로 해도 상관없다.

객채의 생성과 사용
클래스를 사용하려면 선언만 하는 것이 아닌 생성을 해줘야한다. 이걸 바로 생성자를 통해서 한다.

객체의 생성 과정
Tv t = new Tv();
1. Tv클래스 타입의 참조변수 t를 위한 공간이 마련된다. 
2. new Tv()에 new 연산자를 통해 Tv클래스의 인스턴스가 메모리의 빈공간에 생성(주소 할당 받음)된다. 이때, 멤버 변수는 각 자료형에 해당하는 기본값으로 초기화 된다.
3. = 그 다음 대입연산자 =에 의해서 생성된 객체의 주소값이 참조변수 t에 저장된다. 이제는 참조변수 t를 통해 Tv 인스턴스에 접근 가능하다. 인스턴스를 다루기 위해서는 참조변수가 반드시 필요하다.

객체배열
객체 역시 배열로 다루는 것이 가능하다. 그렇다고 배열 안에 객체가 저장되는것이 아니고 객체의 주소가 저장되는 것이다. 즉 객체 배열은 참조변수들을 하나로 묶은 참조변수 배열인 것이다.

클래스의 정의 - 사용자 정의 타입
프로그래밍언어에서 제공하는 기본 자료형 외에 프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것을 사용자 정의 타입이라고 한다. 기본형의 개수는 8개로 정해져 있지만 참조형의 개수가 정해져 있지 않은 이유는 이처럼 프로그래머가 새로운 타입을 추가 할 수 있기 때문이다.
선언 위치에 따른 변수의 종류 (클래스 변수(static변수, 공유변수), 인스턴스 변수, 지역변수)
멤버변수를 제외한 나저미 변수들은 모두 지역 변수이다. (메서드나 생성자, 초기화 블럭 내부 등에 선언된 것, 변수 선언문이 수행되었을때 생성된다.) -> 메서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸되어 사용할수 없게 된다.
멤버변수의 static이 붙은 것은 클래스 변수 (클래스가 메모리에 올라갈때 생성된다.) -> 인스턴스마다 독립적인 저장공간을 갖는 인스턴스 변수와는 달리 클래스 변수는 모든 인스턴스가 공통된 저장공간을 공유하게 된다. 한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우, 클래스 변수로 선언해야 한다. 클래스 변수는 인스턴스 변수와 달리 인스턴스를 생성하지 않고 언제라도 바로 사용할수 있다. 클래스이름.클래스 변수와 같은 형식으로 사용 가능하다.
붙지 않은것은 인스턴스 변수이다. (인스턴스가 생성되었을대 생성된다.) -> 인스턴스마다 고유한 상태를 유지해야하는 속성의 경우 인스턴스 변수로 선언한다.
===> 결론 !! 인스턴스 변수는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.

메서드란?
특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것이다. 어떤 값을 입력하면 이 값으로 작업을 수행해서 결과를 반환한다. 수학의 함수와 달리 메서드는 입력값 또는 출력값이 없을 수도 있으며 심지어는 입력값과 출력값 모두 없을 수도 있다.
메서드는 선언부와 구현부로 이루어져있다.
반환타입 메서드이름 (타입 변수명, ....) { --> 선언부
  // 메서드 호출시 수행될 코드  ---> 구현부
}

 


메서드의 선언부
메서드의 이름과 매개변수 선언, 그리고 반환타입으로 구성되어 있다.
매개변수를 선언할때는 타입이 같아도 변수의 타입을 생략할 수 없다. 무조건 각각 다 써줌.
반환타입은 반환값의 타입을 적는다. 반환값이 없다면 void를 적으면 된다.

메서드의 호출
메서드를 정의했어도 호출되지 않으면 아무 일도 일어나지 않는다. 호출시에 구현부의 코드들이 수행된다. 

return문
return문은 현재 실행중인 메서드를 종료하고 호출한 메서드로 되돌아간다. 반환값이 있을때만 return문을 썼지만, 원래는 반환값의 유무에 관계없이 모든 메서드에는 적어도 하나의 return문이 있어야한다. 그런데도 반환타입이 void인 경우 return문을 안써줘도 문제가 없었던 이유는 컴파일러가 메서드이 마지막에 return;을 자동적으로 추가해주었기 때문이다.

호출스택(call stack)
호출스택은 메서드의 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출되면, 호출스택에 호출된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간 결과 등을 저장하는데 사용된다. 그리고 메서드가 종료되면 할당되었던 메모리 공간은 반환되어 비워진다.
class ex {
public static void main(String[] args) {
System.out.println("hi");
}
} --> 다음 코드 실행시 
1. jvm에 의해서 main 메서드가 호출됨으로써 프로그램이 시작
2. 이때 호출 스택에는 main메서드를 위한 메모리 공간이 할당되고 main메서드의 코드가 수행되기 시작
3. main메서드에서 println()을 호출한 상태, 아직 main메서드가 끝난것은 아니므로 main메서드는 호출스택에 대기상태로 남아 있고 println()의 수행이 시작됨.
4. println메서드에 의해 hi가 화면에 출력됨
5. println 메서드의 수행이 완료되어 호출스택에서 사라지고 자신을 호출한 main메서드로 되돌아감
6. 대기중이던 main메서드는 println()을 호출한 이후부터 수행을 재개
7. main메서드에도 더이상 수행할 코드가 없으므로 종료되어 호출스택에는 완전히 비워지게 되고 프로그램은 종료.

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

기본형 매개변수와 참조형매개변수
메서드를 호출할 때 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨주게 된다. 이때 기본형일때는 기본형 값이 복사되지만 참조형이면 인스턴스의 주소가 복사된다. 그렇기 때문에 참조형으로 선언하면 값을 읽어오는 것은 물론이며 값을 변경하는 것도 가능하게 된다.

참조형 반환타입
매개변수뿐만 아니라 반환타입도 참조형이 될 수 있다. 마찬가지로 객체의 주소가 반환되는 것이다.

static메서드와 인스턴스 메서드
변수에서 그랬던 것과 같이 메서드 앞에 static이 붙으면 클래스 메서드이고 없다면 인스턴스 메서드이다.
클래스 메서드도 마찬가리로 클래스이름.메서드이름으로 호출이 가능하다. 반면 인스턴스 메서드는 반드시 객체를 생성해야만 호출이 가능하다.
그렇다면 클래스를 정의할때 어느 경우에 static을 붙어야 하는것인가? -> 클래스는 데이터와 데이터에 관련된 메서드의 집합이므로 같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있다. 인스턴스 메서드는 인스턴스 변수를 필요로 하는 메서드이다. 그런데 인스턴스 변수는 인스턴스를 생성해야만 만들어지므로 인스턴스 메서드 역시 인스턴스를 생성해야만 호출할 수 있는 것이다. 반면 메서드 중에 인스턴스와 관계없는 (인스턴스 변수나 인스턴스 메서드를 사용하지 않는) 메서드를 클래스 메서드로 정의한다. 물론 사용하지 않는다고 해서 무조건 static으로 선언해야되는 것은 아니지만 특별한 이유가 없는 한 그렇게 하는 것이 일반적이다.

static은 언제 붙이나?
1. 클래스 설계시 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
-> 생성된 각 인스턴스는 서로 독립적이기 때문에 각 인스턴스의 변수는 서로 다른 값을 유지한다. 그러나 모든 인스턴스에서 같은 값으로 유지되어야할 변수는 static을 붙여서 클래스 변수로 정의해야한다.
2. 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있다.
-> static이 붙은 변수(클래스변수)는 클래스가 메모리에 올라갈때 이미 자동적으로 생성되므로
3. 클래스 메서드는 인스턴스 변수를 사용할 수 없다.
-> 인스턴스 변수는 인스턴스가 반드시 존재해야만 사용할 수 있는데, 클래스 메서드는 인스턴스 생성 없이 호출 가능하므로 클래스 메서드가 호출되었을때 인스턴스가 존재하지 않을 수도 있다. 그래서 클래스 메서드에서 인스턴스변수의 사용을 금지한다.
반면에 인스턴스 변수나 인스턴스 메서드에서는 static이 붙은 멤버들을 사용하는 것은 언제나 가능하다. 인스턴스 변수가 존재한다는 것은 static변수가 이미 메모리에 존재한다는 것을 의미하기 때문
4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다 -> 성능 향상을 위해
- 메서드 작업내용 중에서 인스턴스 변수를 사용한다면 인스턴스 변수에 static을 붙이는 것이 불가능하지만 사용하지않는다면 static을 붙이는 것이 성능 향상을 위해 좋다. 왜냐하면 메서드 호출시간이 짧아지기 때문이다.
결론==>>
- 클래스의 멤버변수 중 모든 인스턴스에 공통된 값을 유지해야하는 것이 있는지 살펴보고 있으면 static을 붙여준다.
- 작성한 메서드 중에서 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드에 static을 붙일 것을 고려한다. 

메서드 간의 호출과 참조
같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다. 단 클래스 멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우는 인스턴스를 생성해야 가능하다 (인스턴스 멤버가 존재하는 시점엔 클래스멤버는 항상 존재하지만 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문)

오버로딩
메서드도 변수와 마찬가지로 같은 클래스 내에서 서로 구별될 수 있어야 하기 때문에 각기 다른 이름은 가져야 한다. 그러나 자바에서는 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도 매개변수의 개수 또는 타입이 다르다면 같은 이름을 사용해서 메서드를 정의할 수 있도록 하는 것이 오버로딩이라고 한다.
-> 이름만 같다고 무조건 오버로딩이 아니고 메서드의 이름이 같으면서 매개변수 개수 또는 타입이 달라야 성립한다.
이름이 같더라도 매개변수가 다르기 때문에 서로 구별될 수 있기 때문에 오버로딩이 가능한 것이다. 만약 이 조건이 성립이 안될시 메서드 중복 정의로 간주하여 컴파일시에 에러가 발생한다.  이때 주의점은 오버로딩된 메서드들은 매개변수에 의해서만 구별될 수 있으므로 반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.
--> 오버로딩에 가장 대표적인 예로는 println이다. 어떤 종류의 매개변수를 지정해도 출력할 수 있었던 것이 바로 오버로딩 된 서로 다른 타입의 매개변수로 정의를 해놨기 때문이다.

생성자
생성자는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드이다. 따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 하는 작업을 위해서도 사용된다.
생성자 역시 메서드처럼 클래스 내에 선언되며, 구조도 메서드와 유사하지만 리턴값이 없다는 점이 다르다. 그렇다고 void를 적는 것은 아니며  아무것도 적지 않는다. 

생성자의 조건
1. 생성자의 이름은 클래스의 이름과 같아야한다.
2. 생성자는 리턴 값이 없다 (생성자도 메서드이기 때문에 리턴값이 없다는 의미의 void를 붙여야하지만 모든 생성자가 없기때문에 생략할수있게함)
생성자도 오버로딩이 가능하다. 그러므로 여러개의 생성자가 존재할 수 있다.

생성자 코드
-> 클래스이름 (타입 변수명, ...) {
 //인스턴스 생성 시 수행될 코드
 // 주로 인스턴스 변수의 초기화 코드를 적음
}
-> 이때 연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다. 생성자라는 용어 때문에 오해하기 쉽지만 생성자는 단순히 인스턴스 변수들의 초기화에 사용되는 조금 특별한 메서드일 뿐이지 생성자가 갖는 몇 가지 특징만 제외하면 메서드와 다르지 않다.

기본생성자
-모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다. -> 그런데 클래스에 생성자를 정의하지 않고도 인스턴스를 생성할 수 있었던 이유는 컴파일러가 제공하는 기본생성자 덕분이다. -> 특별히 인스턴스 초기화 작업이 요구되어지지 않는다면 생성자를 정의하지 않고 컴파일러가 제공하는 기본 생성자를 사용하는 것도 좋다.

매개변수가 있는 생성자
생성자도 메서드처럼 매개변수 값이 있다면 호출시 값을 넘겨받아서 인스턴스의 초기화 작업에 사용할 수 있다. 인스턴스마다 각기 다른 값으로 초기화되어야 하는 경우가 많기 때문에 매개변수를 사용한 초기화는 매우 유용하다.
만약 어떤 값을 가지 인스턴스를 생성하려고 할때 기본 생성자를 이용한다면 생성 이후 특정 값을 넣어주는 작업이 필요하지만 매개변수를 가지고 있는 생성자를 이용한다면 생성하는 동시에 값을 주어 초기화 시킬 수 있다. 뿐만 아니라 제 3자로 하여금 생성자 코드를 보면 직관적으로 이 클래스는 다음과 같은 값을 가진 인스턴스로 초기화되는 구나 라는 것을 알 수도 있다.

생성자에서 다른 생성자 호출하기 - this()
같은 클래스의 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능하다. 단 생성자의 이름으로 클래스 이름 대신 this를 사용해야되며, 한 생성자에서 다른 생성자를 호출할때는 반드시 첫 줄에서만 호출이 가능하다는 점만 만족하면 된다.

객체 자신을 가리키는 참조변수 - this
생성자의 매개변수의 이름과 인스턴스 변수의 이름이 같을때는 구분이 안되므로 this를 붙여준다. 만약 이때 this를 안붙여주면 같은 이름의 두 변수는 둘 다 지역변수로 취급받는다. this를 사용하지 않기 위해 변수 이름을 달리하는 것보다 this를 사용해서 구별되도록 하는 것이 의미가 더 명확하고 이해하기 쉽다. 이때 this를 사용할 수 있는건 인스턴스 멤버 뿐이다. static 메서드에서는 인스턴스 멤버들을 사용할 수  없는 것처럼 this역시 사용할 수 없다.
Car(String color, int door) {
this.color = color;
this.door = door;
}
사실 생성자를 포함한 모든 인스턴스메서드에는 자신이 관련된 인스턴스를 가리키는 차조변수 this가 지역변수로 숨겨진 채로 존재한다.

변수의 초기화
변수를 선언하고 처음으로 값을 저장하는 것을 변수의 초기화라고 한다. 변수의 초기화는 경우에 따라 필수적이거나 선택적이기도 하지만 가능하면 선언과 동시에 적절한 값으로 초기화 하는 것이 바람직하다.
멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 되어 초기화를 하지 않고 사용해도 되지만 지역변수는 사용하기 전에 반드시 초기화를 해줘야 한다. 이 상태에서 컴파일시 컴파일러가 에러를 발생 시킨다. 즉 지역변수의 초기화는 필수이다.
참고로 각 타입의 기본값은 다음과 같다
boolean == false / char == '\u0000' / byte short int == 0 / long == 0L / float == 0.0f / double == 0.0d or 0.0 / 참조형 == null

멤버변수의 초기화
멤버변수는 각 타입의 기본값으로 자동 초기화 된다. 멤버변수의 초기화에 대해서는 이 두가지만 기억하면된다.
1. 클래스 변수 초기화 -> 인스턴스 변수 초기화 순으로 초기화 됨
2. 자동 초기화 -> 명시적 초기화 (간단) -> 초기화 블럭, 생성자 (복잡)
명시적 초기화
-> 변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다. 가장 기본적이면서도 간단한 초기화 방법이므로 여러 초기화 방법 중에서 가장 우선적으로 고려되어야한다.
초기화 블력
- 클래스 초기화 블럭과 인스턴스 초기화 블럭 두가지가 존재한다. 클래스 초기화 블럭은 클래스 변수의 초기화에 사용되고 인스턴스 초기화 블럭은 인스턴스 변수의 초기화에 사용된다.
class Ex {
static {
 클래스초기화블럭
}
{
 인스턴스 초기화 블럭
}
public Ex() {
 생성자 블럭
}
}
순서 -> 클래스초기화 블럭이 가장 먼저 수행 -> 인스턴스가 생성되면서 인스턴스 초기화 블럭 수행 -> 끝으로 생성자가 수행
--> 여기서 클래스초기화 블럭은 처음 메모리에 로딩될때 한번만 수행되고 인스턴스 초기화 블럭은 인스턴스가 생성돨때 마다 수행된다.

댓글