[Java] 다시 정리하는 "클래스" - 생성자, 메소드

rara_kim·2022년 11월 30일
0

Java

목록 보기
34/39

생성자(Constructor)

생성자는 new 연산자로 클래스로부터 객체를 생성할 떄 호출되어 객체의 초기화를 담당한다.
new 연산자에 의해 생성자가 성공적으로 실행되면 힙(heap)영역에 객체가 생성되고 객체의 번지가 리턴된다.
그리고 리턴된 객체의 번지는 클래스 변수에 저장된다.

기본 생성자

모든 클래스는 생성자가 반드시 존재하며, 생성자를 하나 이상 가질 수 있다.
클래스 내부에 생성자 선언을 생략했다면 컴파일러가 기본 생성자를 바이트 코드에 자동으로 추가해준다.

public class Car {
	//생성자 선언
	public Car() {...}    
}


public class Main {
	public static void main(String[] args) {
    	Car myCar = new Car(); 
    }
}

따라서 클래스에 생성자를 선언하지 않아도 new 연산자 뒤에 기본 생성자를 호출해서 객체를 생성할 수 있다.

💡그러나 클래스에 명시적으로 선언한 생성자가 1개라도 있으면 컴파일러는 기본 생성자를 추가하지 않기 때문에, 필요한 경우 기본 생성자도 명시적으로 선언해주어야 한다.


생성자 선언

기본 생성자 대신 명시적으로 생성자를 선언하기 위해서는 아래와 같은 형태로 작성하면 된다.

클래스명 (매개변수1, 매개변수2, ....) {
//객체의 초기화 코드
}

생성자는 메소드와 비슷한 형태이지만, 리턴 타입이 없고 클래스 이름과 동일하다.
생성자 블록 내부에는 객체 초기화 코드가 작성되는데, 일반적으로 필드에 초기값을 저장하거나 메소드를 호출하여 객체 사용 전에 필요한 준비를 한다.

매개 변수는 생략할 수도 있고 여러개를 선언해도 된다.
매개 변수는 new 연산자로 생성자를 호출할 때 외부의 값을 생성자 블록 내부로 전달하는 역할을 한다.

public class Car {
	String model;
	String color;
	int maxSpeed;
    
	car(String m, String c, int max) {
    	model = m;
        color = c;
      	maxSpeed = max;
    }
}


//생성자 사용
public class Main{
	public static void main(String[] args) {
    	Car car1 = new Car();   //명시적 생성자 선언이 있었지만 기본 생성자가 없어 사용X
        
        Car car2 = new Car("그랜저", "검정", 300);
    }
}

필드 초기화

클래스로부터 객체가 생성될 때 필드는 기본 초기값으로 자동 설정된다.
다른 값으로 초기화 하고 싶다면,

  • 필드를 선언할 때 초기값을 주거나
  • 생성자에서 초기값을 주면 된다.

필드를 선언할 때 초기값을 주게 되면 동일한 클래스로부터 생성되는 객체들은 객체 생성 시점에 모두 같은 값을 갖게 된다.

public class Koean{
	String nation = "대한민국";
    String name;
    String ssn;
}

public class Main{
	public static void main(String[] args) {
    	Korean k1 = new Korean();
        Korean k2 = new Korean();
    }
}

위와 같이 Korean 클래스에 nation 필드는 선언과 동시에 초기값을 주었기 때문에 k1,k2 객체를 생성하면 두 객체 모두 nation 필드에는 "대한민국"이 저장되어 있다.

객체 생성 시점에 외부에서 제공되는 다양한 값들로 초기화되어야 한다면 매개값으로 이 값들을 받아 생성자에서 초기화 해야한다.

public class Korean {
	String nation = "대한민국";
    String name;
    String ssn;
    
    public Korean(String n, String s) {
    	name = n;
        ssn = s;
    }
}


//생성자 사용
public class Main{
	public static void main(String[] args) {
    	Korean k1 = new Korean("김자바", "012345-1234567");
        Korean k2 = new Korean("이자바", "123456-2345678");
    }
}

객체의 필드가 하나가 아닌 여러개 이고, 이 필드들을 모두 생성자에서 초기화한다면 생성자의 매개 변수 수는 객체의 필드 수만큼 선언되어야 한다.

