코딩 공부/자바(Java)

this와 클래스의 관계(포함과 상속)

cagn 2024. 10. 29. 06:27
SMALL

한 프로젝트가 끝났다. 그래서 그 동안 블로그 글에 소홀헀었다. 그동안 공부했던 것들을 다시 올리면서 열심히 배워나가도록 하겠다.

 

클래스에는 생략된 요소가 많다고 한다. 그 중 하나로 디폴트 생성자이다. 이 생성자는 생성자를 아무 것도 선언하지 않을 때 자동으로 만들어지며, 생성자를 1개라도 만들면 사라지기 때문에 디폴트 생성자를 쓰려면 그 생성자를 오버로딩으로 만들어줘야 한다.

 

class Apple {

	public Apple () { // 디폴트 생성자 - 생성자를 안 만들면 자동 생성 (생략되어 있다.)
    	}
}

 

그렇기 때문에 만약 생성자를 다른 매개변수로 만들고 나서, 그 클래스를 새로 만들 때 매개변수를 빈 채로 대입하려고 하면 매개변수를 받지 않는 생성자가 없기 때문에 오류를 발생시키게 된다.

 

this (자기 자신 클래스)

또, 클래스에는 생략되어 있는 키워드가 있는데 이것이 바로 this 키워드이다. 이것은 클래스 자기 자신을 뜻하는 명령어로, 매개변수의 이름과 필드 이름이 같을 경우, 그리고 메소드에서 자기 자신을 리턴해야할 경우이다. 이 this는 클래스의 메소드에 가장 첫번째 매개변수에 생략되어 있다.

 

class Test {

	String a;
	public Test (String a){
    	a = a;
    }
}

 

이 형식이 있다고 하자, 그렇다면 저 매개변수에는 (Test this, String a)가 들어가 있는 형태이다. 하지만 앞에 있는 a와 뒤에 있는 a가 어떤 것인지 모르게 된다. 그렇다면 어떻게 될까? 자바의 모든 변수는 자기 자신에 가까울수록 우선시한다. 그렇기 때문에 저 생성자 메소드는 바깥에 있는 필드인 a가 아닌, 안에서 선언된 지역 변수인 a를 뜻하게 되고, 둘 다 지역 변수 a를 뜻하기 때문에 필드의 값은 변하지 않는다. 이럴 경우에 앞에 'this.'을 붙여준다. 그 클래스(자기 자신) 안에 있는 필드라는 뜻이다.

 

class Test {
	String a;
	public Test (String a) {
    	this.a = a;
    }
}

 

이런 식으로 선언하면, 뒤에 있는 a는 본인 우선 원칙에 따라 지역 변수 a가 되고, 앞에 있는 a는 this를 붙였기 때문에 자기 자신(여기서는 Test 클래스)의 바로 아래에 있는 변수, 즉 필드에 있는 a가 된다.

 

그리고 메소드에서 자기 자신을 리턴해야할 경우에도 쓰인다고 했는데, 이 기법을 체이닝 기법이라고 한다. 예를 들어 한 클래스에 메소드 A와 메소드 B가 있다고 해보자. 이럴 때 자기 자신을 리턴하게 된다면 이를 붙어서 쓸 수 있게 된다.

 

public Chaining {
	private String apple;
    private String banana;
    
    public Chaining setApple (String color) {
    	apple = color;
        return this;
    }
    
    public Chaining setBanana (String color) {
    	banana = color;
        return this;
    }
}

// 사용 예시
Chaining myCh = new Chaining();
myCh.setApple("Red").setBanana("Yellow");

이런 형태로 this를 리턴하게 되면 객체 자신을 리턴값으로 주게 된다. 그렇기 때문에 myCh(Chaining 클래스의 인스턴스)의 아래에 있는 메소드 setApple를 동작시키면 this(myCh 자기 자신)을 리턴시키게 되고, 그 아래에 있는 메소드인 setBanana도 동작시킬 수 있게 되어서 그 안에 있는 메소드를 또 이을 수 있게 된다. (즉 myCh.setApple("Red")도 myCh이기 때문에 이을 수 있다. 설정만 바꿀뿐 리턴은 객체 전체가 반환되기 때문이다.)

 

반응형

 

