Cute Running Puppy

Language/[Java] 자바의 정석

[자바의 정석] chapter06. 객체지향 프로그래밍1 (2)

R.silver 2023. 2. 10. 14:13
반응형

오버로딩 (overloading)

오버로딩이란?

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

오버로딩의 조건

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

이름이 같더라도 매개변수가 다르기에 메서드가 구분될 수 있다.

조건을 만족시키지 않으면 중복 정의로 간주되어 컴파일 에러가 발생한다

반환 타입을 통해서만 구별되므로 반환타입은 영향을 미치지 않는다 (반환타입만 다르면 함수 중복으로 간주된다)

매개변수의 순서가 다르더라도 오버로딩된다

// 오버로딩 가능 
long add (int a, long b);
long add (long a, int b);

사용자가 매개변수의 순서를 외우지 않아도 되지만

add(3,3)과 같이 호출할 경우 어느 메서드가 호출된 것인지 판단할 수 없어 컴파일 에러가 발생한다

오버로딩의 예

println

매개변수로 지정하는 값의 타입에 따라 호출되는 println 메서드가 달라진다

오버로딩의 장점

메서드가 이름으로만 구별된다면 한 클래스내의 메서드들은 모두 이름이 달라야 한다

예를 들어 println 메서드는 매개변수의 타입에 따라 println(), printlnBoolean(), printlnChar() 등으로 구별 되어야 한다

하지만 오버로딩을 통해 여러 메서드들을 하나의 메서드로 정의할 수 있다면 기억하기 쉽고, 이름을 짧게 할 수 있어 오류의 가능성을 줄일 수 있다.

두번째 장점은 메서드의 이름을 절약할 수 있다는 것이다

가변인자(varargs)와 오버로딩

메서드의 개수를 동적으로 지정해줄 수 있는 것

타입... 변수명 의 형식으로 선언

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

가변인자는 항상 마지막에 선언해야 한다

가변인자인지 아닌지 구별할 방법이 없기에 컴파일 에러가 발생한다

가변인자를 사용하여 선언된 메서드를 호출할 때 인자를 개수를 가변적으로 할 수 있다

(없어도 되고, 배열을 넣어도 된다)

가변인자는 내부적으로 배열을 사용하기에 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성된다 (비효율적)

파라미터로 배열 vs 가변인자

메서드의 파라미터로 배열을 준다면 반드시 인자를 주어야 하기에 인자를 생략할 수 없다

그러나 가변인자로 파라미터를 준다면 인자가 없어도 되기에 인자를 생략할 수 있다

주의할 점

가변인자를 선언한 메서드를 오버로딩하면

메서들를 호출했을 때 구분되지 못하는 경우가 발생하기에 주의해야 한다

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

static String concatenate (String delim, String... args) {
	String result = "";
	for (String str: args) {
		result += str + delim;
	}
	return result;
}

static String concatenate (String... args) {
	return concatenate("", args);
}

두 함수를 구별할 수 없기에 컴파일 에러가 발생한다

생성자 (Constructor)

생성자란?

인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드

인스턴스 변수의 초기화 작업에 주로 사용되며

생성시 실행되어야 할 작업을 위해서도 사용된다

메서드와 유사하지만 리턴값이 없다는 것이 다르다

생성자 조건

  1. 생성자의 이름은 클래스의 이름과 같아야 한다
  2. 생성자는 리턴 값이 없다
클래스이름(타입 변수명, 타입 변수명, ...) {
	// 인스턴스 생성시 수행될 코드, 
	// 주로 인스턴스 변수의 초기화 코드를 적는다
}

// 매개변수가 없는 생성자 
class Card {
	Card() {
	...
	}

// 매개변수가 있는 생성자 
class Card(String k, int num) {
	...
}

연산자 new가 인스턴스를 생성하는 것이지

생성자가 인스턴스를 생성하는 것이 아니다

인스턴스를 생성하는 과정

Card c = new Card();

