생성자는 인스턴스 생성 시 호출되는 인스턴스 변수들을 초기화해주는 메서드이다.
Car car = new Car();
new 키워드를 사용해 car라는 인스턴스를 생성하고, 뒤에 Car()라는 생성자를 호출하여 이 인스턴스를 생성할 때 인스턴스의 변수들을 초기화할 수 있도록 해준다.
생성자와 메서드는 두 가지의 차이가 있다.
생성자의 이름은 반드시 클래스의 이름과 같아야 하고 리턴 타입이 없다.
하지만 생성자도 오버로딩이 가능하기 때문에 한 클래스 내에 여러 개의 생성자가 존재할 수 있다.
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번 생성자");
}
}
위의 예시코드처럼 클래스와 동일한 이름으로 생성자를 만들었고 한 클래스내에서 오버로딩을 하고 있는 것을 알 수 있다.
클래스에는 반드시 하나 이상의 생성자가 존재해야 한다. 이전 코드에서 생성자를 만들지 않았는데도 객체를 만들 수 있었던 것은 클래스 안에 생성자가 없으면 자바 컴파일러가 기본 생성자를 자동으로 추가해주기 때문이다.
이와 다르게 매개변수가 있는 생성자는 생성자 호출 시에 매개변수를 받아 해당값으로 인스턴스를 초기화한다.
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이고, 컬러는 빨간색입니다.
위의 예시처럼 생성자를 사용하게되면 인스턴스를 만든 후에 인스턴스 변수를 일일이 설정해줄 필요없이 생성과 동시에 원하는 값으로 설정해줄 수 있다. 매개변수가 있는 경우에는 그 개수와 타입에 알맞게 생성자를 호출해주면 된다.
this()를 통해 같은 클래스 내의 생성자간 호출이 가능하다. this() 메서드는 자신이 속한 클래스에서 다른 생성자를 호출하는 경우에 사용한다. 예를 들면 만약 클래스명이 Car라는 Car 클래스의 생성자를 호출하는 것은 Car()가 아니라 this()이고, 그 효과는 Car() 생성자를 호출하는 것과 동일하다.
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의 두 번째 생성자 호출!
위의 코드를 보면 new Example()을 통해 Example 클래스의 기본생성자를 호출하고 있다. Example 클래스의 기본생성자를 호출해 기본 생성자 호출!을 출력하고 다시 new Example(5)를 통해 Example 클래스의 매개변수 있는 생성자가 호출된다. Exampe(int x)에서 첫 번째 줄에 있는 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이고, 컬러는 빨간색입니다.
모든 메서드에는 자신이 포함된 클래스의 객체를 가리키는 this라는 참조변수가 있는데, 일반적인 경우에는 컴파일러가 this.를 추가해주기 때문에 생략한다.
하지만 인스턴스 변수와 생성자의 매개변수 이름이 같은 경우 이름을 구분하기 위해 this키워드를 사용한다.
Car의 인스턴스 변수와 생성자의 매개변수가 modelName, color, 그리고 maxSpeed로 선언되어 있는데, 이 경우에는 서로 같은 이름의 변수를 구분할 필요성이 생긴다. 이 변수들을 구분하기 위해 this를 사용한다.
this 키워드는 주로 인스턴스의 필드명과 지역변수를 구분하기 위한 용도로 사용된다.
결론적으로 this는 인스턴스 자신을 가리키며, 우리가 참조변수를 통해 인스턴스의 멤버에 접근할 수 있는 것처럼 this를 통해서 인스턴스 자신의 변수에 접근할 수 있다.
클래스 내에 선언된 클래스로, 외부 클래스와 내부 클래스가 서로 연관되어 있을 때 사용한다. 내부 클래스를 사용하면 외부 클래스의 멤버들에 쉽게 접근 할 수 있고, 코드의 복잡성을 줄일 수 있다.
또한 외부적으로 불필요한 데이터를 감출 수 있어 뒤에서 학습하게 될 객체지향의 중요한 핵심 원칙인 캡슐화(encapsulation)를 달성하는 데 유용하다.
class Outer { // 외부 클래스
class Inner {
// 인스턴스 내부 클래스
}
static class StaticInner {
// 정적 내부 클래스
}
void run() {
class LocalInner {
// 지역 내부 클래스
}
}
}
위의 코드 예제는 외부 클래스와 그 안에 포함될 수 있는 세 가지의 내부 클래스의 종류가 있다.
세 가지의 내부 클래스의 종류는 각각 인스턴스 내부 클래스, 정적 내부 클래스, 그리고 지역 내부 클래스이다.
내부클래스는 선언 위치말고는 일반클래스와 차이점이 없다. 외부 클래스와 내부 클래스가 서로 연관되어 있을 때 사용의 편의성을 고려하여 만들어진 문법 요소이다.
인스턴스 내부 클래스는 객체 내부에 멤버의 형태로 존재하며, 외부 클래스의 모든 접근 지정자의 멤버에 접근할 수 있습니다.
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 num = " +sNum + "(외부 클래스의 정적 변수)");
getPrintStatic();
// num 과 getPrint() 는 정적 멤버가 아니라 사용 불가.
}
}
}
public class Main {
public static void main(String[] args) {
Outer.StaticInClass a = new Outer.StaticInClass(); //정적 이너 클래스의 객체 생성
a.test();
}
}
//출력값
Outer num = 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가 메서드 안에서 선언되고 생성된 후에 정의된 메서드를 호출하여 외부 클래스의 변수들을 출력하고 있는 것을 확인할 수 있다.