코드 스테이츠 10일차
생성자
생성자
객체를 생성하는 역할을 하는 클래스의 구성요소, 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드
생성자와 메서드의 차이
- 생성자의 이름은 반드시 클래스의 이름과 같아야 합니다. 만약 클래스 이름과 생성자의 이름이 다르다면 그 메서드는 더 이상 생성자로서의 기능을 수행할 수 없습니다.
- 생성자는 리턴 타입이 없습니다. 하지만 메서드에서 리턴 값이 없을 때 표시하는 void 키워드를 사용하지 않습니다. 그 이유는 무언가를 ‘리턴하지 않는다’를 의미하는 void와는 다르게 생성자는 아예 리턴 타입 자체가 존재하지 않기 때문입니다.
두 가지 특징을 아래 예시를 통해 살펴봅시다.
클래스명(매개변수) { // 생성자 기본 구조
...생략...
}
먼저 클래스명과 같은 이름의 생성자명을 작성해 주고 리턴 타입이 없기 때문에 리턴 타입에는 아무것도 적지 않습니다. 매개변수는 있을 수도 있고 없을 수도 있습니다.
한 가지 기억해야 하는 사실은 생성자도 앞서 학습했던 오버로딩이 가능하므로 한 클래스 내에 여러 개의 생성자가 존재할 수 있다는 점입니다.
public class ConstructorExample {
public static void main(String[] args) {
Constructor constructor1 = new Constructor();
Constructor constructor2 = new Constructor("Hello World");
Constructor constructor3 = new Constructor(5,10);
}
}
class Constructor {
Constructor() { // (1) 생성자 오버로딩
System.out.println("1번 생성자");
}
Constructor(String str) { // (2)
System.out.println("2번 생성자");
}
Constructor(int a, int b) { // (3)
System.out.println("3번 생성자");
}
}
위의 예시에서 확인할 수 있듯이, 오버로딩을 활용하여 같은 이름을 가진 생성자 여러 개를 만들 수 있습니다.
여기서, 생성자의 모양에 따라서 객체를 생성하는 방법이 결정됩니다. 예를 들면, (2) 번 생성자를 호출하기 위해서는 객체 생성 시에 문자열을 전달해주어야 하고, (3) 번 생성자를 위해서는 두 개의 int형 매개변수를 전달해주어야 합니다.
기본 생성자(Default Constructor)
지금까지 우리는 생성자의 존재를 모르고 그저 new 키워드를 사용하여 생성자를 호출하여 객체를 만들었지만, 사실 모든 클래스에는 반드시 하나 이상의 생성자가 존재해야 합니다.
사실 지금까지 생성자를 따로 만들지 않아도 정상적으로 인스턴스를 만들 수 있었던 이유는 만약 생성자가 클래스 안에 포함되어 있지 않은 경우에는 자바 컴파일러가 기본 생성자를 자동으로 추가해 줬기 때문입니다.
이 기본 생성자는 앞선 챕터에서도 우리가 봤듯이 매개변수가 없는 생성자를 의미합니다.
클래스명(){} //기본 생성자
DefaultConst(){} // 예시) DefaultConst 클래스의 기본 생성자
위의 예시에서 보실 수 있는 것처럼 컴파일러가 자동으로 추가해 주는 기본 생성자에는 매개변수도 없고 바디에 아무런 내용이 없습니다. 그렇다면 만약에 생성자가 이미 추가되어 있는 경우는 어떻게 될까요?
이 경우에는 기본생성자가 아니라 이미 추가되어 있는 생성자를 기본으로 사용하게 됩니다.
매개변수가 있는 생성자
매개변수가 있는 생성자는 메서드처럼 매개변수를 통해 호출 시에 해당 값을 받아 인스턴스를 초기화하는 데 사용됩니다.
고유한 특성을 가진 인스턴스를 계속 만들어야 하는 경우 인스턴스마다 각기 다른 값을 가지고 초기화할 수 있어서 매우 유용합니다.
public class ConstructorExample {
public static void main(String[] args) {
Car c = new Car("Model X", "빨간색", 250);
System.out.println("제 차는 " + c.getModelName() + "이고, 컬러는 " + c.getColor() + "입니다.");
}
}
class Car {
private String modelName;
private String color;
private int maxSpeed;
public Car(String modelName, String color, int maxSpeed) {
this.modelName = modelName;
this.color = color;
this.maxSpeed = maxSpeed;
}
public String getModelName() {
return modelName;
}
public String getColor() {
return color;
}
}
//Output
제 차는 Model X이고, 컬러는 빨간색입니다.
위의 예시를 보시면 Car 인스턴스를 생성 시 매개변수가 있는 생성자를 사용하게 되면 인스턴스를 만든 후에 인스턴스의 필드값을 일일이 설정해 줄 필요 없이 생성과 동시에 원하는 값으로 설정해 줄 수 있어서 굉장히 편리합니다.
생성자의 모양에 따라서 객체를 생성하는 방법도 달라지게 됩니다. 앞의 기본 생성자의 경우에는 매개변수가 없었기 때문에 원래 우리가 객체를 생성하던 방식으로 new 키워드와 생성자를 호출하면 되었지만, 매개변수가 있는 경우에는 그 개수와 타입에 알맞게 생성자를 호출해주어야 합니다.
this vs this()
한마디로 정리하면 this는 자신의 객체, this() 메서드는 자신의 생성자 호출을 의미한다.
this()
this() 메서드는 자신이 속한 클래스에서 다른 생성자를 호출하는 경우에 사용합니다.
this() 메서드를 사용하기 위해서는 크게 두 가지의 문법요소를 충족시켜야 합니다.
- 첫째, this() 메서드는 반드시 생성자의 내부에서만 사용할 수 있습니다.
- 둘째, this() 메서드는 반드시 생성자의 첫 줄에 위치해야 합니다.
public class Test {
public static void main(String[] args) {
Example example = new Example();
Example example2 = new Example(5);
}
}
class Example {
public Example() {
System.out.println("Example의 기본 생성자 호출!");
};
public Example(int x) {
this();
System.out.println("Example의 두 번째 생성자 호출!");
}
}
//Output
Example의 기본 생성자 호출!
Example의 기본 생성자 호출!
Example의 두 번째 생성자 호출!
Example 클래스는 두 개의 생성자를 가지고 있습니다. 하나는 매개변수가 필요하지 않은 기본 생성자이고, 다른 하나는 int 타입의 매개변수를 받고 있는 생성자입니다.
그리고 두 번째 생성자 내부의 첫 번째 줄에 this() 메서드가 포함되어 있습니다.
이제 Example 클래스를 기반으로 만들어지는 인스턴스를 생성하면, 첫 번째 생성자가 호출되고 그 결과로 Example의 기본 생성자 호출!이라는 문구가 출력됩니다.
this 키워드
this 키워드는 this() 메서드와 매우 유사한 생김새를 가지고 있지만 쓰임새는 전혀 다릅니다.
public class ConstructorExample {
public static void main(String[] args) {
Car car = new Car("Model X", "빨간색", 250);
System.out.println("제 차는 " + car.getModelName() + "이고, 컬러는 " + car.getColor() + "입니다.");
}
}
class Car {
private String modelName;
private String color;
private int maxSpeed;
public Car(String modelName, String color, int maxSpeed) {
this.modelName = modelName;
this.color = color;
this.maxSpeed = maxSpeed;
}
public String getModelName() {
return modelName;
}
public String getColor() {
return color;
}
}
//Output
제 차는 Model X이고, 컬러는 빨간색입니다.
인스턴스 변수로 modelName, color, 그리고 maxSpeed가 선언되어 있는데, 동시에 생성자의 매개변수로 modelName, color, maxSpeed가 정의되어 있습니다.
이런 경우, 인스턴스 변수와 매개변수를 이름만으로는 구분하기가 어려워지는 문제가 발생하게 되는데, 이를 구분해 주기 위한 용도로 주로 사용되는 방법이 바로 this 키워드라 할 수 있습니다.
만약에 위의 코드에서, this.modelName = modelName대신 modelName = modelName라고 작성하면 둘 다 지역변수로 간주되게 됩니다.
결론적으로 this는 인스턴스 자신을 가리키며, 우리가 참조변수를 통해 인스턴스의 멤버에 접근할 수 있는 것처럼 this를 통해서 인스턴스 자신의 변수에 접근할 수 있는 것입니다.
내부 클래스
내부 클래스(Inner Class)
클래스 내에 선언된 클래스로, 외부 클래스와 내부 클래스가 서로 연관되어 있을 때 사용합니다.
내부클래스를 사용하면 외부 클래스의 멤버들에 쉽게 접근할 수 있고, 코드의 복잡성을 줄일 수 있습니다.
또한 외부적으로 불필요한 데이터를 감출 수 있어 뒤에서 학습하게 될 객체지향의 중요한 핵심 원칙인 캡슐화(encapsulation)를 달성하는 데 유용합니다.
class Outer { // 외부 클래스
class Inner {
// 인스턴스 내부 클래스
}
static class StaticInner {
// 정적 내부 클래스
}
void run() {
class LocalInner {
// 지역 내부 클래스
}
}
}
세 가지의 내부 클래스의 종류는 각각 인스턴스 내부 클래스, 정적 내부 클래스, 그리고 지역 내부 클래스로 구분할 수 있습니다. 기본적으로 내부 클래스는 외부 클래스 내에 선언된다는 점을 제외하면 일반 클래스와 차이점이 없습니다. 단지 외부 클래스와 내부 클래스가 서로 연관되어 있을 때 사용의 편의성을 고려하여 만들어진 문법 요소입니다.
세 가지 내부 클래스는 변수가 선언 위치에 따라 인스턴스 변수, 클래스 변수, 그리고 지역 변수로 구분되는 것과 유사하게 그 위치를 중심으로 구분될 수 있고, 그 유효범위(scope)와 특성이 변수의 그것과 매우 유사하다고 할 수 있습니다.
종 류 | 선언 위치 | 사용 가능한 변수 |
인스턴스 내부 클래스 (instance inner class) |
외부 클래스의 멤버변수 선언위치에 선언(멤버 내부 클래스) | 외부 인스턴스 변수, 외부 전역 변수 |
정적 내부 클래스 (static inner class) |
외부 클래스의 멤버변수 선언위치에 선언(멤버 내부 클래스) | 외부 전역 변수 |
지역 내부 클래스 (local inner class) |
외부 클래스의 메서드나 초기화블록 안에 선언 | 외부 인스턴스 변수, 외부 전역 변수 |
익명 내부 클래스 (anonymous inner class) |
클래스의 선언과 객체의 생성을 동시에 하는 일회용 익명 클래스 | 외부 인스턴스 변수, 외부 전역 변수 |
멤버 내부 클래스
인스턴스 내부 클래스와 정적 내부클래스를 하나로 묶어 멤버 내부 클래스라 통칭합니다.
인스턴스 내부 클래스
인스턴스 내부 클래스는 객체 내부에 멤버의 형태로 존재하며, 외부 클래스의 모든 접근 지정자의 멤버에 접근할 수 있습니다.
class Outer { //외부 클래스
private int num = 1; //외부 클래스 인스턴스 변수
private static int sNum = 2; // 외부 클래스 정적 변수
private InClass inClass; // 내부 클래스 자료형 변수 선언
public Outer() {
inClass = new InClass(); //외부 클래스 생성자
}
class InClass { //인스턴스 내부 클래스
int inNum = 10; //내부 클래스의 인스턴스 변수
void Test() {
System.out.println("Outer num = " + num + "(외부 클래스의 인스턴스 변수)");
System.out.println("Outer sNum = " + sNum + "(외부 클래스의 정적 변수)");
}
}
public void testClass() {
inClass.Test();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
System.out.println("외부 클래스 사용하여 내부 클래스 기능 호출");
outer.testClass(); // 내부 클래스 기능 호출
}
}
// 출력값
외부 클래스 사용하여 내부 클래스 기능 호출
Outer num = 1(외부 클래스의 인스턴스 변수)
Outer sNum = 2(외부 클래스의 정적 변수)
정적 내부 클래스
정적 내부 클래스는 인스턴스 내부 클래스와 동일하게 클래스의 멤버 변수 위치에 정의하지만, static 키워드를 사용한다는 점에서 차이가 있다고 할 수 있습니다.
class Outer { // 외부 클래스
private int num = 3; // 외부 클래스의 인스턴스 변수
private static int sNum = 4;
void getPrint() {
System.out.println("인스턴스 메서드");
}
static void getPrintStatic() {
System.out.println("스태틱 메서드");
}
static class StaticInClass { // 정적 내부 클래스
void test() {
System.out.println("Outer sNum = " +sNum + "(외부 클래스의 정적 변수)");
getPrintStatic();
// num 과 getPrint() 는 정적 멤버가 아니라 사용 불가.
}
}
}
public class Main {
public static void main(String[] args) {
Outer.StaticInClass a = new Outer.StaticInClass(); //정적 이너 클래스의 객체 생성
a.test();
}
}
//출력값
Outer sNum = 4(외부 클래스의 정적 변수)
스태틱 메서드
지역 내부 클래스
지역 내부 클래스는 클래스의 멤버가 아닌 메서드 내에서 정의되는 클래스입니다.
class Outer { //외부 클래스
int num = 5;
void test() {
int num2 = 6;
class LocalInClass { //지역 내부 클래스
void getPrint() {
System.out.println(num);
System.out.println(num2);
}
}
LocalInClass localInClass = new LocalInClass();
localInClass.getPrint();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}
//출력값
5
6
코드 예제를 보면 지역 내부 클래스 LocalInClass가 메서드 안에서 선언되고 생성된 후에 정의된 메서드를 호출하여 외부 클래스의 변수들을 출력하고 있는 것을 확인할 수 있습니다.
'코드 스테이츠' 카테고리의 다른 글
코드 스테이츠 4/26 - 객체지향 프로그래밍 심화 2 (0) | 2023.04.26 |
---|---|
코드 스테이츠 04/25 - 객체 지향 프로그래밍 심화1 (1) | 2023.04.25 |
코드 스테이츠 4/21 - 객체지향 프로그래밍 기초 1 (0) | 2023.04.21 |
코드 스테이츠 4/20 - Java 기초 3 (0) | 2023.04.20 |
코드 스테이츠 4/19 - Java 기초 3 (0) | 2023.04.19 |