  1. new 연산자에 의해 메모리(heap)에 Card 클래스의 인스턴스가 생성
  2. 생성자 Card()가 호출되어 수행
  3. 연산자 new의 결과로, 생성된 Card의 인스턴스 주소가 반환되어 참조변수 c에 저장

new Card()에서 Card()가 생성자였던 것!

기본 생성자 (default constructor)

모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다

컴파일시 소스파일(*.java)에 생성자가 하나도 정의되어 있지 않은 경우

컴파일러는 자동적으로 아래와 같은 기본 생성자를 추가하여 컴파일한다

클래스이름() {}

// example
Card {]

특별히 초기화되는 작업이 필요하지 않다면 컴파일러가 제공하는 기본 생성자를 사용하는 것도 좋다

그러나 기본 생성자는 정의되어 있는 생성자가 하나도 없을 경우에만 생성되므로

기본 생성자를 사용하고 싶다면 직접 정의해주어야 한다

매개변수가 있는 생성자

생성자도 메서드처럼 매개변수를 선언하여 인스턴스의 초기화 작업에 사용할 수 있다

각기 다른 값으로 초기화되어야 하는 경우에 매개변수가 있는 생성자를 사용하면 된다

장점

  1. 인스턴스 생성과 동시에 초기화 가능 (원하는 값으로)
  2. 코드를 간결하고 직관적으로 작성할 수 있다

클래스를 작성할 때 다양한 생성자를 제겅하여 인스턴스 생성 후에 별도로 초기화를 하지 않도록 하는 것이 바람직하다

class Car {
	String color;
	String gearType;
	int door;

	Car() {} // 자동으로 기본 생성자가 생성되지 않기에 직접 추가 
	Car(String c, String g, int d) {
		color = c;
		gearType = g;
		door = d;
		}
}

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

같은 클래스의 멤버들 간에 서로 호출할 수 있는 것 처럼

생성자 간에도 서로 호출이 가능하다

조건

  1. 생성자의 이름으로 클래스 이름 대신 this를 사용
  2. 한 생성자에서 다른 생성자를 호출할 대에는 반드시 첫 줄에서만 호출 가능
  3. (생성자 내에서 초기화 작업 도중 다른 생성자를 호출하면, 호출된 다른 생성자 내에서도 멤버변수들을 초기화 하므로 다른 생성자들을 호출하기 이전의 초기화 작업이 무의미해진다)

같은 클래스 내의 생성자들은 일반적으로 서로 관계가 깊어서 서로 호출하도록 하여 유기적으로 연결해주면 더 좋은 코드를 얻을 수 있다

수정이 필요한 경우 보다 적은 코드만을 변경하면 되므로 유지보수가 쉬워진다

Car() {
	this ("white", "auto", 4); // Car(String color, String gearType, int door) 호출
}

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

this

인스턴스 자신을 가리키는 참조변수

this 를 통해 인스턴스 변수에 접근할 수 있다 (인스턴스멤버만 사용 가능 - static 메서드가 호출된 시점에 인스턴스가 존재하지 않을 수 있기에)

staic 메서드는 인스턴스와 관련 없는 작업을 하므로 인스턴스에 대한 정보가 필요없다

생성자의 매개변수로 선언된 변수의 이름과 생성자의 매개변수로 생성된 변수의 이름이 동일한 경우에는 인스턴스 변수 this를 사용하여 구분해준다

this.color: 인스턴스 변수

color: 매개변수로 정의된 지역변수

생성자의 매개변수로 인스턴스변수들의 초기값을 제공받는 경우가 많기에 매개변수와 인스턴스 변수의 이름이 일치하는 경우가 자주 있다

이럴 경우에는 매개 변수의 이름을 다르게 하는 것 보다 this를 사용하여 구별되도록 하는 것이 더 명확하고 이해하기 쉽다

<aside> 📌 this - 참조변수 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장 모든 인스턴스메서드에 지역변수로 숨겨진 채로 존재

</aside>

<aside> 📌 this(). this(매개변수) - 생성자 생성자, 같음 클래스의 다른 생성자를 호출할 때 사용

</aside>

둘은 비슷하게 생겼지만 완전히 다른 것 (참조변수와 생성자)

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

현재 사용하고 있는 인스턴스와 같은 상태의 인스턴스를 만들고 싶을 때 생성자를 활용할 수 있다

두 인스턴스가 같은 상태이다 == 인스턴스의 모든 인스턴스 변수가 동일한 값을 가지고 있다

인스턴스의 상태를 전혀 모르더라도 인스턴스를 복사할 수 있다

class Car {
	...
	// 인스턴스의 복사를 위한 생성자 
	Car(Car c) {
		color = c.color;
		gearType = c.gearType;
		door = c.door;
	}
	...

