변수와 메서드는 클래스의 기본 요소이다. 이번 시간엔 이 둘이 객체지향적으론 어떻게 응용되는지 알아보자.
한 클래스 내의 동명의 메서드를 여러 개 정의하는 것.
메서드의 기능은 같지만 사용자가 입력하는 인자가 다른 경우가 있다.
예를 들면 println()
가 있다. 사용할 때는 한 가지 메서드 같지만 실제로는 매개변수가 서로 다른 동명의 메서드들이 있다.
즉, 오버로딩은 같은 기능을 하지만, 사용하는 인자가 다를 경우 적용한다.
이름만 같다고 해서 오버로딩이 아니다. 다음 두 조건을 충족해야된다.
메서드의 이름이 같아야 됨.
매개 변수의 개수 or 타입이 달라야됨.
* 반환타입은 영향을 미치지 못한다.
1. 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)
println()
의 오버로딩 유형이다.2. 매개변수명은 다르지만, 타입이 같은 경우
int add(int a, int b){ return 1 }
int add(int x, int y){ return 1 }
3. 리턴 타입만 다른 경우
int add(int a, int b){ return 1 }
long add(int a, int b{ return 1 }
4. 매개변수의 순서가 다른 경우
long add(int a, long b){ return 1 }
long add(long a, int b){ return 1 }
(3, 3)
을 넘기면 타입 불일치로 메서드 구분이 불가능해 컴파일 에러가 난다.println()
을 보면, 장점은 뚜렷하다.
메서드명(타입... 변수명);
기존 메서드를 정의할 때, 매개변수의 수는 고정적이다. (유동적으로 조절 x)
막상, 상황에 따라 매개변수의 수가 다르거나 아예 정할 수 없을 수도 있다.
그러면 모든 상황에 맞게 메서드를 오버로딩을 해야된다.
그래서 호출 시점에 매개변수를 동적으로 제어하는 기능인 '가변인자'가 만들어졌다.
말 그대로 매개변수가 몇개나 들어올지 모른다.
가변인자는 다른 매개변수들과의 구분이 어렵다,.
그래서 무조건 매개변수의 마지막 순서에 넣어야 된다.
// 1. 구분 가능
public static int add(int a, int... b){return 0};
/* add(3,5,6,7);
순서에 따라 3은 무조건 a의 값이 된다.
*/
// 2. 구분 불가
public static int add(int... a, int b){return 0};
/* add(3,5,6,7);
7이 가변인자인지, b의 값인지 구분할 수 없다.
때문에 가변인자가 있는 경우 오버로딩된 메서드끼리 구분이 불가능하기도 하다.
*/
1. 매개변수 타입이 같은 오버로딩 메서드를 하나로 대체할 수 있다.
// 오버로딩
String concatenate(String s1, String s2) { ... }
String concatenate(String s1, String s2, String s3) { ... }
String concatenate(String s1, String s2, String s3, String s4){ ... }
// 가변인자
String concatenate(String... s) { ... }
2. 호출 시, 인자의 개수가 가변적이다.
concatenate();
concatenate("a");
concatenate(new String[]{"A", "B"};
3. 가변인자는 내부적으로 배열을 사용한다.
String concatenate1(String... s1) { ... } // 가변인자
String concatenate2(String[] s2) { ... } // 배열
public void static main(String[] args){
concatenate1(); // 인자 생략 가능
concatenate2(); // 인자 생략 불가
}
4. 가변인자가 있을 땐, 오버로딩을 안하는 게 좋다.
int add(int a, int... b){ ... };
int add(int... b) { ... };
분명 오버로딩과 가변인자의 사용조건에 부합하지만, 컴파일 에러가 난다.
그건 컴퓨터가 오버로딩된 메서드들을 구분할 수 없기 때문이다.
1를 인자로 넘긴다 할 때, 그게 a
의 값인지, 가변인자 b
인지 구별할 수 없다.
왜냐면 가변인자는 인자의 생략도 가능하기 때문이다.
생성자는 인스턴스 생성 시, 정해둔 작업을 수행하는 메서드이다.
주로 인스턴스 변수의 초기화를 위해 사용된다.
클래스이름(타입 변수명, 타입 변수명, ...) {
// 인스턴스 생성시 수행될 코드
}
다음 조건을 충족하면 클래스의 생성자가 된다.
클래스 내에 선언된다.
리턴값은 없다.
생성자의 이름은 클래스의 이름과 같아야 한다.
생성자의 오버로딩도 가능하다.
class Card {
// 기본 생성자
Card() {}
// 매개변수가 있는 생성자
Card(String k, int num){
...
}
}
생성자 수행 과정
public void static main(String[] args){
Card c = new Card();
}
Card()
호출모든 클래스에는 반드시 하나 이상의 생성자가 정의되어야 한다.
기본으로 컴파일러가 '기본 생성자'를 추가해주기 때문에, 따로 정의할 필요는 없다.
단, 컴파일러의 생성자 추가는 '생성자가 하나도 정의되지 않은 클래스'에만 적용된다.
변수의 초기화작업이 없는 경우에는 기본 생성자만 써도 괜찮다.
접근제어자가 public
이면 기본 생성자로 public 클래스명(){}
이 추가된다.
class Card {
// 기본 생성자
Card(){ }
}
사용자 생성자
사용자가 생성자를 정의한 경우, 컴파일러는 기본생성자를 추가하지 않는다.
class Deck1{
int card = 20;
}
class Deck2{
int card;
// 사용자 생성자
Deck2(int card){
this.card = card;
}
}
/*-------------------------------------*/
public void static main(String[] args){
Deck1 d1 = new Deck1();
Deck2 d2 = new Deck2(); // 컴파일 에러
}
Deck1은 기본생성자를 컴파일러가 추가해준다.
반면 Deck2는 사용자 생성자가 있기 때문에 기본생성자가 존재하지 않는다.
main() 실행 시, Deck2에는 일치하는 생성자가 없기 때문에 컴파일에러가 발생한다.
해결하려면, 기본생성자를 정의하든지, 사용자 생성자에 맞춰서 매개변수를 전달해야 한다.
생성자도 매개변수를 선언해서 호출 시 값을 넘겨받을 수 있다.
class Deck2{
int card;
Deck2(int card){
this.card = card;
}
}
/*-------------------------------------*/
public void static main(String[] args){
Deck2 d2 = new Deck2(20);
System.out.println(d2.card); // 20
}
this(), this
this
는 클래스 내에서 중복되는 생성자나 변수명을 구별하기 위해 사용한다.
this()
: 생성자 내에서 다른 생성자를 호출하는 조건
- 생성자 이름으로 클래스명 대신
this
를 사용한다.- 한 생성자에서 다른 생성자를 호출할 때는, 첫 줄에서만 가능하다.
위 두 조건을 만족해야지 에러 없이 생성자 간 호출이 가능하다.
모든 생성자를 this()
로 호출하기에, 매개변수를 기반으로 생성자를 구별한다.
초기화 작업 중 다른 생성자를 호출하면 기존 초기화 작업이 무의미해질 수 있기 때문에 첫째 줄에서만 호출이 가능하다.
class Car{
String color;
String gearType;
int door;
// 생성자1
Car(){
this("white", "auto", 4); // 생성자3 호출
}
// 생성자2
Car(String color){
this(color, "auto", 4); // 생성자3 호출
}
// 생성자3
Car(String color, String gearType, int door) {
this.color= color;
this.gearType = gearType;
this.door = door;
}
}
this
를 통해, 동명의 인스턴스변수와 매개변수를 구분한다.this
this
는 참조변수로 인스턴스 자신의 주소를 가리킨다. 이때, this()와는 다른 개념이다.
this
로 접근할 수 있는 건 '인스턴스 멤버'만 가능하다.
클래스 멤버는 생성 시점이 달라 인스턴스가 없을 수도 있기 때문이다.
this
가 지역변수로 숨겨진 채 존재한다.this
는 인스턴스 메서드 안에서만 사용이 가능하다.* Stack-frame : 메서드 내의 매개변수와 지역변수, 메서드의 반환 주소 등을 저장하는 공간
사용 중인 인스턴스와 같은 상태의 인스턴스를 더 만들 때, 생성자를 이용할 수 있다.
두 인스턴스의 주소는 다르다. 전혀 다른 인스턴스지만, 갖고 있는 상태(변수)가 일치한다.
방법은 두 가지이다. 1. 생성자를 이용해서 / 2. clone()
사용.
Car(Car c){
color = c.color;
gearType = c.gearType;
door = c.door;
}
// 막상하면, this 참조변수 쓰는 거랑 별반 다르지 않다.
결과적으로 인스턴스를 생성할 때는 2가지를 고려해야 한다.
변수를 선언하고 처음 값을 저장하는 걸 '초기화'라고 한다.
class InitTest {
int x; // 인스턴스 변수, 0으로 초기화
float y; // 인스턴스 변수, 0.0f로 초기화
void method1(){
int i; // 지역변수
int j = i; // 에러, i는 초기화하지 않아서 사용 불가
}
}
따라서 멤버변수, 배열(참조변수 null)의 초기화는 선택 / 지역 변수는 초기화 필수
지역변수는 그 자리에서 초기화를 해야되지만, 멤버변수는 방식은 '명시적 초기화, 생성자, 초기화 블록'으로 여러 가지이다.
변수 선언과 초기화를 동시에 하는 것.
class Car{
int door = 4; // 기본형 변수 초기화
Engine e = new Engine(); // 참조형 변수 초기화
//...
}
초기화 블럭은 두 가지 종류가 있고, 각 종류의 변수의 초기화에 사용된다.
class InitBlock{
static { /* 클래스 초기화 블럭 */}
{ /* 인스턴스 초기화 블럭 */}
}
클래스 초기화 블럭은 클래스가 메모리에 첫 로딩될 때 한번만 수행된다.
반대로 인스턴스는 생성자처럼 인스턴스 생성마다 수행된다.
이때, 인스턴스 초기화 블럭이 생성자보다 먼저 실행된다.
// 실행 순서를 응용해서 초기화 과정을 더 편하게 만들 수 있다.
Car(){
count++; // 중복
serialNo = count; // 중복
color ="White";
gearType ="Auto";
}
Car(String color, String gearType){
count++; // 중복
serialNo =count; // 중복
this.color = color;
this.gearType = gearType;
}
/* ----------- 코드 개선 ----------- */
// 인스턴스 블럭 → 중복 코드를 하나로 처리
{
count++;
serialNo = count;
}
Car(){
color ="White";
gearType ="Auto";
}
Car(String color, String gearType){
this.color = color;
this.gearType = gearType;
}
초기화의 수행 시기와 순서를 파악하는 것은 매우 중요하다.
초기화 시점
초기화 순서
클래스 변수의 초기화는 클래스 데이터가 method area에 올라올 때, 진행된다.
한 번 클래스 데이터가 로딩되었다면, 재로딩되지 않는다.
클래스 데이터 로딩은 JVM의 종류마다 다르게 설계되어 있다.
우선 순위에 따라서 초기화 값이 변화한다.
인스턴스 변수는 '생성자'가 가장 마지막에 실행된다.
공통된 부분은 '초기화 블럭'으로 처리해주는 것이 효율적이고, 객체지향적이다.
중복을 제거하고, 한 곳에서 코드를 관리하기 때문에.
도움이 되셨다면 '좋아요' 부탁드립니다 :)