개발/☕ JAVA

☕ [ Java의 정석 ] Chapter 06. 객체지향 프로그래밍 I

정소은 2025. 1. 6. 11:09

 

 

 

1.  객체 지향 언어

 

 

객체 지향 언어의 특징

 

1 )  코드의 재사용성이 높다

: 새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다

2 )  코드의 관리가 용이하다

: 코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다

3 )  신뢰성이 높은 프로그래밍을 가능하게 한다

: 제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 하며, 

코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다

 

💡객체지향개념을 학습할 때 재사용성유지보수, 중복된 코드의 제거, 이 세가지 관점에서 보면 보다 쉽게 이해할 수 있다

 

 

2.  클래스와 객체

 

1 )  클래스와 객체의 정의와 용도

  클래스 객체
정의 객체를 정의해놓은 것 실제로 존재하는 사물 또는 개념
용도 객체를 생성하는 데에 사용 객체 내의 속성과 기능에 따라 다름

-  클래스가 "설계도"라면 객체는 설계도대로 찍어낸 "제품"

-  객체는 클래스에 정의한 대로 생성됨

 

2 )  객체와 인스턴스

인스턴스 : 특정한 클래스를 통해 생성된 특정한 객체

객체는 클래스를 통해 생성된 각각의 인스턴스들을 대표하는 포괄적인 의미를 가진다 

 

3 )  객체의 구성요소 - 속성과 기능

객체는 여러 속성들과 여러 기능들의 집합이다

클래스에는 객체의 모든 속성기능이 정의되어 있고

클래스를 통해 생성한 객체에는 클래스에 정의된 모든 속성과 기능이 포함되어 있다

속성 = 멤버변수, 특성, 필드, 상태
기능 = 메서드, 함수, 행위

 

class Tv {
	// 속성
	String color,
    boolean power;
    int channel;
    
    // 기능
    void power() {power = !power;}
    void channelUp() {channel++;}
    void channelDown() {channel--;}
}

 

4 )  인스턴스의 생성과 사용

class Tv {
	// 속성
	String color,
    boolean power;
    int channel;
    
    // 기능
    void power() {power = !power;}
    void channelUp() {channel++;}
    void channelDown() {channel--;}
}

// 인스턴스 생성
Tv t;
t = new Tv();

// 인스턴스 사용
t.channel = 8;
t.channelUp();

 

Tv t;

-  Tv 클래스 타입의 참조변수 t 생성

-  메모리에 참조변수 t를 위한 공간 생성

-  멤버변수는 각 자료형에 해당하는 기본값으로 초기화

 

t = new Tv();

-  연산자 new 를 통해 Tv클래스의 인스턴스를 위한 공간 생성

-  대입연산자(=)를 통해 참조 변수 t에 생성된 인스턴스의 주소 저장 

 

t.channel = 8;
t.channelUp();

-  오직 참조변수를 통해서만 인스턴스의 속성과 기능에 접근 가능

-  참조변수 하나가 여러 인스턴스를 가르킬 수 없지만 인스턴스 하나를 여러 참조변수가 가르킬 수 있음

 

5 )  객체 배열

객체 배열이란 참조 변수의 배열과 같다

인스턴스 생성해서 참조변수와 연결하는 작업 잊지 말기!

 

6 )  클래스의 또 다른 정의 

객체 지향적 관점에서 클래스는 객체를 정의해놓은 것이며, 속성과 기능으로 구성되어 있다

프로그래밍적인 관점에서 클래스는 데이터와 함수의 결합을 의미한다

변수 > 배열 > 구조체 > 클래스

변수 : 하나의 데이터를 저장
배열 : 한 종류의 데이터를 여러 개 저장
구조체 : 여러 종류의 데이터를 여러 개 저장
클래스 : 여러 종류의 데이터 여러 개와 함수 저장

 

 

3.  변수와 메서드

 

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

변수의 종류 선언 위치 생성시기
클래스 변수 클래스 영역
(static으로 선언되었을 경우)
클래스가 메모리에 올라갈 때
인스턴스 변수 클래스 영역
(static이 아닌 경우)
인스턴스가 생성되었을 때
지역 변수 클래스 영역 이외의 영역
(메서드, 생성자, 초기화 블럭 내부)
변수 선언문이 수행되었을 때

 

