Java

[Java의 정석] chapter06 객체지향 프로그래밍 II - 요약정리

고쩡이 2024. 4. 15. 15:22

본 글은 위 책을 공부하고 정리한 내용입니다:)

🖤 1. 상속

1.1 상속의 정의와 장점

  • extends 키워드 사용.
  • 생성자, 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.

1.2 클래스간의 관계 - 포함관계

포함관계 : 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것

  • ~은 ~이다 → 상속관계
  • ~은 ~을 가지고 있다  → 포함관계 
  • ex_ 원은 점이다 X , 원은 점을 가지고있다 O
class Point {
	int x,y;
}

class Circle {
	Point c = new Point();
	int r;
}

1.4 단일 상속 (single inheritance)

자바에서는 오직 단일 상속만을 허용한다.

1.5 Object클래스 - 모든 클래스 조상

☀️Object 클래스

  • 모든 클래스 상속계층도의 최상위에 있는 조상클래스
  • 컴파일러가 클래스에 자동으로 extends Object 추가

🖤 2. 오버라이딩

2.1 오버라이딩이란?

조상 클래스로부터 상속받은 메서드 내용을 변경하는 것

2.2 오버라이딩 조건

자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와 선언부가 서로 일치해야한다.

  1. 이름이 같아야 한다.
  2. 매개변수가 같아야 한다.
  3. 반환타입이 같아야 한다.

조상 클래스의 메서드를 자손 클래스에서 오버라이딩 할 때

  1. 접근 제어자를 조상 클래스 메서드보다 좁으 범위로 변경할 수 없다.
  2. 예외는 조상 클래스 메서드보다 많이 선언할 수 없다.
  3. 인스턴스 메서드를 static메서드로 또는 그 반대로 변경할 수 없다.

2.3 오버로딩 vs 오버라이딩

오버로딩: 기존에 없는 새로운 메서드 정의 (new)

오버라이딩: 상속받은 메서드 내용 변경 (change, modify)

class Parent {
    void parentMethod() {}
}

class Child extends Parent {
    void parentMethod() {} // 오버라이딩
    void parentMEthod(int i) {} // 오버로딩
    
    void childMethod() {}
    void childMethod(int i) {} // 오버로딩
    void childMethod() {} // 에러. 중복정의!!!
}

2.4 super

자손클래스에서 조상 클래스로부터 상속받은 멤버 참조하는 참조변수

조상,자손 클래스 멤버가 중복 정의되어 서로 구별해야 하는 경우에만 super를 사용

class Parent{
	int x=10;
}
class Child extends Parent{
	void method(){
		System.out.println("x=" + x);
		System.out.println("this.x=" + this.x);
		System.out.println("super.x" + super.x);
	}
}

 

// method호출시
x = 10
this.x = 10
super.x = 10

아래는 위와 달리 같은 이름 멤버변수가 Parent에도 있을때이다. 이때에는 super.x와 this.x는 서로 다른 값을 참조한다.

class Parent{
	int x=10;
}
class Child extends Parent{
	int x=20;
	void method(){
		System.out.println("x=" + x);
		System.out.println("this.x=" + this.x);
		System.out.println("super.x" + super.x);
	}
}

 

// method호출시
x = 20
this.x = 20
super.x = 10

2.5 super() - 조상 클래스의 생성자

super()는 조상 클래스의 생성자를 호출한다.

Object 클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자, this() 또는 super()를 호출해야한다. 그렇지 않으면 컴파일러가 자동적으로 super();를 생성자의 첫 줄에 삽입한다.

 

예를들어 아래를 보자.

class Point{
	int x,y;
	Point(int x, int y){
		this.x = x;
		this.y = y;
	}
}
class Point3D extends Point{
	int z;
	Point3D(int x, int y, int z){ // 컴파일 에러 발생!!!
        this.x=x;
        this.y=y;
        this.z=z;
	}
	void method(){
		System.out.println(x +" " + y + " " + z);
	}
}

위는 컴파일에러가 발생한다. Point3D의 생성자 첫 줄에서 다른 생성자를 호출하지 않기 때문에, 컴파일러는 super();를 첫줄에 삽입한다. 그러나 Point 클래스에 생성자 Point()가 정의되어있지않기 때문에 컴파일 에러가 발생한 것이다.

	Point3D(int x, int y, int z){
        super(); // super(x,y);
        this.x=x;
        this.y=y;
        this.z=z;
	}

