[자바의 정석] 6장 객체지향 언어 1. 4~6 변수의 초기화
아래 내용은 자바의 정석 강의&책을 보면서 정리한 내용입니다.
들어가기 전에
자바의 정석 6장 내용 리스트
이번 시간에는 아래 내용 중 오버로딩~변수의 초기화 부분을 정리하겠습니다.
4. 오버로딩
4.1 오버로딩이란?
⭐ 오버로딩? 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것
4.2 오버로딩 조건
⭐ 1. 메서드 이름이 같다.
2. 매개변수의 개수 또는 타입이 다르다.
주의! 반환 타입은 오버로딩 구현 시 아무런 영향을 주지 못함
4.3 오버로딩의 예
가장 대표적인 것은 println 메서드이다. 아래와 같이 매개변수 타입에 따라서 오버로딩 되어 있다.
1) 리턴 타입만 다른 경우 - 오버로딩이 아님
아래 add() 메서드에서 리턴값만 다른 경우 아래와 같은 에러가 발생한다. 이는 오버로딩이 아니다!!
class Math{
int add(int a, int b) {return a + b;}
long add(int a, int b){return long(a + b);}
}
2) 매개변수 타입 순서만 다른 경우 - 오버로딩
아래는 두 메서드 모두 int형과 long형 매개변수가 하나씩 선언되어 있지만 서로 순서가 다른 경우이다. 이 경우 구분하여 호출이 가능하므로 오버로딩으로 간주한다. 하지만 이렇게 선언하는 것은 순서를 외워야 하기 때문에 단점이 된다.
아래 m.add(1, 2)와 같은 호출은 불가능하다. 왜냐하면 어떤 메서드를 호출해야할지 알 수가 없다. 따라서 java: reference to add is ambiguous라는 컴파일 에러가 발생한다.
class Math{
long add(int a, long b) {return a + b;}
long add(long a, int b){return a + b;}
}
public class Test {
public static void main(String[] args) {
Math m = new Math();
System.out.println(m.add(1, 2L));
System.out.println(m.add(1, 2)); // 에러 발생:reference to add is ambiguous
}
}
4.4 오버로딩의 장점
⭐ 모두 근본적으로 같은 기능을 하는 메서드지만 서로 다른 이름을 가져야하면 이름 짓기도 어렵고 메서드를 사용하는 쪽에서 이름을 일일이 구분해서 기억해야 한다. 하지만 오버로딩을 하면 한 개만 기억하면 되고 메서드 명만 보고도 예측이 쉽다.
- 오버로딩을 사용 안하는 경우, 아래와 같이 정의해야 한다.
4.5 가변인자와 오버로딩
매개변수 개수가 고정이 아닌 동적으로 지정할 수 있다. 가변 인자 선언을 진행하면 가능하다.
- 가변인자 선언 방식: 타입… 변수명
- 주의: 가변인자는 마지막에 선언
1) 가변인자 vs 배열 차이점
- 배열을 매개변수로 하는 경우, 아래와 같이 빈 값으로 넣을 수 없고 반드시 null을 넣어줘야 한다.
2) 가변인자 오버로딩 시, 주의점
아래 예시를 보면 concatenate 메서드를 오버로딩했다. delim이 없는 경우를 오버로딩하였지만, 실제 컴퓨터는 이 두 메서드를 구분할 수 없기 때문에 에러가 발생한다. 따라서 가변인자 메서드는 오버로딩하지 않는 것을 추천한다.
public class Test {
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);
}
public static void main(String[] args) {
String[] strArr = {"100", "200", "300", "400"};
System.out.println(concatenate("", "100", "200", "300", "400")); // 100200300400
System.out.println(concatenate("-", strArr)); // 100-200-300-400-
System.out.println(concatenate(",", new String[]{"100", "200", "300", "400"})); // 100,200,300,400,
System.out.println("[" + concatenate(",", new String[0]) + "]"); // []
System.out.println("[" + concatenate(",") + "]"); // []
}
}
5. 생성자
5.1 생성자란?
⭐ 생성자란? 인스턴스가 생성될 때 호출되는 인스턴스(iv) 초기화 메서드이다. 따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해서 사용된다. ⇒ 주의! 생성자는 인스턴스를 생성하는 것은 아님!
생성자 조건
- 생성자의 이름은 클래스의 이름과 같다.
- 생성자는 리턴값이 없다. ⇒ void 생략
특징
- 오버로딩이 가능하여 하나의 클래스에 여러 개의 생성자 존재
인스턴스 생성 코드 순서
Card c = new Card();
- 연산자 new에 의하여 메모리(heap)에 Card 클래스의 인스턴스가 생성된다.
- 생성자 Card()가 호출되어 수행된다.
- 연산자 new의 결과로 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장된다.
5.2 기본 생성자
⭐ 기본 생성자란? 컴파일러가 자동으로 생성해 주는 매개변수가 없고 아무런 내용 없는 생성자
주의: 기본 생성자는 생성자가 하나도 없을 때 컴파일러가 자동으로 생성되는 것!!
아래 예제를 통해 주의: 기본 생성자는 생성자가 하나도 없을 때 자동으로 생성되는 것!! 을 알 수 있다.
Data2 클래스는 매개변수가 있는 생성자를 정의하였다. 그러면 컴파일러는 자동으로 기본 생성자를 추가하지 않는다.
class Data1{
int val;
}
class Data2{
int val;
// 매개변수가 존재하는 생성자 정의
Data2(int x){
val = x;
}
}
public class Test {
public static void main(String[] args) {
Data1 d1 = new Data1();
Data2 d2 = new Data2(); // 컴파일 에러!!
}
}
5.3 매개변수가 있는 생성자 - 초기화
생성자도 메서드와 같이 매개변수를 선언하여 호출 시 인스턴스의 초기화 작업을 사용할 수 있다.
class Car{
String color;
String gearType;
int door;
Car(){}
Car(String c, String g, int d){
color = c;
gearType = g;
door = d;
}
}
public class Test {
public static void main(String[] args) {
// 인스턴스 초기화 방법 1. 직접
Car c1 = new Car();
c1.color = "white";
c1.gearType = "auto";
c1.door = 4;
// 인스턴스 초기화 방법 2. 생성자 사용
Car c2 = new Car("white", "auto", 4);
}
}
5.4 생성자에서 다른 생성자 호출하기 - this(), this
⭐ this: 인스턴스 자신을 가리키는 참조변수. 인스턴스 주소가 저장되어 있다.
모든 인스턴스메서드에 지역변수로 숨겨진 채 존재.
this(), this(매개변수): 생성자. 같은 클래스의 다른 생성자 호출 시 사용
- 생성자의 이름으로 클래스 이름 대신 this를 사용한다.
- 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.
주의: this는 static 메서드(클래스 메서드)에서 사용할 수 없다. this는 인스턴스를 의미하기 때문!: this는 static 메서드(클래스 메서드)에서 사용할 수 없다. this는 인스턴스를 의미하기 때문!
- 첫 줄에서만 호출이 가능한 이유?
- 다른 생성자 호출하기 이전 초기화 작업이 무의미해질 수 있다. → 7장에서 자세히 배움!
- 결과적으로 아래 예시는 에러가 발생한다.
class Car{
String color;
String gearType;
int door;
Car(){
this("white", "auto", 4);
}
Car(String color){
door = 5; // this가 첫번째줄에 존재하지 않음!
Car(color, "auto", 4); // this로 생성자를 호출해야한다.
}
Car(String c, String g, int d){
color = c;
gearType = g;
door = d;
}
}
- 수정된 올바른 예시
class Car{
String color;
String gearType;
int door;
Car(){
this("white", "auto", 4); // 아무것도 지정 안하는 경우, default값 설정
}
Car(String color){
this(color, "auto", 4);
}
Car(String c, String g, int d){
color = c;
gearType = g;
door = d;
}
}
public class Test {
public static void main(String[] args) {
// 인스턴스 초기화 방법 1. 직접
Car c1 = new Car();
c1.color = "white";
c1.gearType = "auto";
c1.door = 4;
// 인스턴스 초기화 방법 2. 생성자 사용
Car c2 = new Car("white", "auto", 4);
}
}
- this.인스턴스 사용
- 아래와 같이 this.인스턴스로 적어야 인스턴스와 매개변수 구분이 가능하다.
class Car{
String color;
String gearType;
int door;
Car(){
this("white", "auto", 4);
}
Car(String color){
this(color, "auto", 4);
}
Car(String color, String gearType, int door){
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
5.5 생성자를 이용한 인스턴스의 복사
- 아래 Car(Car c)를 통해 인스턴스를 복사하여 사용한 예시이다. c1.door = 100;로 값을 변경했지만 c2.door는 변경되지 않음을 알 수 있다.
- this()를 호출하여 인스턴스 복사를 하였다.
class Car{
String color;
String gearType;
int door;
Car(){
this("white", "auto", 4);
}
Car(Car c){
this(c.color, c.gearType, c.door); //this를 활용하여 복사
}
Car(String color, String gearType, int door){
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
public class Test {
public static void main(String[] args) {
// 인스턴스 초기화 방법 1. 직접
Car c1 = new Car();
Car c2 = new Car(c1);
System.out.println("c1: " + c1.color + " " + c1.gearType + " " + c1.door); // c1: white auto 4
System.out.println("c2: " + c2.color + " " + c2.gearType + " " + c2.door); // c2: white auto 4
c1.door = 100;
System.out.println("c1: " + c1.color + " " + c1.gearType + " " + c1.door); // c1: white auto 100
System.out.println("c2: " + c2.color + " " + c2.gearType + " " + c2.door); // c2: white auto 4
}
}
6. 변수의 초기화
6.1 변수의 초기화
⭐ 멤버변수(클래스변수와 인스턴스변수)와 배열의 초기화: 선택적 ⇒ 기본값으로 자동 초기화됨 (대부분 0으로 초기화)
* 지역변수의 초기화: 필수
멤버변수(cv, iv) 초기화 방법
- 자동 초기화: 0으로 기본 초기화
- 간단 초기화(=명시적 초기화) : =
- 복잡 초기화
- 생성자
- 초기화 블럭
- 인스턴스 초기화 블럭: 인스턴스변수를 초기화 하는데 사용
- 클래스 초기화 블럭: 클래스변수를 초기화 하는데 사용
6.2 멤버변수 초기화 방식 1. 명시적 초기화
변수를 선언과 동시에 초기화 하는 것
class Car {
int door = 4; // 기본형 초기화
Engine e = new Engine(); // 참조형 초기화. null이 아닌 객체 주소를 넣기
}
6.3 멤버변수 초기화 방식 2. 초기화 블럭
⭐ - 클래스 초기화 블럭: 클래스변수의 복잡한 초기화에 사용된다.
- 인스턴스 초기화 블럭: 인스턴스변수의 복잡한 초기화에 사용된다. 거의 사용 안 함!!
클래스 초기화 인스턴스 초기화 (거의 사용 안 함)→ 생성자 사용
방법 | static { /* 클래스 초기화블럭 */} | {/* 인스턴스 초기화블럭 */} |
수행 시기 | 클래스가 메모리에 처음 로딩될 때 한 번 | - 인스턴스 생성할 때 마다 수행 |
인스턴스 초기화 사용 목적 예시
Car 생성자 수행 시 count ++; serialNo = count; 해당 부분은 중복이 된다.
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;
}
{
count ++;
serialNo = count;
}
클래스 초기화, 인스턴스 초기화 사용 예시
아래예시는 초기화 블럭 호출 시기를 알 수 있다.
class BlockTest {
static {
System.out.println("static {}"); // 클래스 초기화. 1번 호출. BlockTest가 메모리에 로딩 시 가장 먼저 수행
}
{
System.out.println("{ }"); // 인스턴스 초기화. 3번 호출. 객체 생성 시 인스턴스 초기화 블록 수행
}
public BlockTest(){
System.out.println("생성자"); // 4번 호출. 인스턴스 초기화 -> 생성자 호출
}
public static void main(String[] args) {
System.out.println("BlockTest bt = new BlockTest();"); // 2번 호출
BlockTest bt = new BlockTest();
System.out.println("BlockTest bt2 = new BlockTest();");
BlockTest bt2 = new BlockTest();
}
}
- 결과
static {}
BlockTest bt = new BlockTest();
{ }
생성자
BlockTest bt2 = new BlockTest();
{ }
생성자
6.4 멤버변수의 초기화 시기와 순서
클래스 변수 | 인스턴스 변수 | |
초기화 시점 | 클래스가 처음 로딩될 때 한번 | 인스턴스가 생성될 때마다 각 인스턴스별로 초기화 |
초기화 순서 | 기본값 → 명시적초기화 → 클래스 초기화 블럭 | 기본값 → 명시적초기화 → 인스턴스 초기화 블럭 → 생성자 |
초기화 순서 예시
class InitTest {
// 명시적 초기화 (간단 초기화)
static int cv = 1;
int iv = 1;
static {cv = 2;} // 클래스 초기화 블럭
{iv = 2;} // 인스턴스 초기화 블럭
InitTest() { // 생성자
iv = 3;
}
}
위 예시의 순서는 아래와 같이 이뤄진다.
- cv 초기화 → iv 초기화 진행
- 자동(0으로 초기화) → 간단(=) → 복잡 초기화(static {}, 생성자)
사용 예시
아래 예시는 초기화 사용 예시다.
예를 들어 공장에서 제품 생산 시 생산일련번호(serialNo)를 제품마다 부여한다. 이때 {} 블럭(초기화 블럭)을 사용한다.
class Product {
static int count = 0; // 인스턴스 수 저장
int serialNo;
{
++count;
serialNo = count;
}
public Product(){}
}
public class Test {
public static void main(String[] args) {
Product p1 = new Product();
Product p2 = new Product();
Product p3 = new Product();
System.out.println("p1: " + p1.serialNo ); // p1: 1
System.out.println("p2: " + p2.serialNo ); // p2: 2
System.out.println("p3: " + p3.serialNo ); // p3: 3
System.out.println("count: " + Product.count ); // count: 3
}
}
다음 시간에는 자바의 정석 7장 객체지향 언어 2에 대해서 정리하겠습니다. 😀