또, this는 자기 자신의 필드와 메소드 외에도 생성자에도 접근할 수 있는데, 이를 통해서 생성자를 여러 개 중에서 1개만 설정해서 활용할 수 있다. (나머지는 자기 자신의 생성자를 참조하면 되므로..)

 

class Person {
    String name;
    int age;
    String city;

    // 생성자 1: 모든 필드를 초기화하는 생성자
    public Person(String name, int age, String city) {
        this.name = name;
        this.age = age;
        this.city = city;
    }

    // 생성자 2: 이름과 나이만 초기화하고, 도시를 기본값("Unknown")으로 설정
    public Person(String name, int age) {
        this(name, age, "Unknown"); // this()로 생성자 1 호출
    }

    // 생성자 3: 이름만 초기화하고, 나이와 도시를 기본값(0, "Unknown")으로 설정
    public Person(String name) {
        this(name, 0); // this()로 생성자 2 호출
    }

    // 생성자 4: 기본 생성자로, 모든 필드를 기본값으로 설정
    public Person() {
        this("No Info"); // this()로 생성자 3 호출
    }

    // Person 객체의 정보를 출력하는 메소드
    public void displayInfo() {
        System.out.println("이름: " + name + ", 나이: " + age + ", 도시: " + city);
    }
}

 

 

 

클래스는 다른 클래스에 새롭게 인스턴스화 해서 활용하는 방안도 있지만, 그 클래스에 포함시키거나 상속시키는 관계도 존재한다. 이를 전자를 has ~ a 관계 (a는 b를 가지고 있다.), 후자를 is ~ a 관계 (a는 b이다.)라고 한다.

 

has ~ a 관계

이는 특정한 클래스가 다른 클래스를 필드로 소유하고 있을 때를 뜻한다. 그 특정한 클래스가 필드명으로 되어있는 다른 클래스를 포함하고 있다고 한다.

 

class Apple {
}

class Fruit {
    private Apple apple; // has ~ a 관계
    
    public Fruit (Apple apple) {
    	this.apple = apple;
    }
}

이처럼 Fruit 클래스 내부에 Apple 클래스가 필드명으로 선언되어서 그 클래스를 포함하고 있는 경우 has ~ a 관계라고 한다. 이 경우 그 클래스가 인스턴스화 되지는 않았지만 포함되어 있어서 그 클래스를 사용하는 것을 말한다. 이런 경우에 this는 당연히 자기 자신 클래스인 Fruit 클래스이다.

 

is ~ a 관계

이는 특정한 클래스의 모든 성분을 물려받는 다른 클래스가 있을 때를 뜻한다. 즉 이를 알려면 상속에 대한 이해가 필요하다. 상속은 자신의 모든 성분(필드, 생성자, 메소드)을 주는 슈퍼 클래스가 존재하고, 슈퍼 클래스의 모든 성분(필드, 생성자, 메소드)을 받는 서브 클래스가 존재한다.

 

그렇기 때문에 슈퍼 클래스는 각 서브 클래스간의 공통적인 부분이 담기게 되고, 서브 클래스는 그 공통적인 부분에서 파생되는 세부적인 부분이 들어가게 된다.

 

가운데에 있는 클래스는 그 위에 있는 슈퍼 클래스에게는 서브 클래스, 그 밑에 있는 서브 클래스에게는 슈퍼 클래스다.

그리고 슈퍼 클래스와 서브 클래스는 불변적인 것이 아니라, 자신 위에 클래스를 상속(공통된 부분을 받는다.)받게 되면 서브 클래스가 되고, 자신 밑에 클래스를 상속을 주게 되면 슈퍼 클래스가 된다. (참고로 자바에서 모든 클래스의 슈퍼 클래스로 Object 클래스를 받고 있다.)

 

자바에서 상속을 받는 방법은 그 클래스의 이름을 extends 뒤에 붙이는 것이고, 객체끼리의 상속은 단일 상속만 된다. 그렇기에 2개 이상의 클래스를 슈퍼 클래스로 받을 수는 없다.

 

class Car {
    // Car 클래스의 필드와 메소드
}

class SportsCar extends Car {
    // SportsCar 클래스만의 추가적인 필드와 메소드
    // Car 클래스의 모든 필드와 메소드를 상속받음
}