2 )  클래스변수와 인스턴스변수

 

클래스 변수

: 인스턴스 변수 앞에 static을 붙이기만 하면 됨

:  한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야 하는 속성일 경우, 클래스 변수로 선언

:  인스턴스를 생성하지 않아도 클래스가 메모리에 로딩되었다면 언제든지 사용 가능

:  public을 붙이면 프로그램 내에서 어디서나 접근 가능한 전역 변수의 성질을 띔

 

인스턴스 변수

:  클래스 영역에 선언됨

:  인스턴스를 생성해야만 사용 가능

:  인스턴스마다 고유한 상태를 유지해야 하는 속성의 경우 인스턴스 변수로 선언

 

💡 클래스 변수와 인스턴스 변수

인스턴스 변수는 인스턴스가 생성될 때마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만,
클래스 변수는 모든 인스턴스가 하나의 저장 공간을 공유하므로, 항상 공통된 값을 갖는다

 

지역 변수

:  지역 변수가 선언된 블럭 내에서만 사용 가능

:  블럭을 벗어나면 소멸

 

3 )  메서드

메서드 : 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것, 수학의 함수와 유사

 

메서드를 사용하는 이유

1.  높은 재사용성
2.  중복된 코드의 제거
3.  프로그램의 구조화

 

반복적으로 나타나는 문장들을 메서드로 생성하자!

 

4 )  메서드의 선언과 구현

 

메서드는 선언부구현부로 이루어져있다

 

메서드 선언부

:  메서드의 이름, 매개변수 선언, 반환타입으로 구성되어 있음

 

메서드 구현부

:  메서드를 호출했을 때 수행될 문장들 삽입

-  return문 : return문 통해서 메서드의 선언부에서 지정한 반환타입과 동일하거나 자동 형변환 가능한 타입의 변수 반환

-  지역변수 : 메서드 내에서만 사용 가능

 

5 )  메서드의 호출

메서드를 호출해야만 메서드 구현부의 내용이 실행됨

 

인자와 매개변수

-  인자 : 메서드 호출할 때 넣는 값

-  매개변수 : 메서드 선언할 때 넣는 값 

-  인자의 타입과 개수 = 매개변수의 타입과 개수

 

메서드의 실행 흐름

메서드 호출 > 인자 값이 매개변수에 저장 > 구현부 실행 > 메서드 호출한 곳으로 돌아감

 

6 )  return문

 

( 1 ) 반환타입이 void일 경우

return문 따로 없어도 됨 -> 컴파일러 단에서 return; 자동 삽입

( 2 ) 반환타입이 void가 아닐 경우

-  return문 뒤에 오는 값의 타입이 메서드의 선언부에서 지정한 반환타입과 동일하거나 자동 형변환이 가능한 타입이어야 함

-  조건문의 경우, 모든 조건에서 return문이 있어야 함

-  변수 뿐만 아니라 문장이 와도 됨

 

매개변수의 유효성 검사를 하자!

 

7 )  JVM의 메모리 구조

 

JVM의 메모리 구조 : 메서드 영역, 콜 스택, 힙

( 1 ) 메서드 영역

클래스 데이터(클래스 변수) 저장

( 2 ) 힙

인스턴스 데이터(인스턴스 변수) 저장

( 3 ) 콜 스택

-  메서드 관련 데이터(중간 연산 결과, 지역변수 등) 저장

-  후입선출

1.  call stack이 비어 있음
2.  main 호출  ->  call stack에 main 삽입
3.  firstMethod 호출  ->  main 중단하고 firstMethod 실행  ->  call stack에 firstMethod 삽입
4.  secondMethod 호출  ->  firstMethod 중단하고 secondMethod 실행  ->  call stack에 secondMethod 삽입
5.  println 호출  ->  secondMethod 중단하고 println 실행  ->  call stack에 println 삽입
6.  println 실행 완료  ->  println 호출했던 secondMethod로 회귀  ->  call stack에서 println 삭제
7.  secondMethod 실행 완료  ->  secondMethod 호출했던 firstMethod로 회귀  ->  call stack에서 secondMethod 삭제
8.  firstMethod 실행 완료  ->  firstMethod 호출했던 main으로 회귀  ->  call stack에서 firstMethod 삭제
9.  main 실행 완료  ->  call stack에서 main 삭제

 