생성자 오버로딩

외부에서 제공되는 다양한 데이터들을 이용해서 객체를 초기화하려면 생성자도 다양화될 필요가 있다.
그래서 자바는 다양한 방법으로 객체를 생성할 수 있도록 생성자 오버로딩(overloading)을 제공한다.
생성자 오버로딩이란 매개 변수를 달리하는 생성자를 여러 개 선언하는 것을 말한다.

public class Car {
	Car() {...}
    Car(String model) {...}
    Car(String model, String color) {...}
    Car(String model, String color, int maxSpeed) {...}
}

생성자 오버로딩 시 주의할 점은 매개 변수의 타입과 개수 그리고 선언된 순서가 똑같을 경우 매개 변수 이름만 바꾸는 것은 생성자 오버로딩이 아니라는 점이다.

Car(String model, String color) {...}
Car(String color, String model) {...}   //오버로딩X

다른 생성자 호출: this()

생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드가 발생할 수 있다.
이 경우에는 필드 초기화 내용은 한 생성자에만 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법으로 개선할 수 있다.
생성자에서 다른 생성자를 호출할 때는 this() 코드를 사용한다.

this()는 자신의 생성자를 호출하는 코드로 반드시 생성자의 첫 줄에서만 허용된다.
또한 this()의 매개값은 호출되는 생성자의 매개 변수에 맞게 제공해야 한다.

public class Car {
	String company = "현대 자동차";
    String model;
    String color;
    int maxSpeed;
    
    Car() {}
    Car(String model) {
    	this(model, "은색", 250);
    }
    
    Car(String model, String color) {
    	this(model, color, 250);
    }
    
    Car(String model, String color, int maxSpeed) {
    	this.model = model;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }
}



메소드(Method)

메소드는 객체의 동작에 해당하는 중괄호 {}블록을 말한다.
중괄호 블록 이름이 메소드 이름이며, 메소드를 호출하면 중괄호 블록에 있는 모든 코드들이 일괄적으로 실행된다.

메소드의 선언은 선언부와 실행 블록으로 구성되는데, 아래 요소들을 포함한다.

  • 리턴 타입: 메소드가 리턴하는 결과의 타입을 표시한다.
  • 메소드 이름: 메소드의 기능이 드러나도록 식별자 규칙에 맞게 이름을 지어준다.
  • 매개 변수 선언: 메소드를 실행할 때 필요한 데이터를 받기 위한 변수를 선언한다.
  • 메소드 실행 블록: 실행할 코드를 작성한다.

메소드 선언

리턴 타입

리턴값이란 메소드를 실행한 후의 결과값을 말한다.
메소드는 리턴값이 있을 수도, 없을 수도 있다. 리턴값이 있을 경우 리턴 타입이 선언부에 명시되어야 한다.

void powerOn() {
	System.out.println("Power On!");
}

double divide(int x, int y) {
	return x / y;
}

리턴값이 있느냐 없느냐에 따라 메소드를 호출하는 방법이 달라진다.
위의 두 메소드는 아래와 같이 호출할 수 있다.

powerOn();
double result = divide(10, 20);

powerOn() 메소드는 리턴값이 없어 변수에 저장할 내용이 없기 때문에 단순히 메소드만 호출하면 된다.
반면 divide() 메소드는 10을 20으로 나눈 후 0.5를 리턴하므로 이것을 저장할 변수가 있어야 한다.

그러나 리턴 타입이 있다고 해서 반드시 변수에 리턴값을 저장할 필요는 없다.
메소드 실행이 중요할 경우에는 변수를 선언하지 않고 메소드만을 호출할 수도 있다.

메소드 이름

메소드 이름은 자바 식별자 규칙에 맞게 작성하면 된다.

  • 숫자로 시작하면 안되고, $와 _를 제외한 특수 문자를 사용하면 안된다.
  • 관례적으로 메소드 이름은 소문자로 작성한다.
  • 서로 다른 단어가 혼합된 이름이라면 뒤이어 오는 단어의 첫 글자는 대문자로 작성한다.
void run() {...}
void startEngine() {...}
String getName() {...}
int[] getScores() {...}

매개 변수의 개수를 모를 경우