이 경우 SportsCar 클래스는 Car 클래스의 모든 부분인 필드, 메소드, 생성자들을 상속받게 된다. 그리고 SportsCar만의 추가적인 필드와 메소드를 선언할 수 있는 것이다.

 

오버라이딩

오버로딩이 기존에 로딩되는 것(메소드들이 실행되는 것)을 여러 개를 덧붙이는 것이라고 했다. 오버라이딩은 슈퍼클래스로부터 상속받은 메소드가 있을 것이다. 하지만, 그것이 마음에 들지 않거나 추가적인 기능을 넣어야할 경우가 존재할 것이다. 그럴 경우 슈퍼 클래스와 똑같은 이름의 메소드를 만들어서 덧쓰는 것이다. 즉, 슈퍼 클래스에서 정의된 메소드를 재정의하는 것을 오버라이딩이라고 한다.

 

class Animal {
    public void sound() {
        System.out.println("동물 소리");
    }
}

class Dog extends Animal {
    public void sound() {  // 슈퍼 클래스의 sound 메소드를 오버라이딩한다.
        System.out.println("멍멍");
    }
}

이 경우 Dog 클래스의 sound 메소드를 실행시킬 경우 오버라이딩(덮어쓰기)된 효과로 동물 소리가 나오는 것이 아닌, 멍멍이 나오게 된다. 오버라이딩은 슈퍼 클래스의 메소드와 이름과 매개변수의 형태와 갯수가 같아야 한다. 만약 메소드의 이름이 같지만 매개변수가 다르면 오버라이딩하는 것이 아니라 동일한 이름의 메소드를 하나 더 만드는 것으로 오버로딩(매개변수에 따라 메소드의 실행이 다르게 하는 것)하는 것이다.

 

super (슈퍼 클래스)

또, 서브 클래스에는 생략된 것이 더 있는데, 이것은 서브 클래스의 생성자 맨 처음에 super();가 숨겨져있다. super는 슈퍼 클래스를 뜻하는 것으로, 서브 클래스의 생성자 맨 처음에 super();가 숨겨져있다는 슈퍼 클래스의 생성자가 먼저 호출되고 그 뒤 서브 클래스의 생성자가 호출된다는 것이다.

 

class Fruit {
    String name;

    // 부모 클래스 생성자
    public Fruit (String name) {
        this.name = name;
        System.out.println("Super Class 생성자 호출");
    }
}

class Apple extends Fruit {
    public Apple (String name) {
        super(name); // 부모 생성자 호출 (이것이 평소에 생략되어 있다.)
        System.out.println("Sub Class 생성자 호출");
    }
}

이렇기 때문에 맨 처음에는 Super Class 생성자 호출이 나오고, 그 다음 Sub Class 생성자 호출이 나온다. (참고로 명시적으로 super를 쓸 경우에는 서브 클래스 생성자의 맨 첫 줄에 써야한다. 그렇지 않으면 오류가 나온다.) 이것은 오버라이딩을 할 경우에는 super가 없는데, 명시적으로 super를 쓰면 덮어쓰기를 하면서도 부모 클래스의 해당하는 부분을 가져오기 때문에 추가하는 효과를 만들기도 한다.

 

또, super는 this처럼 필드와 메소드를 불러오는 데에도 쓸 수 있다.

class Fruit {
    String type = "과일";
    public void printType() {
        System.out.println("과일은 맛있습니다.");
    }
}

class Apple extends Fruit {
    String type = "사과";

    public void printType() {
    	super.sound(); // 부모 클래스의 printType 메소드 호출
        System.out.println("사과는 새콤달콤합니다.");
    
        System.out.println(super.type); // 부모 클래스의 type 필드 접근
        System.out.println(this.type);  // 자식 클래스의 type 필드 접근
    }
}

이러면 super의 형태로 슈퍼 클래스의 필드와 메소드에 접근할 수 있다. 그리고 this를 하면 자기 자신의 필드와 메소드에 접근할 수 있다. 

 

LIST

'코딩 공부 > 자바(Java)' 카테고리의 다른 글

추상클래스와 인터페이스, 그리고 바인딩 (+메모리 영역)  (9) 2024.11.07
자바 설치 및 환경 변수 설정  (24) 2024.10.21
클래스  (12) 2024.10.19
함수  (3) 2024.10.14
배열 - 성적 처리 프로그램  (13) 2024.10.12