8 )  기본형 매개변수와 참조형 매개변수

 

기본형 매개변수 : 변수의 값을 읽기만 할 수 있음

->  메서드 종료되면 매개변수에 저장했던 값 삭제됨

 

참조형 매개변수 : 변수의 값을 읽고 변경할 수 있음

->  인스턴스의 주소를 넘겨주면 주소에 접근해서 변수값 변경 가능

 

9 )  참조형 반환타입

💡 반환타입이 참조형이라는 것은 메서드가 객체의 주소를 반환한다는 것을 의미한다

 

10 )  재귀 호출

 

재귀 호출 : 메서드 내부에서 메서드 자신을 다시 호출하는 것

-  값에 의한 호출  ->  복사된 값으로 작업

-  무한 반복에 빠지지 않으려면 조건문과 함께 사용되어야 함

 

재귀 호출의 장점 : 논리적 간결함

재귀 호출의 단점 : 매개변수 복사, 종료 후 복귀할 주소 저장 등이 추가로 필요, 수행 시간이 오래 걸림

 

11 )  클래스 메서드(static메서드)와 인스턴스 메서드

 

인스턴스 메서드 : 인스턴스 변수와 관련된 작업을 하는, 인스턴스 변수를 필요로 하는 메서드

클래스 메서드 : 인스턴스 변수나 메서드를 사용하지 않는 메서드

 

 

1.  클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static 붙인다
2.  클래스 변수는 인스턴스를 생성하지 않아도 사용 가능하다
3.  클래스 메서드는 인스턴스 변수를 사용할 수 없다
4.  메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다

 

12 )  클래스 멤버와 인스턴스 멤버간의 참조와 호출

 
호출하는 곳 \ 호출받는 곳 클래스 인스턴스
클래스 호출 가능 호출 불가
( 클래스 내에 객체 생성해야 사용 가능 )
인스턴스 호출 가능 호출 가능

 

 

4.  오버로딩

 

1 )  오버로딩이란?

한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것

 

2 )  오버로딩의 조건

1.  메서드 이름이 같아야 한다
2.  매개변수의 개수 혹은 타입이 달라야 한다

 

3 )  오버로딩의 예

 

우리가 빈번하게 사용하는 println() 메서드도 사실 아래와 같이 매개변수의 타입이 다르게 오버로딩된 메서드다

void println()
void println(boolean x)
void println(char x)
void println(char[] x)
void println(double x)
void println(float x)
void println(int x)
void println(long x)
void println(Object x)
void println(String x)

 

4 )  오버로딩의 장점

1.  근본적으로 같은 기능을 하는 메서드들을 하나의 이름으로 통일할 수 있다
->  기억하기도 쉽고 메서드 이름을 통해서 기능을 예측하기도 쉽다

2.  메서드의 이름을 절약할 수 있다

 

5 )  가변인자와 오버로딩

 

가변인자 : 메서드의 매개변수 개수를 동적으로 지정해줄 수 있는 기능

public PrintStream printf(String format, Object... args) {}

-  선언 형식 : "타입... 변수명" 

-  매개변수 중 가장 마지막에 선언

-  내부적으로 배열을 사용 -> 가변인자가 선언된 메서드 호출할 때마다 배열이 새로 생성됨

-  배열을 매개변수로 가지는 메서드와 달리 인자를 아예 생략해도 됨

 

가변인자와 오버로딩

가변인자를 사용한 메서드와 사용하지 않은 메서드는 구별되지 않는 경우가 왕왕 있기 때문에

가변인자를 사용한 메서드는 오버로딩하지 않는 것이 좋음 

 

 

5.  생성자

 

1 )  생성자란?

 

생성자 : 인스턴스 초기화 메서드

인스턴스 초기화란, 인스턴스 변수들을 초기화하는 것

 

생성자의 조건

1.  생성자의 이름은 클래스의 이름과 같아야 함

2.  리턴값이 없어야 함

3.  클래스 내에 선언

Class Car {
	String color;
    String gearType;
    int door;
    
    Car(String c, String g, int d) {
    	color = c;
        gearType = g;
        door = d;
    }
}

 

