on
객체지향 프로그래밍 I 2편
객체지향 프로그래밍 I
이 글은 남궁성님의 자바의 정석 3/e을 기반으로 공부한 내용을 정리한 글입니다.
변수와 메서드
3.11 클래스 메서드(static메서드)와 인스턴스 메서드
메서드 앞에 static
이 붙어 있으면 클래스 메서드이고 없으면 인스턴스 메서드이다. 클래스 메서드는 클래스변수처럼, 객체를 생성하지 않고도 ‘클래스이름.메서드이름(매개변수)’와 같은 식으로 호출이 가능하다. 반면에 인스턴스 메서드는 반드시 객체를 생성해야만 호출할 수 있다.
클래스는 ‘데이터(변수)와 데이터에 관련된 메서드의 집합’이므로, 같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있다. 인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다. 인스턴스 변수는 인스턴스(객체)를 생성해야만 만들어지므로 인스턴스 메서드 역시 인스턴스를 생성해야만 호출할 수 있는 것이다.
메서드 중에서 인스턴스와 관계없는(인스턴스 변수나 인스턴스 메서드를 사용하지 않는) 메서드를 클래스 메서드(static메서드)로 정의한다. 인스턴스 변수를 사용하지 않는다고 해서 반드시 클래스 메서드로 정의해야하는 것은 아니지만 특별한 이유가 없다면 클래스 메서드로 정의한다.
| 참고 | 클래스 영역에 선언된 변수를 멤버변수라 한다. 멤버변수 중에 static이 붙은 것은 클래스변수(static변수), static이 붙지 않은 것은 인스턴스변수이다. 멤버변수는 둘 다 통칭하는 말이다.
1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
- 생성된 각 인스턴스는 서로 독립적이기 때문에 각 인스턴스의 변수(iv)는 서로 다른 값을 유지한다. 그러나 모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스변수로 정의해야 한다.
2. 클래스 변수(static변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.
- static이 붙은 변수(클래스변수)는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다.
3. 클래스 메서드(static 메서드)는 인스턴스 변수를 사용할 수 없다.
- 인스턴스변수는 인스턴스가 반드시 존재해야만 사용할 수 있는데, 클래스메서드(static이 붙은 메서드)는 인스턴스 생성 없이 호출이 가능하므로 클래스 메서드가 호출되었을 때 인스턴스가 존재하지 않을 수도 있다. 그래서 클래스 메서드에서 인스턴스변수의 사용을 금지한다. 반면 인스턴스변수나 인스턴스 메서드에서는 static이 붙은 멤버들을 사용하는 것이 항상 가능하다. 인스턴스 변수가 존재한다는 것은 static변수가 메모리에 이미 존재한다는 것을 의미하기 때문이다.
4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.
- 메서드의 작업내용 중에서 인스턴스변수를 필요로 한다면, static을 붙일 수 없다. 반대로 인스턴스 변수를 필요로 하지 않는다면 static을 붙이는 것이 좋다. 메서드 호출시간이 짧아져 성능이 향상된다.
| 참고 | random()과 같은 Math클래스의 메서드는 모두 클래스 메서드이다. Math클래스에는 인스턴스변수가 하나도 없고 작업을 수행하는데 필요한 값들을 모두 매개변수로 받아서 처리하기 때문이다.
3.12 클래스 멤버와 인스턴스 멤버간의 참조와 호흡
같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다. 단, 클래스멤버가 인스턴스 멤버를 참조 또는 호출하는 경우에는 인스턴스를 생성해야 한다.
인스턴스 멤버간의 호출은 아무런 문제가 없다. 하나의 인스턴스멤버가 존재한다는 것은 인스턴스가 이미 생성되어있다는 것을 의미하며, 다른 인스턴스멤버들도 존재하기 때문이다.
오버로딩
4.1 오버로딩(overloading)이란?
메서드도 변수와 마찬가지로 같은 클래스 내에서 서로 구별될 수 있어야 하기 때문에 각기 다른 이름을 가져야 한다. 그러나 자바에서는 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도 매개변수의 개수 또는 타입이 다르면, 같은 이름을 사용해서 메서드를 정의할 수 있다. 이처럼, 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것을 ‘메서드 오버로딩(method overloading)’ 또는 간단히 오버로딩(overloading)
이라 한다.
4.2 오버로딩의 조건
같은 이름의 메서드를 정의한다고 해서 무조건 오버로딩인 것은 아니다. 오버로딩이 성립하기 위한 조건은 다음과 같다.
- 메서드 이름이 같아야 한다.
- 매개변수의 개수 또는 타입이 달라야 한다.
메서드의 이름이 같다 하더라도 매개변수가 다르면 서로 구별될 수 있기 때문에 오버로딩이 가능한 것이다. 위 조건을 만족시키지 못하면 중복 정의로 간주되어 컴파일 시 에러가 발생한다. 오버로딩된 메서드들은 매개변수에 의해서만 구별될 수 있으므로 반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.
4.3 오버로딩의 예
대표적인 예는 println
메서드이다. println
메서드를 호출할 때 매개변수로 지정하는 값의 타입에 따라서 호출되는 println
메서드가 달라진다.
PrintStream
클래스에는 어떤 종류의 매개변수를 지정해도 출력할 수 있도록 10개의 오버로딩된 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
메서드를 호출할 때 매개변수로 넘겨주는 값의 타입에 따라서 위의 오버로딩된 메서드 중의 하나가 선택되어 실행되는 것이다.
4.4 오버로딩의 장점
만일 메서드도 변수처럼 이름만으로 구별된다면, 같은 기능을 하지만 서로 다른 이름을 가져야 하기 때문에 이름을 짓기도 어렵고, 구분해서 기억해야하기 때문에 서로 부담이 된다. 오버로딩을 통해 하나의 이름으로 통일해서 정의한다면 외우기도 쉽고 이름을 짓는 것도 훨씬 간편해진다.
4.5 가변인자(varargs)와 오버로딩
JDK1.5부터 동적으로 메서드의 매개변수 개수를 동적으로 지정해 줄 수 있게 되었으며, 이 기능을 가변인자(variable arguments)
라고 한다.
가변인자는 타입... 변수명
과 같은 형식으로 선언하며 PrintStream
클래스의 printf()
가 대표적인 예이다.
public PrintStream printf(String format, Object... args) { ... }
가변인자 외에 매개변수가 더 있다면, 가변인자를 매개변수 중에서 제일 마지막에 선언해야 한다. 가변인자는 내부적으로 배열을 이용하는 것이다. 그래서 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성된다. 따라서 꼭 필요한 경우에만 사용하는 것이 좋다.
매개변수의 타입을 배열로 하면, 반드시 인자를 지정해 줘야하기 때문에 인자를 생략할 수 없다. 그래서 null
이나 길이가 0
인 배열을 인자로 지정해줘야 한다.
가변인자를 선언한 메서드를 오버로딩하면, 메서드를 호출했을 때 구별되지 못하는 오류가 발생하기 쉽다. 따라서 가능하면 가변인자를 사용한 메서드는 오버로딩하지 않는 것이 좋다.
생성자
5.1 생성자(Constructor)란?
생성자는 인스턴스가 생성될 때 호출되는 ‘인스턴스 초기화 메서드’이다. 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해서도 사용된다.
| 참고 | 인스턴스 초기화란, 인스턴스 변수들을 초기화하는 것을 뜻한다.
생성자 역시 메서드처럼 클래스 내에 선언되며, 구조도 메서드와 유사하지만 리턴값이 없다는 점이 다르다. 리턴값이 없지만 키워드 void
를 사용하지 않고 생성자 앞에는 아무 것도 적지 않는다.
- 생성자의 이름은 클래스의 이름과 같아야 한다.
- 생성자는 리턴 값이 없다.
| 참고 | 생성자도 메서드이기 때문에 리턴값이 없다는 의미의 void를 붙여야 하지만, 모든 생성자가 리턴값이 없으므로 void를 생략할 수 있게 한 것이다.
생성자도 오버로딩이 가능하므로 하나의 클래스에 여러 개의 생성자가 존재할 수 있다.
클래스이름(타입 변수명, 타입 변수명, ...) {
// 인스턴스 생성 시 수행될 코드,
// 주로 인스턴스 변수의 초기화 코드를 적는다
}
연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다. 지금까지 인스턴스를 생성하기위해 사용했던 클래스이름()
이 바로 생성자이다. 인스턴스를 생성할 때는 반드시 클래스 내에 정의된 생성자 중의 하나를 선택하여 지정해주어야 한다.
5.2 기본 생성자(default constructor)
모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다. 컴파일 할 때, 소스파일(*.java)의 클래스에 생성자가 하나도 정의되지 않은 경우 컴파일러는 자동적으로 ‘기본 생성자(default constructor)’를 추가하여 컴파일 한다.
컴파일러가 추가해주는 기본 생성자는 매개변수도 없고 아무런 내용도 없는 아주 간단한 것이다. 특별히 인스턴스 초기화 작업이 요구되지 않는다면 생성자를 정의하지 않고 컴파일러가 제공하는 기본 생성자를 사용하는 것도 좋다.
| 참고 | 클래스의 ‘접근 제어자(Access Modifier)’가 public인 경우에는 기본 생성자로 ‘public 클래스이름() { }’이 추가된다.
class Data1 {
int value;
}
class Data2 {
int value;
Data2(int x) {
value = x;
}
}
class Constructor {
public static void main(String[] args) {
Data1 d1 = new Data1();
Data2 d2 = new Data2();
}
}
위 코드를 컴파일하면 cannot resolve symbol
이라는 에러메시지가 나타난다. Data2
에서 Data2()
라는 생성자를 찾을 수 없고 나타나는데 이는 Data2
에 생성자 Data2()
가 정의되어 있지 않기 때문에 에러가 발생한 것이다. Data1
에는 정의되어 있는 생성자가 하나도 없으므로 컴파일러가 기본 생성자를 추가해주었지만, Data2
에는 이미 생성자 Data2(int x)
가 정의되어 있으므로 기본 생성자가 추가되지 않았기 때문이다.
컴파일러가 자동적으로 기본 생성자를 추가해주는 경우는 ‘클래스 내에 생성자가 하나도 없을 때’라는 것을 기억해야한다.
5.3 매개변수가 있는 생성자
생성자도 메서드처럼 매개변수를 선언하여 호출 시 값을 넘겨받아서 인스턴스의 초기화 작업에 사용할 수 있다. 인스턴스마다 각기 다른 값으로 초기화되어야하는 경우가 많기 때문에 매개변수를 사용한 초기화는 매우 유용하다.
class Car {
String color;
String gearType;
int door;
Car() { }
Car(String c, String g, int d) {
color = c;
gearType = g;
door = d;
}
}
생성자 Car()
를 사용한다면 인스턴스를 생성한 다음 인스턴스변수들을 따로 초기화해주어야 하지만, 매개변수가 있는 생성자 Car(String color, String gearType, int door)
를 사용한다면 생성과 동시에 원하는 값으로 초기화 할 수 있게 된다.
이처럼 클래스를 작성할 때 다양한 생성자를 제공해서 인스턴스 생성 후에 별도로 초기화를 하지 않아도 되도록 하는 것이 좋다.
5.4 생성자에서 다른 생성자 호출하기 - this(), this
같은 클래스의 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능하다. 단, 다음 조건을 만족시켜야 한다.
- 생성자의 이름으로 클래스이름 대신 this를 사용한다.
- 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.
같은 클래스 내의 생성자들은 일반적으로 서로 관계가 깊은 경우가 많아서 서로 호출하도록 하여 유기적으로 연결해주면 더 좋은 코드를 얻을 수 있다.
Car(String c, String g, int d) {
color = c;
gearType = g;
door = d;
}
color = c;
는 생성자의 매개변수로 선언된 지역변수 c
의 값을 인스턴스변수 color에 저장한다. 이 때 변수 color
와 c
는 이름만으로도 서로 구별되므로 아무 문제가 없다.
Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
생성자의 매개변수로 선언된 변수의 이름이 color
로 인스턴스변수 color
와 같을 경우 이름만으로는 두 변수가 서로 구별이 안 된다. 이런 경우에 인스턴스변수 앞에 this
를 사용하면 된다. this.color
는 인스턴스변수이고, color
는 생성자의 매개변수로 정의된 지역변수로 서로 구별이 가능하다.
this
는 참조변수로 인스턴스 자신을 가리킨다. 참조변수를 통해 인스턴스의 멤버에 접근할 수 있는 것처럼, this
로 인스턴스변수에 접근할 수 있는 것이다.
this 인스턴스 자신을 가리키는 참조변수. 인스턴스의 주소가 저장되어 있다. 모든 인스턴스메서드에 지역변수로 숨겨진 채로 존재한다.
this(), this(매개변수) 생성자. 같은 클래스의 다른 생성자를 호출할 때 사용한다.
| 참고 | this와 this()는 비슷하게 생겼지만 완전히 다른 것이다. this는 ‘참조변수’이고, this()는 ‘생성자’이다.
5.5 생성자를 이용한 인스턴스의 복사
현재 사용하고 있는 인스턴스와 같은 상태를 갖는 인스턴스를 하나 더 만들고자 할 때 생성자를 이용할 수 있다. 두 인스턴스가 같은 상태를 가진다는 것은 두 인스턴스의 모든 인스턴스변수(상태)가 동일한 값을 갖고 있다는 것을 뜻한다.
하나의 클래스로부터 생성된 모든 인스턴스의 메서드와 클래스변수는 서로 동일하기 때문에 인스턴스간의 차이는, 인스턴스마다 각기 다른 값을 가질 수 있는 인스턴스변수 뿐이다.
Car(Car c) {
color = c.color;
gearType = c.gearType;
door = c.door;
}
Car
클래스의 참조변수를 매개변수로 선언한 생성자이다. 매개변수로 넘겨진 참조변수가 가리키는 Car
인스턴스의 인스턴스변수인 color, gearType, door의 값을 인스턴스 자신으로 복사하는 것이다.
변수의 초기화
6.1 변수의 초기화
변수를 선언하고 처음으로 값을 저장하는 것을 ‘변수의 초기화’라고 한다. 초기화는 경우에 따라서 필수적이거나 선택적이지만, 선언과 동시에 적절한 값으로 초기화하는 것이 좋다. 멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지지만, 지역변수는 사용하기 전에 반드시 초기화해야 한다.
class Init {
int x;
int y = x;
void method1() {
int i;
int j = i;
}
}
x
, y
는 인스턴스 변수, i
, j
는 지역변수이다. x
, i
는 선언만 하고 초기화하지 않았다. 그리고 y
를 초기화 하는데 x
를 사용하고, j
를 초기화 하는데 i
를 사용하였다.
인스턴스 변수는 int형 기본값인 0으로 자동 초기화가 되므로 int y = x;
는 가능하지만, 지역변수는 자동 초기화되지 않으므로 컴파일하면 에러가 발생한다. 멤버변수(클래스변수와 인스턴스변수)와 배열의 초기화는 선택이지만, 지역변수의 초기화는 필수이다.
멤버변수의 초기화는 지역변수와 달리 여러가지 방법이 있다.
▶멤버변수의 초기화 방법 1. 명시적 초기화(explicit initialization) 2. 생성자(constructor) 3. 초기화 블럭(initialization block) \- 인스턴스 초기화 블럭 : 인스턴스변수를 초기화 하는데 사용 \- 클래스 초기화 블럭 : 클래스변수를 초기화 하는데 사용 |
6.2 명시적 초기화(explicit initialization)
변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다. 가장 기본적이며 간단한 초기화 방법이므로 최우선적으로 고려되어야 한다. 간단하고 명료하지만, 보다 복잡한 초기화 작업이 필요할 때는 ‘초기화 블럭(initialization block)’ 또는 생성자를 사용해야 한다.
6.3 초기화 블럭(initialization block)
초기화 블럭은 ‘클래스 초기화 블럭’과 ‘인스턴스 초기화 블럭’ 두 가지 종류가 있다. 클래스 초기화 블럭은 클래스변수의 초기화에 사용되고, 인스턴스 초기화 블럭은 인스턴스 변수 초기화에 사용된다.
클래스 초기화 블럭 클래스변수의 복잡한 초기화에 사용된다.
인스턴스 초기화 블럭 인스턴스변수의 복잡한 초기화에 사용된다.
초기화 블럭을 작성하려면, 인스턴스 초기화 블럭은 단순히 클래스 내에 블럭 { }을 만들고 그 안에 코드를 작성하면 된다. 클래스 초기화 블럭은 인스턴스 초기화 블럭 앞에 static
을 덧붙이기만 하면 된다.
초기화 블럭 내에는 조건문, 반복문, 예외처리구문 등을 자유롭게 사용할 수 있으므로, 초기화 작업이 복잡하여 명시적 초기화만으로 부족한 경우 사용한다.
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;
}
배열이나 예외처리가 필요한 초기화에서는 명시적 초기화만으로는 복잡한 초기화 작업을 할 수 없다. 이런 경우에 클래스 초기화 블럭을 사용한다.
| 참고 | 인스턴습변수의 복잡한 초기화는 생성자 또는 인스턴스 초기화 블럭을 사용한다.
6.4 멤버변수의 초기화 시기와 순서
초기화가 수행되는 시기와 순서는 다음과 같다.
클래스변수의 초기화시점 클래스가 처음 로딩될 때 단 한번 초기화 된다.
인스턴스변수의 초기화시점 인스턴스가 생성될 때마다 각 인스턴스별로 초기화가 이루어진다.
클래스변수의 초기화순서 기본값 → 명시적초기화 → 클래스 초기화 블럭
인스턴스변수의 초기화순서 기본값 → 명시적초기화 → 인스턴스 초기화 블럭 → 생성자
프로그램 실행도중 클래스에 대한 정보가 요구될 때, 클래스는 메모리에 로딩된다. 클래스 멤버를 사용했을 때, 인스턴스를 생성할 때 등이 해당된다. 하지만 해당 클래스가 이미 메모리에 로딩되어 있다면, 다시 로딩하지 않는다. 초기화도 다시 수행되지 않는다.
| 참고 | 클래스의 로딩 시기는 JVM의 종류에 따라 다를 수 있는데, 클래스가 필요할 때 바로 메모리에 로딩하도록 설계 되어있거나, 실행효율을 높이기 위해서 사용될 클래스들을 프로그램이 시작될 때 미리 로딩하도록 되어있는 것도 있다.
Comments
JAVA 의 다른 글
-
서블릿 컨테이너(Servlet Container)란? 05 May 2021
-
지네릭스, 열거형, 애너테이션 27 Oct 2020
-
컬렉션 프레임웍 2편 16 Oct 2020
-
컬렉션 프레임웍 1편 14 Oct 2020
-
날짜와 시간 & 형식화 12 Oct 2020
-
java.lang패키지와 유용한 클래스 29 Sep 2020
-
예외처리 24 Sep 2020
-
객체지향 프로그래밍 II 2편 21 Sep 2020
-
객체지향 프로그래밍 II 1편 19 Sep 2020
-
객체지향 프로그래밍 I 2편 18 Sep 2020
-
객체지향 프로그래밍 I 1편 16 Sep 2020
-
배열 array 14 Sep 2020
-
조건문과 반복문 if, switch, for, while statement 13 Sep 2020
-
연산자 Operator 2편 13 Sep 2020
-
연산자 Operator 1편 11 Sep 2020
-
변수 Variable 2편 09 Sep 2020
-
변수 Variable 1편 08 Sep 2020
-
자바를 시작하기 전에 07 Sep 2020
-
래퍼 클래스(Wrapper Class)란 무엇인가 30 Aug 2020
-
java.lang.Math 29 Aug 2020
-
진수 변환, 형 변환 29 Aug 2020
-
Arrays.sort() 29 Aug 2020
-
Enhanced for loop 20 Aug 2020
-
toCharArray() 20 Aug 2020