생정자는 클래스의 구성 요소로서 객체를 생성한다. 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드라고 할 수 있다.
new
키워드를 사용해 객체를 생성할 때 호출되는 것이 바로 생성자다. 생성자가 인스턴스를 생성한다는 오해를 할 수 있는데, 인스턴스를 생성하는 것은 new
이고, 생성자는 인스턴스 변수들을 초기화하는데 사용하는 특수한 메서드다.
생성자는 메서드와 비슷한 구조를 갖고 있지만 두 부분에서 차이가 있다.
void
는 '리턴하지 않는다'는 의미를 갖는데 생성자는 리턴 타입 자체가 존재하지 않아 void
를 사용하지 않는다.클래스명 (매개변수) {
...
}
public class Ex_constructor {
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() { //생성자 오버로딩
System.out.println("생성자1");
}
Constructor(String str) {
System.out.println("생성자2")
}
Constructor(int a, int b) { //생성자 오버로딩
System.out.println("생성자3");
}
}
클래스명과 같은 이름의 생성자명을 적고 리턴 타입은 없으므로 적지 않았다. 매개변수는 있을 수도, 없을 수도 있다.
생성자도 오버로딩이 가능해 한 클래스 내에 여러 생성자가 존재할 수 있다.
지금까지는 new
를 사용해 생성자를 호출하고 객체를 만들었다. 모든 클래스에는 반드시 하나 이상의 생성자가 존재해야 하는데, 생성자를 만들지 않아도 정상적으로 인스턴스를 만들 수 있었던 이유는 무엇일까?
바로, 생성자가 클래스 안에 생성되어 있지 않을 때 자바 컴파일러가 기본 생성자를 자동으로 추가해줬기 때문이다.
클래스명 () { } //기본 생성자
DefaultConst() { } //DefaultConst 클래스의 기본 생성자
위 코드는 컴파일러가 자동으로 추가한 기본 생성자에는 매개변수도 없고 바디에도 아무 내용이 없다.
생성자가 이미 추가되어 있는 경우에는 기본 생성자가 아닌 이미 추가된 생성자를 기본으로 사용하게 된다.
매개변수가 있는 생성자는 메서드와 같이 매개변수를 통해 호출 시 해당 값을 받아 인스턴스를 초기화한다.
고유 특성을 가진 인스턴스를 계속해서 만들어야 하는 경우, 인스턴스마다 각기 다른 값을 갖고 초기화할 수 있어 유용하다.
public class Ex_constructor {
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;
}
}
Car 인스턴스 생성 시 매개변수가 있는 생성자를 사용하여, 생성과 동시에 원하는 값으로 설정을 해주었다.
인스턴스를 만든 후 인스턴스의 필드값을 일일이 설명할 필요가 없어 편리하다.
지금까지는 new
로 생성자를 호출했지만, 매개변수가 경우에는 그 개수와 타입에 맞게 생성자를 호출해줘야 한다.
같은 클래스 내에 메서드들끼리 서로 호출할 수 있는 것처럼 생성자도 상호 호출이 가능하다. 이를 위해 사용하는 것이 this()
메서드다.
this()
메서드는 자신이 속한 클래스에서 다른 생성자를 호출할 때 사용한다. Car class의 생성자를 호출할 때는 Car()
가 아닌 this()
를 사용하는 것이다.
this()
메서드를 사용하기 위해서는 아래 두 가지의 조건을 충족해야 한다.
public class Ex_this1 {
public static void main(String[] args) {
Ex ex1 = new Ex();
Ex ex2 = new Ex(5);
}
}
class Ex {
public Ex() {
System.out.println("Ex의 기본 생성자 호출");
};
public Ex(int x) {
this();
System.out.println("Ex의 두 번째 생성자 호출");
}
}
Ex class는 두 개의 생성자를 가지고 있다. 하나는 매개변수가 필요하지 않은 기본 생성자, 다른 하나는 int타입의 매개변수를 받고 있는 생성자다.
두 번째 생성자 내부에 this()
메서드가 포함되어 있다.
Ex class를 기반으로 만들어지는 인스턴스를 생성하면, 첫 생성자가 호출되고 Ex의 기본 생성자 호출
문구가 출력된다.
두 번째 생성자를 사용해 객체를 만드는 과정에서 생성자가 호출되면 this()
메서드가 출력되어 다시 첫 번째 생성자가 호출되고 Ex의 두 번째 생성자 호출
문구가 출력된다.
this
를 이해하기 위해서 매개변수가 있는 생성자에서 사용한 예제를 다시 사용하겠다.
public class Ex_constructor {
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;
}
}
Car class를 살펴보면, 인스턴스 변수로 modelName
, color
, maxSpeed
가 선언되어 있고 동시에 생성자의 매개변수로 정의되어 있다.
이런 경우 인스턴스 변수와 매개변수를 이름만으로 구분하기 어려운데, 이를 구분하기 위해 사용되는 것이 this
키워드다.
만약 위 코드에서 this.modelName = modelName
대신 modelName = modelName
이라 작성하면 둘 다 지역변수라 간주된다.
모든 메서드에는 자신이 포함된 클래스의 객체를 가리키는 this
참조변수가 있다. 일반적으로는 컴파일러가 this.
를 추가해주기 때문에 생략하는 경우가 많다.
현재 Car class의 modelName이라는 인스턴스 필드를 클래스 내부에 출력하고자 한다면, 원래는 System.out.println(this.modelName)
이라 작성해야 한다.
결론적으로 this
는 인스턴스 자신을 가리킨다. 참조변수를 통해 인스턴스 멤버에 접근할 수 있듯이 this
를 통해 인스턴스 변수에 접근할 수 있는 것이다.
내부 클래스란 클래스 내에 선언된 클래스다. 외부 클래스와 내부 클래스가 서로 연관돼 있을 때 사용한다.
내부 클래스를 사용하면 외부 클래스의 멤버에 쉽게 접근할 수 있고 코드가 간결해진다.
class Outer { //외부 클래스
class Inner { //인스턴스 내부 클래스(instance inner class)
}
static class StaticInner { //정적 내부 클래스(static inner class)
}
void run() {
class LocalInner { //지역 내부 클래스(local inner class)
}
}
}
종류 | 선언 위치 | 사용 가능한 변수 |
---|---|---|
instance inner class | 외부 클래스의 멤버변수 선언 위치(맴버 내부 클래스) | 외부 인스턴스 변수, 외부 전역 변수 |
static inner class | 외부 클래스의 멤버변수 선언 위치(맴버 내부 클래스) | 외부 전역 변수 |
local inner classs | 외부 클래스의 메서드나 초기화 블럭 내에 선언 | 외부 인스턴스 변수, 외부 전역 변수 |
anonymous inner class | 클래스의 선언과 객체의 생성을 동시에 하는 일회용 익명 클래스 | 외부 인스턴스 변수, 외부 전역 변수 |
인스턴스 내부 클래스와 정적 내부 클래스를 하나로 묶어 멤버 내부 클래스라 부른다.
public 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(); //내부 클래스 기능 호출
}
}
위 코드를 보고 전체적인 흐름을 이해해보자.
인스턴스 내부 클래스는 외부 클래스의 내부에 위치해있고, 내부에서 외부 클래스의 인스턴스 변수와 정적 변수에 각각 접근해 해당 값을 사용한다.
유의할 점은 인스턴스 내부 클래스는 반드시 외부 클래스를 생성한 후 사용해야 한다.
클래스의 생성과 상관없이 사용할 수 있는 정적 변수와 정적 메서드는 인스턴스 내부 클래스에서 선언할 수 없다.
내부 클래스는 기본적으로 외부 클래스의 존재에 의존하고 있다는 것을 알았다.
만일 내부 클래스가 외부 클래스의 존재와 무관하게 정적 변수를 사용할 수 있게 하려면 어떻게 해야할까?
이 경우, 사용할 수 있는 것이 정적 내부 클래스다. 정적 내부 클래스는 인스턴스 내부 클래스와 동일하게 클래스의 멤버 변수 위치에 정의하지만 static
키워드를 사용한다는 점에서 차이가 있다.
public class Outer2 { //외부 클래스
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 num = " + sNum + "(외부 클래스의 정적 변수)");
getPrintStatic();
//num 과 getPrint() 는 정적 멤버가 아니라 사용 불가
}
}
}
public class Main {
public static void main(String[] args) {
Outer.StaticInClass a = new Outer.StaticInClass(); //정적 이너 클래스의 객체 생성
a.test();
}
}
지역 내부 클래스는 클래스의 멤버가 아닌 메서드 내에 정의된다.
지역 변수와 유사하게 메서드 내에서만 사용이 가능해서 일반적으로 메서드 안에서 선언하고 바로 객체를 생성해 사용한다.
public class Outer3 { //외부 클래스
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();
}
}
지역 내부 클래스인 LocalInClass
가 메서드 안에서 선언되고 생성된 후 정의된 메서드를 호출해 외부 클래스의 변수를 출력하고 있는 것을 확인할 수 있다.