	// 기존 생성자를 활용한 복사 생성자 
	Car(Car c) {
		this(c.color, c.gearType, c.door);
	}
}

인스턴스를 생성할 때는 결정해야 하는 것

  1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
  2. 생성자 - 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가?

변수의 초기화

변수의 초기화

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

가능하면 선언과 동시에 적절한 값으로 초기화 하는 것이 좋다

초기화를 하지 않으면

멤버변수는 기본값으로 초기화되지만,

지역변수는 반드시 사용자가 초기화 해주어야 한다

<aside> 📌 멤버변수와 배열의 초기화는 선택적이지만, 지역변수의 초기화는 필수적이다

</aside>

멤버변수의 초기화 방법

  1. 명시적 초기화 (explicit initialization)
  2. 생성자 (constructor)
  3. 초기화 블럭(initialization block)클래스 초기화 블럭; 클래스변수를 초기화 하는데 사용
  4. 인스턴스 초기화 블럭: 인스턴스변수를 초기화 하는데 사용

명시적 초기화 (explicit initalization)

변수를 선언과 동시에 초기화 하는 것

여러 초기화 방법 중 가장 우선적으로 고려

초기화 블럭(initialization block)

  1. 인스턴스 초기화 블럭인스턴스를 생성할 때 마다 (생성자와 같이, 생성자보다 먼저)
  2. 인스턴스변수의 복잡한 초기화에 사용
  3. 클래스 초기화 블럭클래스가 메모리에 로딩될 때 한번만 수행
  4. 클래스 변수의 복잡한 초기화에 사용

클래스가 처음 로딩될 때 클래스 변수들이 자동적으로 메모리에 만들어지고, 곧바로 클래스 초기화블럭이 클래스변수들을 초기화

작성 방법

  1. 인스턴스 초기화 블럭
  2. 클래스 내에 블럭을 만들고 코드 작성
  3. 클래스 초기화 블럭
  4. 인스턴스 초기화 블럭 앞에 static 붙이기

초기화 블럭 내에서는 조건문, 반복문, 예외 처리 등을 자유롭게 사용할 수 있다

명시적 초기화로 부족한 경우 초기화 블럭을 사용

class InitBlock {
	static { 
		//클래스 초기화 블럭 
	}
	{
		//인스턴스 초기화 블럭
	}
	...
}

인스턴스 변수의 초기화는 주로 생성자를 이용하고

인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행되어야 하는 코드를 넣는다

{
	// 인스턴스 초기화 블럭 
	// 모든 생성자에서 사용되는 코드
	count++;
	serialNo = count;
}

Car() {
	color = "white";
	gearType = "Auto";
}

Car(String color, String gearType) {
	this.color = color; 
	this.gearType = gearType;
}

코드의 중복을 줄여 오류의 가능성을 줄여준다

재사용성을 높이고 중복이 제거된다 ⇒ 객체 지향 프로그래밍이 추구하는 목표

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

클래스 변수 인스턴스 변수

초기화 시점 단 한번 클래스가 처음 로딩될 때 인스턴스가 생성될 때마다 각 인스턴스별로 초기화
초기화 순서 기본값 → 명시적초기화 → 클래스 초기화 블럭 기본값 → 명시적 초기화 → 생성자
class InitTest {
	// 명시적 초기화 
	static int cv = 1;
	int iv = 1;

	// 클래스 초기화 블럭 
	static { cv = 2; }
	// 인스턴스 초기화 
	{ iv = 2; }
	// 생성자 
	InitTest() {
		iv = 3;
	}
}
반응형