2 )  기본 생성자

 

기본 생성자 : 개발자가 생성자를 정의하지 않았을 때 컴파일러 단에서 자동으로 추가해주는 생성자

-  매개 변수도, 내용도 없음

-  개발자가 클래스 내에 생성자를 하나라도 정의해놨을 경우, 기본생성자는 생성되지 않음

Class Car {
	String color;
    String gearType;
    int door;
    
    // 기본 생성자
    Car() {}
}

 

3 )  매개변수가 있는 생성자

 

생성자도 메서드처럼 오버로딩 가능!

Class Car {
	String color;
    String gearType;
    int door;
    
    // 기본 생성자
    Car() {}
    
    // 생성자2
    Car(String c, String g) {
    	color = c;
        gearType = d;
    }
    
    // 생성자3
    Car(String c, String g, int d){
    	color = c;
        gearType = d;
        door = d;
    }
}

 

4 )  생성자에서 다른 생성자 호출하기 - this(), this

 

this() : 생성자 내에서 오버로딩된 다른 생성자 호출할 때 사용

-  생성자의 이름으로 클래스 이름 대신 this 사용

-  한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출 가능

Class Car {
	String color;
    String gearType;
    int door;
    
    // 기본 생성자
    Car() {}
    
    // 생성자2
    Car(String c, String g) {
    	color = c;
        gearType = d;
    }
    
    // 생성자3
    Car(String c, String g, int d){
    	this(c, d) 		// this 통해서 생성자2 호출
    	color = c;
        gearType = g;
        door = d;
    }
}

 

this : 인스턴스 자신을 가르키는 참조변수

-  인스턴스의 주소가 담겨 있음

-  this 통해서 인스턴스 변수 접근

-  클래스 멤버에서는 사용할 수 없음

Class Car {
	String color;
    String gearType;
    int door;
    
    Car(String color, String gearType, int door){
    	// this 통해서 인스턴스에 접근 후 변수값 초기화
    	this.color = color;
        this.gearType = gearType;
        this.door = door;
    }
}

 

5 )  생성자를 이용한 인스턴스의 복사

Class Car {
	String color;
    String gearType;
    int door;
    
    Car(Car c) {
    	this.color = c.color;
        this.gearType = c.gearType;
        this.door = c.door;
    }
}

-  매개변수로 Car 인스턴스의 주소를 받아서 인스턴스에 직접 접근해서 변수값 변경

 

 

💡 인스턴스를 생성할 때 결정할 사항

1.  어떤 클래스의 인스턴스를 생성할 것인가
2.  어떤 생성자를 활용해서 생성할 것인가

 

 

6.  변수의 초기화

 

1 )  변수의 초기화

 

변수의 초기화

:  변수를 선언하고 처음으로 값을 저장하는 것

-  멤버 변수(클래스변수, 인스턴스변수)는 따로 초기화하는 코드 없이도 변수의 자료형에 맞는 기본값으로 초기화됨

-  지역변수는 사용하기 전에 반드시 초기화하는 코드가 필요함

 

멤버변수의 초기화 방법

1.  명시적 초기화
2.  생성자
3.  초기화 블럭
     -  인스턴스 초기화 블럭
     -  클래스 초기화 블럭

 

2 )  명시적 초기화

말그대로 변수를 선언과 동시에 초기화하는 것

Class Car {
	int door = 4;
}

 

3 )  초기화 블럭

초기화 블럭에는 클래스 멤버를 위한 클래스 초기화 블럭과 인스턴스 멤버를 위한 인스턴스 초기화 블럭이 있는데

둘의 형식적 차이점은 static의 여부이다

클래스 초기화 블럭은 인스턴스 초기화 블럭 앞에 static을 덧붙이기만 하면 된다

앞선 명시적 초기화보다 복잡한 초기화 과정(조건문, 반복문 등)이 필요할 때 사용한다

 

4 )  멤버변수의 초기화 시기와 순서

  초기화 시점 초기화순서
클래스변수 클래스가 처음 로딩될 때 기본값 -> 명시적 초기화
-> 클래스 초기화 블럭
인스턴스변수 인스턴스가 생성될 때마다 기본값 -> 명시적 초기화
-> 인스턴스 초기화 블럭 -> 생성자