따라서, 이 에러를 수정하려면 Point클래스에 Point()를 추가하거나 Point3D에 super(x,y)를 추가해주면 된다.

참고로, 생성자가 정의되어 있는 클래스에는 컴파일러가 기본 생성자를 자동적으로 추가하지않는다.

조상 클래스의 멤버변수는 조상의 생성자에 의해 초기화되도록해야한다. 아래예시를 보자.

class Main {
	public static void main (String[] args) throws java.lang.Exception {
	    Point3D p3 = new Point3D();
	    System.out.println("p3.x=" + p3.x);
	    System.out.println("p3.y=" + p3.y);
	    System.out.println("p3.z=" + p3.z);
	}
}
class Point{
    int x=10;
    int y=20;
    
    Point(int x,int y) {
        this.x = x;
        this.y = y;
    }
}

class Point3D extends Point {
    int z = 30;
    
    Point3D() {
        this(100,200,300); // Point3D(int x, int y,int z) 호출
    }
    Point3D(int x, int y,int z){
        super(x, y); // Point(int x, int y)를 호출
        this.z = z;
    }
}
p3.x=100
p3.y=200
p3.z=300

 

위의 생성자 호출 순서는 다음과 같다. 거슬러올라가며 모든 조상클래스의 생성자가 순서대로 호출된다.

Point3D() → Point3D(int x, int y,int z) Point(int x, int y) Object()

🖤 4. 제어자 (modifier)

4.1 제어자란?

클래스, 변수 또는 메서드 선언부에 함께 사용되어 부가적인 의미를 부여한다. 크게 접근 제어자와 그 외로 나뉜다.

4.3 final - 마지막의, 변경될 수 없는

final은 거의 모든 대상에 사용될 수 있다. 변수에 사용시 상수가 되며, 메서드에 사용되면 오버라이드(재정의)할 수 없고 클래스에 사용되면 자신을 확장하는 자손 클래스를 정의하지 못하게 된다.

final class FinalTest { // 조상이 될 수 없는 클래스
    final int MAX_SIZE = 10; // 값 변경 불가 멤버변수 (상수)
    
    final void getMaxSize() { // 오버라이딩할 수 없는 메서드 (변경불가)
        final int LV = MAX_SIZE; // 값 변경 불가 지역변수 (상수수)
        return MAX_SIZE;
    }
}

또한, 인스턴스 변수의 경우 생성자에서 초기화가 가능하다.

class Card {
    final int NUM;
    
    Card(int num){ // 생성자에서 단 한 번 초기화 가능
        NUM = num;
    }
}

4.4 abstract - 추상의, 미완성의

abstract는 클래스,메서드에서 사용될 수 있다.

추상 클래스는 아직 완성되지 않은 메서드가 존재하는 '미완성 설계도'이다.

4.5 접근 제어자 (access modifier)

  • private: 같은 클래스 내 접근 가능
  • default: 같은 패키지 내 접근 가능
  • protected: 같은 패키지 내+다른 패키지 자손클래스에서 접근 가능
  • public: 접근 제한이 전혀 없다.

☀️캡슐화

  • 접근제어자를 사용하는 이유는 외부 데이터를 보호하기 위해서, 외부에 불필요한 내부 부분을 감추기 위해서이다.
  • 예를 들어, 멤버변수를 private이나 protected로 제한하고, public메서드를 제공함으로써 간접적으로 멤버변수 값을 다룰 수 있게 할 수 있다.

☀️생성자의 접근 제어자

  • 생성자의 접근 제어자를 private으로 지정하면, 외부에서 생성자에 접근할 수 없으므로 인스턴스를 생성할 수 없게 된다.
class SingleTon {
    // getInstance()에서 사용될 수 있도록 인스턴스가 미리 생성되어야 하므로 static
    private static Singleton s = new Singleton();
    private Singleton() {
        
    }
    // 인스턴스를 생성하지 않고도 호출가능해야하므로 static
    public static Singleton getInstance(){
        return s;
    }
}

 

  • 생성자가 private인 클래스는 다른 클래스의 조상이 될 수 없다. ( 자손클래스인스턴스생성시 조상클래스 생성자를 호출하는데, 이때 접근 제어자가 private이므로 호출 불가 ) 이럴때에는 클래스 앞에 final을 추가하여 상속할 수 없는 클래스임을 알리는 것이 좋다.
final class SingleTon {...}

 

다형성부터..to be continue...