this는 객체가 자신을 가리킬 때 사용하는 키워드이다. 즉, this 키워드를 통해서 자신의 객체를 참조할 수 있다. 항상 문법을 학습할 때는 이 문법이 왜 생겼는지를 파악하면 이해가 빠르다. this는 같은 이름의 무언가를 구분하기 위해서
사용하는 키워드이다.
첫 번째 사용목적은 클래스 필드와 매개변수의 구분을 하기 위해서이다.
public class Person {
String name;
int age;
public Person(String name, int age) {
name = name;
age = age;
}
}
위와 같이 작성하면 name과 age는 항상 클래스의 필드로 인식한다. 이 값을 출력해보면 각각 null, 0이 나온다. 인텔리제이에서는 마우스를 갖다대면 Variable 'name' is assigned to itself
라는 메시지가 출력되는데, 이는 둘 다 클래스의 필드로 인식한다는 내용이다. 따라서 매개변수와 클래스의 필드를 구분해주기 위해서 this 키워드가 사용된다.
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
이렇게 this 키워드를 붙여주면 this.name은 클래스의 필드를 의미하고, name은 매개변수로 인식하여 생성자를 통해 클래스 필드에 매개변수로 전달받은 값을 대입할 수 있게 된다.
두 번째는 다른 오버로딩된 다른 생성자를 호출할 때 사용된다. 마찬가지로 오버로딩된 생성자의 이름이 모두 같으므로, this 키워드를 통해 호출하면 해당 시그니처에 맞는 생성자가 호출되는 것이다. 예를 들어, 아래와 같이 클래스 필드 3개 중에 일부만 전달 받는 생성자를 정의할 때, 이미 다른 생성자에 작성된 코드를 중복하여 작성할 필요가 없다.
public class Person {
String name;
int age;
String job;
public Person(String name) {
this(name, 20, "unemployed");
}
public Person(String name, int age) {
this(name, age, "unemployed");
}
public Person(String name, int age, String job) {
this.name = name;
this.age = age;
this.job = job;
}
}
여기에서 this를 사용하지 않고 Person(name, 20, "unemployed")
와 같은 방식으로 다른 생성자를 호출할 수는 없다.
상속관계에 있을 때 자식 클래스에서 부모 클래스의 메서드를 오버라이딩하게 되면, 같은 이름으로 호출했을 때 부모의 메서드는 가려지고 오버라이딩된 자식 클래스의 메서드만 호출된다. 그러나 자식 클래스 내부에서 오버라이딩된 부모 클래스의 메서드(오버라이딩되기 전)를 호출해야 하는 상황이 발생한다면 자식 객체와 부모 객체를 구분할 필요가 있다. 그래서 부모 객체를 가리키는 키워드 super가 사용된다. 즉, super를 통해서 부모 객체를 참조할 수 있다. super로 부모의 필드에 접근할 수도 있고, 부모 생성자를 호출할 수 있으며, 부모의 메서드 및 오버라이딩된 메서드까지도 호출할 수 있다.
super가 부모 객체를 가리키므로, super()를 이용하면 부모 생성자를 자식 객체에서 직접 호출할 수 있다. 부모 객체가 반드시 먼저 생성되고나서 자식 객체가 생성되므로 super를 통해서 부모 생성자를 호출할 수 있는 것이다. 문법적인 허용은 메모리에 로드되는 시점이 논리적으로 맞는가에 대해 생각해보면 이해가 쉽다.
public class People {
public String name;
public String ssn;
public People(String name, String ssn) {
this.name = name;
this.ssn = ssn;
}
}
public class Student extends People {
public int studentNo;
public Student(String name, String ssn, int studentNo) {
super(name, ssn);
this.studentNo = studentNo;
}
}
위의 코드에서 Student는 People을 상속받았고, name과 ssn필드는 가지고있지 않지만 그대로 상속받는다. 부모 클래스인 Student에서 매개변수 두개를 모두 받는 생성자만 정의되어 있으므로, 자식 클래스의 생성자에서 부모 클래스의 생성자를 호출해야할 의무를 갖는다. 그러므로 super(name, ssn)
이 없다면 적절한 생성자가 없다는 컴파일 에러가 발생한다.
public Student(String name, String ssn, int studentNo) {
this.studentNo = studentNo;
super(name, ssn);
}
또한, 부모 생성자 호출(super())을 자식 생성자 실행코드 이후에 위치시키면 컴파일 에러가 발생한다. 부모 클래스의 생성이 가장 먼저 되고나서 자식 클래스를 생성하도록 해야하기 때문이다. 따라서 자식 생성자에서 부모생성자를 명시적으로 호출할 때에는 가장 첫줄에 super()를 작성해야한다. 아무것도 작성하지 않았다면 내부적으로 부모의 기본 생성자를 자식 생성자의 가장 첫줄에서 호출한다.
예를 들어, 일반 비행기가 있고, 음속 비행가능한 전투기가 이를 상속한다고 한다면 아래와 같이 코드를 작성할 수 있다.
public class Airplane {
public void land() {
System.out.println("Landing...");
}
public void fly() {
System.out.println("flying...");
}
public void takeOff() {
System.out.println("taking off...");
}
}
public class FighterPlane extends Airplane {
public static final int NORMAL = 1;
public static final int SUPERSONIC = 2;
public int flyMode = NORMAL;
@Override
public void fly() {
if (flyMode == SUPERSONIC) {
System.out.println("supersonic flying...");
} else {
// Airplane.fly() 호출
super.fly();
}
}
}
이렇게 작성하면 flyMode의 값에 따라 자식 클래스의 fly()와 부모 클래스의 fly()를 모두 호출해볼 수 있게 되는 것이다.
public class PlaneTest {
public static void main(String[] args) {
FighterPlane fighterPlane = new FighterPlane();
fighterPlane.takeOff();
fighterPlane.fly();
fighterPlane.flyMode = fighterPlane.SUPERSONIC;
fighterPlane.fly();
fighterPlane.land();
}
}