메소드의 매개 변수는 개수가 이미 정해져 있는 것이 일반적이지만, 어떤 상황에서는 메소드를 선언할 때 매개 변수의 개수를 알 수 없는 경우도 있다.
예를 들어 여러개의 수를 모두 합산하는 메소드를 선언할 때 몇 개의 매개 변수가 입력될지 알 수 없는 경우, 배열 타입이나 ...을 사용하여 매개변수를 선언 할 수 있다.

int sum1(int[] values) {...}
int sum2(int... values) {...}   

int[] values = {1, 2, 3};
int result1 = sum1(values);
int result2 = sum1(new int[]{1, 2, 3, 4, 5});

int result3 = sum2(1, 2, 3, 4, 5);

리턴(return)문

리턴값이 있는 메소드

메소드 선언에 리턴 타입이 있는 메소드는 반드시 return문을 사용해서 리턴값을 지정해야 한다.
만약 return문이 없다면 컴파일 에러가 발생하고, return문이 실행되면 메소드는 즉시 종료된다.

int plus (int x, int y) {
	return x + y;
}

리턴값이 없는 메소드: void

리턴값이 없는 메소드는 리턴 타입으로 void를 사용한다.
그런데 void로 선언된 메소드에서도 return문을 사용할 수 있는데, 이것은 리턴값을 지정하기 위한 것이 아니라 메소드 실행을 강제 종료하기 위함이다.

void run() {
	while (true) {
    	if (gas > 0) {
        	System.out.println("달립니다.(gas 잔량: " + gas + ")");
            gas -= 1;
        } else {
        	System.out.println("멈춥니다.(gas 잔량: " + gas + ")");
            return;    // 메소드 실행 종료
        }
    }
}

메소드 호출

메소드는 클래스 내/외부의 호출에 의해 실행된다.

1️⃣객체 내부에서 호출

클래스 내부에서 메소드를 호출할 경우에는 메소드 이름으로 호출할 수 있다.

public class ClassName() {
	int method1(int x, int y) {
    	return x + y;
    }
	
    void method2() {
    	int result2 = method1(10, 20);      //30
        double result2 = method1(10, 20);   //30.0
    }
}

2️⃣객체 외부에서 호출

외부 클래스에서 메소드를 호출하려면 클래스로부터 객체를 생성해야 한다.
객체가 생성되었다면 참조 변수와 함께 도트(.) 연산자를 사용해서 메소드를 호출할 수 있다.

public class Main {
	public static void main(String[] args) {
      	ClassName() name = new ClassName();
      	name.method1(1, 2);
      	name.method2(1, 2);
    }
}      

메소드 오버로딩

클래스 내에 같은 이름의 메소드를 여러 개 선언하는 것을 메소드 오버로딩(overloading)이라고 한다.
하나의 메소드명으로 여러 기능을 담는다고 하여 붙여진 이름이다.
메소드 오버로딩은 매개 변수의 타입, 개수, 순서 중 하나가 달라야 한다.

int plus(int x, int y) {
	return x + y;
}

double plus(double x, double y) {
	return x + y;
}

오버로딩된 메소드를 호출할 경우 JVM은 매개값의 타입을 보고 메소드를 선택한다.
그렇다면 아래의 코드는 어떻게 될까?

int x = 10;
double y = 20.3;
plus(x, y);

매개변수가 int, double 타입인 메소드가 없기 때문에 컴파일 에러가 날 것 같지만, plus(double x, double y) 메소드가 실행된다.
JVM은 일차적으로 매개 변수의 타입을 보지만, 매개 변수의 타입이 일치하지 않을 경우 자동 타입 변환이 가능한지 검사한다.
위의 코드에서 int는 double 타입으로 변환이 가능하므로 최종적으로 plus(double x, double y) 메소드가 선택된다.

💡메소드 오버로딩 시 주의점

매개 변수의 타입과 개수, 순서가 똑같을 경우 매개 변수 이름이 다르다고 해서 이것을 메소드 오버로딩이라고 하지 않는다.
또한 리턴 타입만 다르고 매개 변수가 동일하다면 이것도 오버로딩이 아니다.
아래와 같은 경우 오버로딩이 아니기 때문에 컴파일 에러가 발생한다.

int divide(int x, int y) {...}
double divide(int boonja, int boonmo) {...}

profile
느리더라도 꾸준하게

0개의 댓글